mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-03-31 10:05:08 +00:00
Compare commits
1 Commits
dev/v3
...
ssh-echo-o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cb4f6396b |
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"isRoot": true,
|
|
||||||
"tools": {
|
|
||||||
"csharpier": {
|
|
||||||
"version": "1.2.4",
|
|
||||||
"commands": [
|
|
||||||
"csharpier"
|
|
||||||
],
|
|
||||||
"rollForward": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
247
.github/workflows/essentials-3-dev-build.yml
vendored
247
.github/workflows/essentials-3-dev-build.yml
vendored
@@ -1,247 +0,0 @@
|
|||||||
name: Essentials v3 Development Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- feature-3.0.0/*
|
|
||||||
- hotfix-3.0.0/*
|
|
||||||
- release-3.0.0/*
|
|
||||||
- development-3.0.0
|
|
||||||
|
|
||||||
env:
|
|
||||||
SOLUTION_PATH: .
|
|
||||||
SOLUTION_FILE: PepperDash.Essentials
|
|
||||||
VERSION: 0.0.0-buildtype-buildnumber
|
|
||||||
BUILD_TYPE: Debug
|
|
||||||
RELEASE_BRANCH: main
|
|
||||||
jobs:
|
|
||||||
Build_Project_4-Series:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# Detect environment (Act vs GitHub)
|
|
||||||
- name: Detect environment
|
|
||||||
id: detect_env
|
|
||||||
run: |
|
|
||||||
if [ -n "$ACT" ]; then
|
|
||||||
echo "is_local=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "is_local=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Install prerequisites
|
|
||||||
run: |
|
|
||||||
if [ "${{ steps.detect_env.outputs.is_local }}" == "true" ]; then
|
|
||||||
# For Act - no sudo needed
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y curl wget libicu-dev git unzip
|
|
||||||
else
|
|
||||||
# For GitHub runners - sudo required
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y curl wget libicu-dev git unzip
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Set Version Number
|
|
||||||
id: setVersion
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
latestVersion="3.0.0"
|
|
||||||
newVersion=$latestVersion
|
|
||||||
phase=""
|
|
||||||
newVersionString=""
|
|
||||||
|
|
||||||
if [[ $GITHUB_REF =~ ^refs/pull/.* ]]; then
|
|
||||||
phase="beta"
|
|
||||||
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
|
|
||||||
elif [[ $GITHUB_REF =~ ^refs/heads/hotfix-3.0.0/.* ]]; then
|
|
||||||
phase="hotfix"
|
|
||||||
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
|
|
||||||
elif [[ $GITHUB_REF =~ ^refs/heads/feature-3.0.0/.* ]]; then
|
|
||||||
phase="alpha"
|
|
||||||
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
|
|
||||||
elif [[ $GITHUB_REF == "refs/heads/development-3.0.0" ]]; then
|
|
||||||
phase="beta"
|
|
||||||
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
|
|
||||||
elif [[ $GITHUB_REF =~ ^refs/heads/release-3.0.0/.* ]]; then
|
|
||||||
version=$(echo $GITHUB_REF | awk -F '/' '{print $NF}' | sed 's/v//')
|
|
||||||
phase="rc"
|
|
||||||
newVersionString="${version}-${phase}-${GITHUB_RUN_NUMBER}"
|
|
||||||
else
|
|
||||||
# For local builds or unrecognized branches
|
|
||||||
newVersionString="${newVersion}-local"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "version=$newVersionString" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Create Build Properties file
|
|
||||||
- name: Create Build Properties
|
|
||||||
run: |
|
|
||||||
cat > Directory.Build.props << EOF
|
|
||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<Version>${{ steps.setVersion.outputs.version }}</Version>
|
|
||||||
<AssemblyVersion>${{ steps.setVersion.outputs.version }}</AssemblyVersion>
|
|
||||||
<FileVersion>${{ steps.setVersion.outputs.version }}</FileVersion>
|
|
||||||
<InformationalVersion>${{ steps.setVersion.outputs.version }}</InformationalVersion>
|
|
||||||
<PackageVersion>${{ steps.setVersion.outputs.version }}</PackageVersion>
|
|
||||||
<NuGetVersion>${{ steps.setVersion.outputs.version }}</NuGetVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Setup .NET
|
|
||||||
uses: actions/setup-dotnet@v3
|
|
||||||
with:
|
|
||||||
dotnet-version: '8.0.x'
|
|
||||||
|
|
||||||
- name: Restore NuGet Packages
|
|
||||||
run: dotnet restore ${SOLUTION_FILE}.sln
|
|
||||||
|
|
||||||
- name: Build Solution
|
|
||||||
run: dotnet build ${SOLUTION_FILE}.sln --configuration ${BUILD_TYPE} --no-restore
|
|
||||||
|
|
||||||
# Copy the CPZ file to the output directory with version in the filename
|
|
||||||
- name: Copy and Rename CPZ Files
|
|
||||||
run: |
|
|
||||||
mkdir -p ./output/cpz
|
|
||||||
|
|
||||||
# Find the main CPZ file in the build output
|
|
||||||
if [ -f "./src/PepperDash.Essentials/bin/${BUILD_TYPE}/net8/PepperDashEssentials.cpz" ]; then
|
|
||||||
cp "./src/PepperDash.Essentials/bin/${BUILD_TYPE}/net8/PepperDashEssentials.cpz" "./output/cpz/PepperDashEssentials.${{ steps.setVersion.outputs.version }}.cpz"
|
|
||||||
echo "Main CPZ file copied and renamed successfully."
|
|
||||||
else
|
|
||||||
echo "Warning: Main CPZ file not found at expected location."
|
|
||||||
find ./src -name "*.cpz" | xargs -I {} cp {} ./output/cpz/
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Pack Solution
|
|
||||||
run: dotnet pack ${SOLUTION_FILE}.sln --configuration ${BUILD_TYPE} --output ./output/nuget --no-build
|
|
||||||
|
|
||||||
# List build artifacts (runs in both environments)
|
|
||||||
- name: List Build Artifacts
|
|
||||||
run: |
|
|
||||||
echo "=== Build Artifacts ==="
|
|
||||||
echo "NuGet Packages:"
|
|
||||||
find ./output/nuget -type f | sort
|
|
||||||
echo ""
|
|
||||||
echo "CPZ/CPLZ Files:"
|
|
||||||
find ./output -name "*.cpz" -o -name "*.cplz" | sort
|
|
||||||
echo "======================="
|
|
||||||
|
|
||||||
# Enhanced package inspection for local runs
|
|
||||||
- name: Inspect NuGet Packages
|
|
||||||
if: steps.detect_env.outputs.is_local == 'true'
|
|
||||||
run: |
|
|
||||||
echo "=== NuGet Package Details ==="
|
|
||||||
for pkg in $(find ./output/nuget -name "*.nupkg"); do
|
|
||||||
echo "Package: $(basename "$pkg")"
|
|
||||||
echo "Size: $(du -h "$pkg" | cut -f1)"
|
|
||||||
|
|
||||||
# Extract and show package contents
|
|
||||||
echo "Contents:"
|
|
||||||
unzip -l "$pkg" | tail -n +4 | head -n -2
|
|
||||||
echo "--------------------------"
|
|
||||||
|
|
||||||
# Try to extract and show the nuspec file (contains metadata)
|
|
||||||
echo "Metadata:"
|
|
||||||
unzip -p "$pkg" "*.nuspec" 2>/dev/null | grep -E "(<id>|<version>|<description>|<authors>|<dependencies>)" || echo "Metadata extraction failed"
|
|
||||||
echo "--------------------------"
|
|
||||||
done
|
|
||||||
echo "==========================="
|
|
||||||
|
|
||||||
# Tag creation - GitHub version
|
|
||||||
- name: Create tag for non-rc builds (GitHub)
|
|
||||||
if: ${{ !contains(steps.setVersion.outputs.version, 'rc') && steps.detect_env.outputs.is_local == 'false' }}
|
|
||||||
run: |
|
|
||||||
git config --global user.name "GitHub Actions"
|
|
||||||
git config --global user.email "actions@github.com"
|
|
||||||
git tag ${{ steps.setVersion.outputs.version }}
|
|
||||||
git push --tags origin
|
|
||||||
|
|
||||||
# Tag creation - Act mock version
|
|
||||||
- name: Create tag for non-rc builds (Act Mock)
|
|
||||||
if: ${{ !contains(steps.setVersion.outputs.version, 'rc') && steps.detect_env.outputs.is_local == 'true' }}
|
|
||||||
run: |
|
|
||||||
echo "Would create git tag: ${{ steps.setVersion.outputs.version }}"
|
|
||||||
echo "Would push tag to: origin"
|
|
||||||
|
|
||||||
# Release creation - GitHub version
|
|
||||||
- name: Create Release (GitHub)
|
|
||||||
if: steps.detect_env.outputs.is_local == 'false'
|
|
||||||
id: create_release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
artifacts: 'output/cpz/*,output/**/*.cplz'
|
|
||||||
generateReleaseNotes: true
|
|
||||||
prerelease: ${{contains('debug', env.BUILD_TYPE)}}
|
|
||||||
tag: ${{ steps.setVersion.outputs.version }}
|
|
||||||
|
|
||||||
# Release creation - Act mock version with enhanced output
|
|
||||||
- name: Create Release (Act Mock)
|
|
||||||
if: steps.detect_env.outputs.is_local == 'true'
|
|
||||||
run: |
|
|
||||||
echo "=== Mock Release Creation ==="
|
|
||||||
echo "Would create release with:"
|
|
||||||
echo "- Tag: ${{ steps.setVersion.outputs.version }}"
|
|
||||||
echo "- Prerelease: ${{contains('debug', env.BUILD_TYPE)}}"
|
|
||||||
echo "- Artifacts matching pattern: output/cpz/*,output/**/*.cplz"
|
|
||||||
echo ""
|
|
||||||
echo "Matching artifacts:"
|
|
||||||
find ./output/cpz -type f
|
|
||||||
find ./output -name "*.cplz"
|
|
||||||
|
|
||||||
# Detailed info about release artifacts
|
|
||||||
echo ""
|
|
||||||
echo "Artifact Details:"
|
|
||||||
for artifact in $(find ./output/cpz -type f; find ./output -name "*.cplz"); do
|
|
||||||
echo "File: $(basename "$artifact")"
|
|
||||||
echo "Size: $(du -h "$artifact" | cut -f1)"
|
|
||||||
echo "Created: $(stat -c %y "$artifact")"
|
|
||||||
echo "MD5: $(md5sum "$artifact" | cut -d' ' -f1)"
|
|
||||||
echo "--------------------------"
|
|
||||||
done
|
|
||||||
echo "============================"
|
|
||||||
|
|
||||||
# NuGet setup - GitHub version
|
|
||||||
- name: Setup NuGet (GitHub)
|
|
||||||
if: steps.detect_env.outputs.is_local == 'false'
|
|
||||||
run: |
|
|
||||||
dotnet nuget add source https://nuget.pkg.github.com/pepperdash/index.json -n github -u pepperdash -p ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text
|
|
||||||
|
|
||||||
# NuGet setup - Act mock version
|
|
||||||
- name: Setup NuGet (Act Mock)
|
|
||||||
if: steps.detect_env.outputs.is_local == 'true'
|
|
||||||
run: |
|
|
||||||
echo "=== Mock NuGet Setup ==="
|
|
||||||
echo "Would add GitHub NuGet source: https://nuget.pkg.github.com/pepperdash/index.json"
|
|
||||||
echo "======================="
|
|
||||||
|
|
||||||
# Publish to NuGet - GitHub version
|
|
||||||
- name: Publish to Nuget (GitHub)
|
|
||||||
if: steps.detect_env.outputs.is_local == 'false'
|
|
||||||
run: dotnet nuget push ./output/nuget/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
|
|
||||||
|
|
||||||
# Publish to NuGet - Act mock version
|
|
||||||
- name: Publish to Nuget (Act Mock)
|
|
||||||
if: steps.detect_env.outputs.is_local == 'true'
|
|
||||||
run: |
|
|
||||||
echo "=== Mock Publish to NuGet ==="
|
|
||||||
echo "Would publish the following packages to https://api.nuget.org/v3/index.json:"
|
|
||||||
find ./output/nuget -name "*.nupkg" | sort
|
|
||||||
echo "============================="
|
|
||||||
|
|
||||||
# Publish to GitHub NuGet - GitHub version
|
|
||||||
- name: Publish to Github Nuget (GitHub)
|
|
||||||
if: steps.detect_env.outputs.is_local == 'false'
|
|
||||||
run: dotnet nuget push ./output/nuget/*.nupkg --source github --api-key ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# Publish to GitHub NuGet - Act mock version
|
|
||||||
- name: Publish to Github Nuget (Act Mock)
|
|
||||||
if: steps.detect_env.outputs.is_local == 'true'
|
|
||||||
run: |
|
|
||||||
echo "=== Mock Publish to GitHub NuGet ==="
|
|
||||||
echo "Would publish the following packages to the GitHub NuGet registry:"
|
|
||||||
find ./output/nuget -name "*.nupkg" | sort
|
|
||||||
echo "=================================="
|
|
||||||
@@ -52,7 +52,6 @@
|
|||||||
"_appLogoPath": "docs/images/favicon-32x32.png",
|
"_appLogoPath": "docs/images/favicon-32x32.png",
|
||||||
"_appFaviconPath": "docs/images/favicon.ico",
|
"_appFaviconPath": "docs/images/favicon.ico",
|
||||||
"_disableToc": false,
|
"_disableToc": false,
|
||||||
"_enableNewTab": true,
|
|
||||||
"pdf": false
|
"pdf": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Types of things in `DeviceManager`:
|
|||||||
|
|
||||||
A Device doesn't always represent a physical piece of hardware, but rather a logical construct that "does something" and is used by one or more other devices in the running program. For example, we create a room device, and its corresponding Fusion device, and that room has a Cisco codec device, with an attached SSh client device. All of these lie in a flat collection in the `DeviceManager`.
|
A Device doesn't always represent a physical piece of hardware, but rather a logical construct that "does something" and is used by one or more other devices in the running program. For example, we create a room device, and its corresponding Fusion device, and that room has a Cisco codec device, with an attached SSh client device. All of these lie in a flat collection in the `DeviceManager`.
|
||||||
|
|
||||||
> The `DeviceManager` is nothing more than a modified collection of things, and technically those things don't have to be Devices, but must at least implement the `IKeyed` (`PepperDash.Core.IKeyed`) interface (simply so items can be looked up by their key.) Items in the `DeviceManager` that are Devices are run through additional steps of [activation](~/docs/technical-docs/Arch-activate.md#2-pre-activation) at startup. This collection of devices is all interrelated by their string keys.
|
> The `DeviceManager` is nothing more than a modified collection of things, and technically those things don't have to be Devices, but must at least implement the `IKeyed` (`PepperDash.Core.IKeyed`) interface (simply so items can be looked up by their key.) Items in the `DeviceManager` that are Devices are run through additional steps of [activation](~/docs/Arch-activate.md#2-pre-activation) at startup. This collection of devices is all interrelated by their string keys.
|
||||||
|
|
||||||
In this flat design, we spin up devices, and then introduce them to their "coworkers and bosses" - the other devices and logical units that they will interact with - and get them all operating together to form a running unit. For example: A room configuration will contain a "VideoCodecKey" property and a "DefaultDisplayKey" property. The `DeviceManager` provides the room with the codec or displays having the appropriate keys. What the room does with those is dependent on its coding.
|
In this flat design, we spin up devices, and then introduce them to their "coworkers and bosses" - the other devices and logical units that they will interact with - and get them all operating together to form a running unit. For example: A room configuration will contain a "VideoCodecKey" property and a "DefaultDisplayKey" property. The `DeviceManager` provides the room with the codec or displays having the appropriate keys. What the room does with those is dependent on its coding.
|
||||||
|
|
||||||
@@ -38,4 +38,4 @@ This flat structure ensures that every device in a system exists in one place an
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Next: [Configurable lifecycle](~/docs/technical-docs/Arch-lifecycle.md)
|
Next: [Configurable lifecycle](~/docs/Arch-lifecycle.md)
|
||||||
@@ -105,7 +105,7 @@ Each of the three activation phases operates in a try/catch block for each devic
|
|||||||
|
|
||||||
In any real-world system, devices and business logic need to talk to each other, otherwise, what's the point of all this coding? When creating your classes and configuration, it is best practice to _try_ not to "plug" one device into another during construction or activation. For example your touchpanel controller class has a `Display1` property that holds the display-1 object. Rather, it may be better to refer to the device as it is stored in the `DeviceManager` when it's needed using the static `DeviceManager.GetDeviceForKey(key)` method to get a reference to the device, which can be cast using various interfaces/class types, and then interacted with. This prevents objects from being referenced in places where the developer may later forget to dereference them, causing memory leak. This will become more important as Essentials becomes more able to be reconfigured at runtime.
|
In any real-world system, devices and business logic need to talk to each other, otherwise, what's the point of all this coding? When creating your classes and configuration, it is best practice to _try_ not to "plug" one device into another during construction or activation. For example your touchpanel controller class has a `Display1` property that holds the display-1 object. Rather, it may be better to refer to the device as it is stored in the `DeviceManager` when it's needed using the static `DeviceManager.GetDeviceForKey(key)` method to get a reference to the device, which can be cast using various interfaces/class types, and then interacted with. This prevents objects from being referenced in places where the developer may later forget to dereference them, causing memory leak. This will become more important as Essentials becomes more able to be reconfigured at runtime.
|
||||||
|
|
||||||
As an example, [connection-based routing](~/docs/technical-docs/Connection-based-routing.md#essentials-connection-based-routing) uses these methods. When a route is requested, the collection of tielines and devices is searched for the devices and paths necessary to complete a route, but there are no devices or tie lines that are object-referenced in running code. It can all be torn down and reconfigured without any memory-management dereferencing, setting things to null.
|
As an example, [connection-based routing](~/docs/Connection-based-routing.md#essentials-connection-based-routing) uses these methods. When a route is requested, the collection of tielines and devices is searched for the devices and paths necessary to complete a route, but there are no devices or tie lines that are object-referenced in running code. It can all be torn down and reconfigured without any memory-management dereferencing, setting things to null.
|
||||||
|
|
||||||
## Device Initialization
|
## Device Initialization
|
||||||
|
|
||||||
@@ -155,4 +155,4 @@ Robust C#-based system code should not depend on "order" or "time" to get runnin
|
|||||||
|
|
||||||
When designing new Device-based classes, be it rooms, devices, port controllers, bridges, make them as independent as possible. They could exist alone in a program with no required partner objects, and just quietly exist without failing. We want the system to be fast and flexible, and keeping the interdependence between objects at a minimum improves this flexibility into the future.
|
When designing new Device-based classes, be it rooms, devices, port controllers, bridges, make them as independent as possible. They could exist alone in a program with no required partner objects, and just quietly exist without failing. We want the system to be fast and flexible, and keeping the interdependence between objects at a minimum improves this flexibility into the future.
|
||||||
|
|
||||||
Next: [More architecture](~/docs/technical-docs/Arch-topics.md)
|
Next: [More architecture](~/docs/Arch-topics.md)
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
The diagram below describes how Essentials gets a program up and running.
|
The diagram below describes how Essentials gets a program up and running.
|
||||||
|
|
||||||
(The various activation phases are covered in more detail on the [next page](~/docs/technical-docs/Arch-activate.md))
|
(The various activation phases are covered in more detail on the [next page](~/docs/Arch-activate.md))
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Next: [Activation phases](~/docs/technical-docs/Arch-activate.md)
|
Next: [Activation phases](~/docs/Arch-activate.md)
|
||||||
@@ -16,4 +16,4 @@ The diagram below shows the reference dependencies that exist between the differ
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Next: [Architecture](~/docs/technical-docs/Arch-1.md)
|
Next: [Architecture](~/docs/Arch-1.md)
|
||||||
@@ -6,10 +6,10 @@ One of the most powerful features of Essentials is the ability to bridge SIMPL t
|
|||||||
|
|
||||||
Follow the links below for examples of bridging to hardware and network resources.
|
Follow the links below for examples of bridging to hardware and network resources.
|
||||||
|
|
||||||
**[GenericComm Bridging](~/docs/usage/GenericComm.md)**
|
**[GenericComm Bridging](~/docs/GenericComm.md)**
|
||||||
|
|
||||||
**[RelayOutput Bridging](~/docs/usage/RelayOutput.md)**
|
**[RelayOutput Bridging](~/docs/RelayOutput.md)**
|
||||||
|
|
||||||
**[Digital Input Bridging](~/docs/usage/DigitalInput.md)**
|
**[Digital Input Bridging](~/docs/DigitalInput.md)**
|
||||||
|
|
||||||
**[Card Frame Bridging](~/docs/CardFrame.md)**
|
**[Card Frame Bridging](~/docs/CardFrame.md)**
|
||||||
@@ -4,44 +4,23 @@
|
|||||||
[YouTube Video - Getting Started with PepperDash Essentials](https://youtu.be/FxEZtbpCwiQ)
|
[YouTube Video - Getting Started with PepperDash Essentials](https://youtu.be/FxEZtbpCwiQ)
|
||||||
***
|
***
|
||||||
|
|
||||||
## Get a CPZ
|
## Download or clone
|
||||||
|
|
||||||
### Prerequisites
|
You may clone Essentials at <https://github.com/PepperDash/Essentials.git>
|
||||||
|
|
||||||
* [VS Code](https://code.visualstudio.com/)
|
|
||||||
* [.NET 9 SDK](https://dotnet.microsoft.com/en-us/download)
|
|
||||||
* [Git](https://git-scm.com/)
|
|
||||||
|
|
||||||
> Note: Essentials 2.x.x uses .NET Framework 4.7.2 currently. The .NET 9 SDK will build the project with the appropriate references
|
|
||||||
|
|
||||||
### Build From Source
|
|
||||||
|
|
||||||
1. Clone the repo: `git clone https://github.com/PepperDash/Essentials.git`
|
|
||||||
2. Open the folder in VS Code
|
|
||||||
3. Build using the dotnet CLI: `dotnet build`
|
|
||||||
|
|
||||||
### Download the latest release
|
|
||||||
|
|
||||||
The latest release can be found on [Github](https://github.com/PepperDash/Essentials/releases/latest)
|
|
||||||
|
|
||||||
## How to Get Started
|
## How to Get Started
|
||||||
|
|
||||||
2. Using an SFTP client or Crestron Toolbox, load the downloaded (or built) cpz to the processor in program slot 1
|
This section assumes knowledge of loading programs to and working with the file system on a Crestron processor.
|
||||||
1. If using SFTP, connect via SSH and start the program by sending console command `progload -p:1`
|
|
||||||
3. On first boot, the Essentials Application will build the necessary configuration folder structure in the user/program1/ path.
|
|
||||||
4. The application has some example configuration files included. Copy `/Program01/Example Configuration/EssentialsSpaceHuddleRoom/configurationFile-HuddleSpace-2-Source.json` to the `/User/Program1/` folder.
|
|
||||||
6. Reset the program via console `progreset -p:1`. The program will load the example configuration file.
|
|
||||||
|
|
||||||
Once Essentials is running with a valid configuration, the following console commands can be used to see what's going on:
|
1. Using an SFTP client, load `PepperDashEssentials1.4.32.cpz` to the processor in program slot 1 and start the program by sending console command `progload -p:1`
|
||||||
|
1. On first boot, the Essentials Application will build the necessary configuration folder structure in the User/Program1/ path.
|
||||||
|
1. The application has some example configuration files included. Copy `/Program01/Example Configuration/EssentialsSpaceHuddleRoom/configurationFile-HuddleSpace-2-Source.json` to the `/User/Program1/` folder.
|
||||||
|
1. Copy the SGD files from `/Program01/SGD` to `/User/Program1/sgd`
|
||||||
|
1. Reset the program via console `progreset -p:1`. The program will load the example configuration file.
|
||||||
|
1. Via console, you can run the `devlist:1` command to get some insight into what has been loaded from the configuration file into the system . This will print the basic device information in the form of ["key"] "Name". The "key" value is what we can use to interact with each device uniquely.
|
||||||
|
1. Run the command `devprops:1 display-1`. This will print the real-time property values of the device with key "display-1".
|
||||||
|
1. Run the command `devmethods:1 display-1`. This will print the public methods available for the device with key "display-1".
|
||||||
|
1. Run the command `devjson:1 {"deviceKey":"display-1","methodName":"PowerOn", "params": []}`. This will call the method PowerOn() on the device with key "display-1".
|
||||||
|
1. Run the provided example XPanel SmartGraphics project and connect to your processor at the appropriate IPID.
|
||||||
|
|
||||||
* ```devlist:1```
|
Next: [Standalone use](~/docs/Standalone-Use.md)
|
||||||
* Print the list of devices in [{key}] {name} format
|
|
||||||
* The key of a device can be used with the rest of the commands to get more information
|
|
||||||
* `devprops:1 {deviceKey}`
|
|
||||||
* Print the real-time property values of the device with key "display-1".
|
|
||||||
* `devmethods:1 display-1`
|
|
||||||
* Print the public methods available for the device with key "display-1".
|
|
||||||
* `devjson:1 {"deviceKey":"display-1","methodName":"PowerOn", "params": []}`
|
|
||||||
* Call the method `PowerOn()` on the device with key "display-1".
|
|
||||||
|
|
||||||
Next: [Standalone use](~/docs/usage/Standalone-Use.md)
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ Thanks!
|
|||||||
|
|
||||||
## Collaboration
|
## Collaboration
|
||||||
|
|
||||||
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/technical-docs/Plugins.md)
|
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/Plugins.md)
|
||||||
|
|
||||||
### Open-source-collaborative workflow
|
### Open-source-collaborative workflow
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Deprecated
|
# Deprecated
|
||||||
|
|
||||||
**Note : this entry is out of date - please see [Plugins](~/docs/technical-docs/Plugins.md)**
|
**Note : this entry is out of date - please see [Plugins](~/docs/Plugins.md)**
|
||||||
|
|
||||||
## What are Essentials Plugins?
|
## What are Essentials Plugins?
|
||||||
|
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
|
|||||||
|
|
||||||
1. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
|
1. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
|
||||||
|
|
||||||
1. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/technical-docs/Feedback-Classes.md) for this.
|
1. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/Feedback-Classes.md) for this.
|
||||||
|
|
||||||
1. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing DisplayControllerJoinMap. This way, you can swap plugins without needing any change on the Simpl Windows side. This is extremely powerful when maintaining Simpl Windows code bases for large deployments that may utilize differing equipment per room. If you can build a Simpl Windows program that interacts with established join maps, you can swap out the device via config without any change needed to Simpl Windows.
|
1. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing DisplayControllerJoinMap. This way, you can swap plugins without needing any change on the Simpl Windows side. This is extremely powerful when maintaining Simpl Windows code bases for large deployments that may utilize differing equipment per room. If you can build a Simpl Windows program that interacts with established join maps, you can swap out the device via config without any change needed to Simpl Windows.
|
||||||
|
|
||||||
@@ -474,4 +474,4 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
|
|||||||
|
|
||||||
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
|
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
|
||||||
|
|
||||||
Next: [Essentials architecture](~/docs/technical-docs/Arch-summary.md)
|
Next: [Essentials architecture](~/docs/Arch-summary.md)
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
|
|||||||
|
|
||||||
3. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
|
3. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
|
||||||
|
|
||||||
4. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/technical-docs/Feedback-Classes.md) for this.
|
4. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/Feedback-Classes.md) for this.
|
||||||
|
|
||||||
5. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing `DisplayControllerJoinMap`. This way, you can swap plugins without needing any change on the SIMPL Windows side. This is extremely powerful when maintaining SIMPL Windows code bases for large deployments that may utilize differing equipment per room. If you can build a SIMPL Windows program that interacts with established join maps, you can swap out the device via config without any change needed to SIMPL Windows.
|
5. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing `DisplayControllerJoinMap`. This way, you can swap plugins without needing any change on the SIMPL Windows side. This is extremely powerful when maintaining SIMPL Windows code bases for large deployments that may utilize differing equipment per room. If you can build a SIMPL Windows program that interacts with established join maps, you can swap out the device via config without any change needed to SIMPL Windows.
|
||||||
|
|
||||||
@@ -302,7 +302,7 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
|
|||||||
|
|
||||||
## Join Map Documentation
|
## Join Map Documentation
|
||||||
|
|
||||||
[Join Map Documentation](~/docs/usage/JoinMaps.md)
|
[Join Map Documentation](~/docs/JoinMaps.md)
|
||||||
|
|
||||||
## Device Type Join Maps
|
## Device Type Join Maps
|
||||||
|
|
||||||
@@ -408,4 +408,4 @@ Please note that these joinmaps _may_ be using a deprecated implementation. The
|
|||||||
|
|
||||||
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
|
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
|
||||||
|
|
||||||
Next: [Essentials architecture](~/docs/technical-docs/Arch-summary.md)
|
Next: [Essentials architecture](~/docs/Arch-summary.md)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# SIMPL Windows Bridging
|
# SIMPL Windows Bridging
|
||||||
|
|
||||||
**Note : this entry is out of date - please see [SIMPL Windows Bridging - Updated](~/docs/usage/SIMPL-Bridging-Updated.md)**
|
**Note : this entry is out of date - please see [SIMPL Windows Bridging - Updated](~/docs/SIMPL-Bridging-Updated.md)**
|
||||||
|
|
||||||
Essentials allows for devices defined within the SIMPL# Pro application to be bridged to a SIMPL Windows application over Ethernet Intersystem Communication (EISC). This allows a SIMPL Windows program to take advantage of some of the features of the SIMPL# Pro environment, without requiring the entire application to be written in C#.
|
Essentials allows for devices defined within the SIMPL# Pro application to be bridged to a SIMPL Windows application over Ethernet Intersystem Communication (EISC). This allows a SIMPL Windows program to take advantage of some of the features of the SIMPL# Pro environment, without requiring the entire application to be written in C#.
|
||||||
|
|
||||||
@@ -356,7 +356,7 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
|
|||||||
|
|
||||||
1. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
|
1. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
|
||||||
|
|
||||||
1. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/technical-docs/Feedback-Classes.md) for this.
|
1. For devices included in Essentials, you will be able to find defined join maps below. If you are building your own plugins, you will need to build the join map yourself. It would be beneficial to review the wiki entry on the [Feedback Class](~/docs/Feedback-Classes.md) for this.
|
||||||
|
|
||||||
1. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing DisplayControllerJoinMap. This way, you can swap plugins without needing any change on the Simpl Windows side. This is extremely powerful when maintaining Simpl Windows code bases for large deployments that may utilize differing equipment per room. If you can build a Simpl Windows program that interacts with established join maps, you can swap out the device via config without any change needed to Simpl Windows.
|
1. When building plugins, we highly recommend reusing JoinMaps, as this will make code more easily interchangeable. For example; if you were to build a display plugin, we'd recommend you use/extend the existing DisplayControllerJoinMap. This way, you can swap plugins without needing any change on the Simpl Windows side. This is extremely powerful when maintaining Simpl Windows code bases for large deployments that may utilize differing equipment per room. If you can build a Simpl Windows program that interacts with established join maps, you can swap out the device via config without any change needed to Simpl Windows.
|
||||||
|
|
||||||
@@ -472,4 +472,4 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
|
|||||||
|
|
||||||
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
|
We've provided an [example program](https://github.com/PepperDash/EssentialsSIMPLWindowsBridgeExample) for SIMPL Windows that works with the provided example Essentials configuration file [SIMPLBridgeExample_configurationFile.json](https://github.com/PepperDash/Essentials/blob/main/PepperDashEssentials/Example%20Configuration/SIMPLBridging/SIMPLBridgeExample_configurationFile.json). Load Essentials and the example SIMPL program to two slots on the same processor and you can get a better idea of how to take advantage of SIMPL Windows bridging.
|
||||||
|
|
||||||
Next: [Essentials architecture](~/docs/technical-docs/Arch-summary.md)
|
Next: [Essentials architecture](~/docs/Arch-summary.md)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ By defining devices and a room in a JSON configuration file, Essentials can cont
|
|||||||
|
|
||||||
### Devices
|
### Devices
|
||||||
|
|
||||||
Essentials supports device plugins for communicating with various devices using both standard Crestron CIP communications, Cresnet, SSH, or other TCP/IP-based communication methods. See [the Plugins section](~/docs/technical-docs/Plugins.md) for more details
|
Essentials supports device plugins for communicating with various devices using both standard Crestron CIP communications, Cresnet, SSH, or other TCP/IP-based communication methods. See [the Plugins section](~/docs/Plugins.md) for more details
|
||||||
|
|
||||||
### Rooms
|
### Rooms
|
||||||
|
|
||||||
@@ -16,4 +16,4 @@ In order to tie together equipment into a unit that comprises what devices are u
|
|||||||
|
|
||||||
See Also: [[Supported Devices|Supported-Devices]]
|
See Also: [[Supported Devices|Supported-Devices]]
|
||||||
|
|
||||||
Next: [Simpl Windows bridging](~/docs/usage/SIMPL-Bridging-Updated.md)
|
Next: [Simpl Windows bridging](~/docs/SIMPL-Bridging-Updated.md)
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
# How to Add Documentation to Essentials
|
|
||||||
|
|
||||||
This guide explains how to add new documentation articles to the Essentials docFx site.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Essentials documentation uses [docFx](https://dotnet.github.io/docfx/) to generate a static documentation website. Documentation files are organized in a hierarchical structure with a table of contents (TOC) file that defines the site navigation. Documentation should be organized and written to fit into the [Diátaxis](https://diataxis.fr/start-here/) conceptual framework.
|
|
||||||
|
|
||||||
## Documentation Structure
|
|
||||||
|
|
||||||
Documentation files are located in `/docs/docs/` and organized into the following subdirectories:
|
|
||||||
|
|
||||||
- **how-to/** - Step-by-step guides and tutorials
|
|
||||||
- **usage/** - Usage documentation for SIMPL bridging, standalone use, and hardware integration
|
|
||||||
- **technical-docs/** - Technical documentation including architecture, plugins, and API references
|
|
||||||
- **images/** - Image assets used in documentation
|
|
||||||
|
|
||||||
## Adding a New Document
|
|
||||||
|
|
||||||
### Step 1: Create Your Markdown File
|
|
||||||
|
|
||||||
1. Determine which category your document belongs to (how-to, usage, or technical-docs)
|
|
||||||
2. Create a new `.md` file in the appropriate subdirectory
|
|
||||||
3. Use a descriptive filename with hyphens (e.g., `my-new-feature.md`)
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
# For a how-to guide
|
|
||||||
touch /docs/docs/how-to/configure-audio-settings.md
|
|
||||||
|
|
||||||
# For usage documentation
|
|
||||||
touch /docs/docs/usage/video-switcher-control.md
|
|
||||||
|
|
||||||
# For technical documentation
|
|
||||||
touch /docs/docs/technical-docs/custom-device-plugin.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Write Your Content
|
|
||||||
|
|
||||||
Start your markdown file with a level 1 heading (`#`) that serves as the page title:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Your Document Title
|
|
||||||
|
|
||||||
Brief introduction to the topic.
|
|
||||||
|
|
||||||
## Section Heading
|
|
||||||
|
|
||||||
Content goes here...
|
|
||||||
|
|
||||||
### Subsection
|
|
||||||
|
|
||||||
More detailed content...
|
|
||||||
```
|
|
||||||
|
|
||||||
**Markdown Features:**
|
|
||||||
- Use standard markdown syntax
|
|
||||||
- Include code blocks with language specifiers (```csharp, ```json, etc.)
|
|
||||||
- Add images: ``
|
|
||||||
- Link to other docs: `[Link text](../usage/related-doc.md)`
|
|
||||||
|
|
||||||
### Step 3: Add to Table of Contents
|
|
||||||
|
|
||||||
Edit `/docs/docs/toc.yml` to add your new document to the navigation:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: How-to's
|
|
||||||
items:
|
|
||||||
- href: how-to/how-to-add-docs.md
|
|
||||||
- href: how-to/your-new-doc.md # Add your document here
|
|
||||||
```
|
|
||||||
|
|
||||||
**TOC Structure:**
|
|
||||||
- `name:` - Display name in the navigation menu
|
|
||||||
- `href:` - Relative path to the markdown file
|
|
||||||
- `items:` - Nested items for hierarchical navigation
|
|
||||||
|
|
||||||
**Example with nested items:**
|
|
||||||
```yaml
|
|
||||||
- name: Usage
|
|
||||||
items:
|
|
||||||
- name: SIMPL Bridging
|
|
||||||
href: usage/SIMPL-Bridging-Updated.md
|
|
||||||
items:
|
|
||||||
- name: Your Sub-Topic
|
|
||||||
href: usage/your-sub-topic.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Test Locally
|
|
||||||
|
|
||||||
Build and preview the docFx site locally to verify your changes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Navigate to the docs directory
|
|
||||||
cd docs
|
|
||||||
|
|
||||||
# Build the documentation
|
|
||||||
docfx build
|
|
||||||
|
|
||||||
# Serve the site locally
|
|
||||||
docfx serve _site
|
|
||||||
```
|
|
||||||
|
|
||||||
Then open your browser to `http://localhost:8080` to view the site.
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### File Naming
|
|
||||||
- Use lowercase with hyphens: `my-document-name.md`
|
|
||||||
- Be descriptive but concise
|
|
||||||
- Avoid special characters
|
|
||||||
|
|
||||||
### Content Guidelines
|
|
||||||
- Start with a clear introduction
|
|
||||||
- Use hierarchical headings (H1 → H2 → H3)
|
|
||||||
- Include code examples where appropriate
|
|
||||||
- Add images to illustrate complex concepts
|
|
||||||
- Link to related documentation
|
|
||||||
|
|
||||||
### TOC Organization
|
|
||||||
- Group related documents under the same parent
|
|
||||||
- Order items logically (basic → advanced)
|
|
||||||
- Keep the TOC hierarchy shallow (2-3 levels max)
|
|
||||||
- Use clear, descriptive names for navigation items
|
|
||||||
|
|
||||||
## Common Issues
|
|
||||||
|
|
||||||
### Document Not Appearing
|
|
||||||
- Verify the file path in `toc.yml` is correct and uses forward slashes
|
|
||||||
- Ensure the markdown file exists in the specified location
|
|
||||||
- Check for YAML syntax errors in `toc.yml`
|
|
||||||
|
|
||||||
### Images Not Loading
|
|
||||||
- Verify image path is relative to the markdown file location
|
|
||||||
- Use `../images/` for files in the images directory
|
|
||||||
- Ensure image files are committed to the repository
|
|
||||||
|
|
||||||
### Broken Links
|
|
||||||
- Use relative paths for internal links
|
|
||||||
- Test all links after building the site
|
|
||||||
- Use `.md` extension when linking to other documentation files
|
|
||||||
|
|
||||||
## Additional Resources
|
|
||||||
|
|
||||||
- [docFx Documentation](https://dotnet.github.io/docfx/)
|
|
||||||
- [Markdown Guide](https://www.markdownguide.org/)
|
|
||||||
- [YAML Syntax](https://yaml.org/spec/1.2/spec.html)
|
|
||||||
- [Diátaxis](https://diataxis.fr/start-here/)
|
|
||||||
@@ -1,52 +1,48 @@
|
|||||||
- name: Get Started With Essentials
|
- name: Get Started With Essentials
|
||||||
- href: ../index.md
|
- href: ../index.md
|
||||||
- href: Get-started.md
|
- href: Get-started.md
|
||||||
- name: How-to's
|
|
||||||
items:
|
|
||||||
- name: How to add an article or doc page
|
|
||||||
href: how-to/how-to-add-docs.md
|
|
||||||
- name: Usage
|
- name: Usage
|
||||||
items:
|
items:
|
||||||
- href: usage/Standalone-Use.md
|
- href: Standalone-Use.md
|
||||||
- href: usage/SIMPL-Bridging-Updated.md
|
- href: SIMPL-Bridging-Updated.md
|
||||||
items:
|
items:
|
||||||
- name: Join Maps
|
- name: Join Maps
|
||||||
href: usage/JoinMaps.md
|
href: JoinMaps.md
|
||||||
- name: Bridging to Hardware Resources
|
- name: Bridging to Hardware Resources
|
||||||
href: usage/Bridging-To-Hardware-Resources.md
|
href: Bridging-To-Hardware-Resources.md
|
||||||
items:
|
items:
|
||||||
- name: GenericComm Bridging
|
- name: GenericComm Bridging
|
||||||
href: usage/GenericComm.md
|
href: GenericComm.md
|
||||||
- name: RelayOutput Bridging
|
- name: RelayOutput Bridging
|
||||||
href: usage/RelayOutput.md
|
href: RelayOutput.md
|
||||||
- name: Digital Input Bridging
|
- name: Digital Input Bridging
|
||||||
href: usage/DigitalInput.md
|
href: DigitalInput.md
|
||||||
- name: IR Driver Bridging
|
- name: IR Driver Bridging
|
||||||
href: usage/IR-Driver-Bridging.md
|
href: IR-Driver-Bridging.md
|
||||||
- name: Technical documentation
|
- name: Technical documentation
|
||||||
items:
|
items:
|
||||||
- href: technical-docs/Arch-summary.md
|
- href: Arch-summary.md
|
||||||
- name: Devices and DeviceManager
|
- name: Devices and DeviceManager
|
||||||
href: technical-docs/Arch-1.md
|
href: Arch-1.md
|
||||||
- name: Configurable lifecycle
|
- name: Configurable lifecycle
|
||||||
href: technical-docs/Arch-lifecycle.md
|
href: Arch-lifecycle.md
|
||||||
- name: Activation phases
|
- name: Activation phases
|
||||||
href: technical-docs/Arch-activate.md
|
href: Arch-activate.md
|
||||||
- name: More
|
- name: More
|
||||||
href: technical-docs/Arch-topics.md
|
href: Arch-topics.md
|
||||||
- name: Plugins
|
- name: Plugins
|
||||||
href: technical-docs/Plugins.md
|
href: Plugins.md
|
||||||
- name: Communication Basics
|
- name: Communication Basics
|
||||||
href: technical-docs/Communication-Basics.md
|
href: Communication-Basics.md
|
||||||
- name: Debugging
|
- name: Debugging
|
||||||
href: technical-docs/Debugging.md
|
href: Debugging.md
|
||||||
- name: Feedback Classes
|
- name: Feedback Classes
|
||||||
href: technical-docs/Feedback-Classes.md
|
href: Feedback-Classes.md
|
||||||
- name: Connection Based Routing
|
- name: Connection Based Routing
|
||||||
href: technical-docs/Connection-Based-Routing.md
|
href: Connection-Based-Routing.md
|
||||||
- name: Configuration Structure
|
- name: Configuration Structure
|
||||||
href: technical-docs/ConfigurationStructure.md
|
href: ConfigurationStructure.md
|
||||||
- name: Supported Devices
|
- name: Supported Devices
|
||||||
href: technical-docs/Supported-Devices.md
|
href: Supported-Devices.md
|
||||||
- name: Glossary of Terms
|
- name: Glossary of Terms
|
||||||
href: technical-docs/Glossary-of-Terms.md
|
href: Glossary-of-Terms.md
|
||||||
@@ -8,12 +8,12 @@ Essentials is a collection of C# libraries that can be used in many ways. It is
|
|||||||
|
|
||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
- [Download an Essentials build or clone the repo](~/docs/Get-started.md)
|
- [Download essentials build or clone repo](~/docs/Get-started.md)
|
||||||
- [Get started](~/docs/Get-started.md)
|
- [How to get started](~/docs/Get-started.md)
|
||||||
- [YouTube Video Series Playlist](https://youtube.com/playlist?list=PLKOoNNwgPFZdV5wDEBDZxTHu1KROspaBu)
|
- [YouTube Video Series Playlist](https://youtube.com/playlist?list=PLKOoNNwgPFZdV5wDEBDZxTHu1KROspaBu)
|
||||||
- [Discord Server](https://discord.gg/6Vh3ssDdPs)
|
- [Discord Server](https://discord.gg/6Vh3ssDdPs)
|
||||||
|
|
||||||
Or use the links to the left to navigate our documentation.
|
Or use the links to the right to navigate our documentation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -25,12 +25,21 @@ Or use the links to the left to navigate our documentation.
|
|||||||
- Shared resources made easily available
|
- Shared resources made easily available
|
||||||
- More flexibility with less code
|
- More flexibility with less code
|
||||||
- Configurable using simple JSON files
|
- Configurable using simple JSON files
|
||||||
|
- Is awesome
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comment
|
||||||
|
|
||||||
|
The Essentials wiki is clearly in-progress right now. Take a look at the links to the right. We are actively working on this documentation, so please be patient with us. If you have any comments on or suggestions for the documentation, please file an issue here, with as much detail as you can provide: <https://github.com/PepperDash/Essentials/issues>
|
||||||
|
|
||||||
|
Thanks!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Collaboration
|
## Collaboration
|
||||||
|
|
||||||
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/technical-docs/Plugins.md)
|
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/Plugins.md)
|
||||||
|
|
||||||
### Open-source-collaborative workflow
|
### Open-source-collaborative workflow
|
||||||
|
|
||||||
@@ -43,7 +52,7 @@ The `main` branch always contain the latest stable version. The `development` br
|
|||||||
- Example: `feature/add-awesomeness` or `hotfix/really-big-oops`
|
- Example: `feature/add-awesomeness` or `hotfix/really-big-oops`
|
||||||
- When working on a new feature or bugfix, branch from the `development` branch. When working on a hotfix, branch from `main`.
|
- When working on a new feature or bugfix, branch from the `development` branch. When working on a hotfix, branch from `main`.
|
||||||
3. Make commits as necessary (often is better). And use concise, descriptive language, leveraging issue notation and/or [Closing Keywords](https://help.github.com/articles/closing-issues-using-keywords) to ensure any issues addressed by your work are referenced accordingly.
|
3. Make commits as necessary (often is better). And use concise, descriptive language, leveraging issue notation and/or [Closing Keywords](https://help.github.com/articles/closing-issues-using-keywords) to ensure any issues addressed by your work are referenced accordingly.
|
||||||
4. When the scope of the work for your branch is complete, make sure to update your branch in case further progress has been made since the repo was forked
|
4. When the scope of the work for your branch is complete, make sure to rebase your branch in case further progress has been made since the repo was forked
|
||||||
5. Create a Pull Request to pull your branch into the appropriate branch in the main repository.
|
5. Create a Pull Request to pull your branch into the appropriate branch in the main repository.
|
||||||
6. Your Pull Request will be reviewed by our team and evaluated for inclusion into the main repository.
|
6. Your Pull Request will be reviewed by our team and evaluated for inclusion into the main repository.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"runtimeOptions": {
|
|
||||||
"configProperties": {
|
|
||||||
"System.Globalization.Invariant": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>3.0.0-local</Version>
|
<Version>2.19.4-local</Version>
|
||||||
<InformationalVersion>$(Version)</InformationalVersion>
|
<InformationalVersion>$(Version)</InformationalVersion>
|
||||||
<Authors>PepperDash Technology</Authors>
|
<Authors>PepperDash Technology</Authors>
|
||||||
<Company>PepperDash Technology</Company>
|
<Company>PepperDash Technology</Company>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz" Condition="$(ProjectType) == 'Library' And '$(TargetFramework)' != '' And '$(TargetName)' != '' And '$(TargetDir)' != ''">
|
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz" Condition="$(ProjectType) == 'Library'">
|
||||||
<Pack>true</Pack>
|
<Pack>true</Pack>
|
||||||
<PackagePath>build;</PackagePath>
|
<PackagePath>build;</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" Condition="$(ProjectType) == 'Program' And '$(TargetFramework)' != '' And '$(TargetName)' != '' And '$(TargetDir)' != '' And ( '$(TargetFramework)' != 'net6.0' ) And ( '$(TargetFramework)' != 'net8.0' )">
|
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz" Condition="$(ProjectType) == 'Program'">
|
||||||
<Pack>true</Pack>
|
<Pack>true</Pack>
|
||||||
<PackagePath>build;</PackagePath>
|
<PackagePath>build;</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" Condition="$(ProjectType) == 'ProgramLibrary' And '$(TargetFramework)' != '' And '$(TargetName)' != '' And '$(TargetDir)' != ''">
|
<None Include="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz" Condition="$(ProjectType) == 'ProgramLibrary'">
|
||||||
<Pack>true</Pack>
|
<Pack>true</Pack>
|
||||||
<PackagePath>build;</PackagePath>
|
<PackagePath>build;</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ using Crestron.SimplSharp;
|
|||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the string event handler for line events on the gather
|
/// Defines the string event handler for line events on the gather
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -175,3 +175,4 @@ namespace PepperDash.Core;
|
|||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Timers;
|
using Crestron.SimplSharp;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable
|
/// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -20,10 +20,10 @@ public class CommunicationStreamDebugging
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timer to disable automatically if not manually disabled
|
/// Timer to disable automatically if not manually disabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Timer DebugExpiryPeriod;
|
private CTimer DebugExpiryPeriod;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current debug setting
|
/// Gets or sets the DebugSetting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public eStreamDebuggingSetting DebugSetting { get; private set; }
|
public eStreamDebuggingSetting DebugSetting { get; private set; }
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ public class CommunicationStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that receive stream debugging is enabled
|
/// Gets or sets the RxStreamDebuggingIsEnabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool RxStreamDebuggingIsEnabled{ get; private set; }
|
public bool RxStreamDebuggingIsEnabled{ get; private set; }
|
||||||
|
|
||||||
@@ -65,6 +65,9 @@ public class CommunicationStreamDebugging
|
|||||||
/// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues
|
/// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="setting"></param>
|
/// <param name="setting"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SetDebuggingWithDefaultTimeout method
|
||||||
|
/// </summary>
|
||||||
public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting)
|
public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting)
|
||||||
{
|
{
|
||||||
if (setting == eStreamDebuggingSetting.Off)
|
if (setting == eStreamDebuggingSetting.Off)
|
||||||
@@ -81,6 +84,9 @@ public class CommunicationStreamDebugging
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="setting"></param>
|
/// <param name="setting"></param>
|
||||||
/// <param name="minutes"></param>
|
/// <param name="minutes"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SetDebuggingWithSpecificTimeout method
|
||||||
|
/// </summary>
|
||||||
public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes)
|
public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes)
|
||||||
{
|
{
|
||||||
if (setting == eStreamDebuggingSetting.Off)
|
if (setting == eStreamDebuggingSetting.Off)
|
||||||
@@ -93,9 +99,7 @@ public class CommunicationStreamDebugging
|
|||||||
|
|
||||||
StopDebugTimer();
|
StopDebugTimer();
|
||||||
|
|
||||||
DebugExpiryPeriod = new Timer(_DebugTimeoutInMs) { AutoReset = false };
|
DebugExpiryPeriod = new CTimer((o) => DisableDebugging(), _DebugTimeoutInMs);
|
||||||
DebugExpiryPeriod.Elapsed += (s, e) => DisableDebugging();
|
|
||||||
DebugExpiryPeriod.Start();
|
|
||||||
|
|
||||||
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
|
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
|
||||||
RxStreamDebuggingIsEnabled = true;
|
RxStreamDebuggingIsEnabled = true;
|
||||||
@@ -137,6 +141,9 @@ public class CommunicationStreamDebugging
|
|||||||
/// The available settings for stream debugging
|
/// The available settings for stream debugging
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Flags]
|
[Flags]
|
||||||
|
/// <summary>
|
||||||
|
/// Enumeration of eStreamDebuggingSetting values
|
||||||
|
/// </summary>
|
||||||
public enum eStreamDebuggingSetting
|
public enum eStreamDebuggingSetting
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -157,3 +164,23 @@ public enum eStreamDebuggingSetting
|
|||||||
Both = Rx | Tx
|
Both = Rx | Tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The available settings for stream debugging response types
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum eStreamDebuggingDataTypeSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Debug data in byte format
|
||||||
|
/// </summary>
|
||||||
|
Bytes = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Debug data in text format
|
||||||
|
/// </summary>
|
||||||
|
Text = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// Debug data in both byte and text formats
|
||||||
|
/// </summary>
|
||||||
|
Both = Bytes | Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using JsonConverter = NewtonsoftJson::Newtonsoft.Json.JsonConverterAttribute;
|
using Newtonsoft.Json;
|
||||||
using JsonIgnore = NewtonsoftJson::Newtonsoft.Json.JsonIgnoreAttribute;
|
using Newtonsoft.Json.Converters;
|
||||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
|
||||||
using NullValueHandling = NewtonsoftJson::Newtonsoft.Json.NullValueHandling;
|
|
||||||
using StringEnumConverter = NewtonsoftJson::Newtonsoft.Json.Converters.StringEnumConverter;
|
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Config properties that indicate how to communicate with a device for control
|
/// Represents a ControlPropertiesConfig
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ControlPropertiesConfig
|
public class ControlPropertiesConfig
|
||||||
{
|
{
|
||||||
@@ -95,3 +90,4 @@ public class ControlPropertiesConfig
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,8 @@ using Crestron.SimplSharp;
|
|||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for notifying of socket status changes
|
/// Delegate for notifying of socket status changes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -35,7 +35,7 @@ public class GenericSocketStatusChageEventArgs : EventArgs
|
|||||||
public ISocketStatus Client { get; private set; }
|
public ISocketStatus Client { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client"></param>
|
/// <param name="client"></param>
|
||||||
public GenericSocketStatusChageEventArgs(ISocketStatus client)
|
public GenericSocketStatusChageEventArgs(ISocketStatus client)
|
||||||
@@ -65,7 +65,7 @@ public class GenericTcpServerStateChangedEventArgs : EventArgs
|
|||||||
public ServerState State { get; private set; }
|
public ServerState State { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state"></param>
|
/// <param name="state"></param>
|
||||||
public GenericTcpServerStateChangedEventArgs(ServerState state)
|
public GenericTcpServerStateChangedEventArgs(ServerState state)
|
||||||
@@ -91,22 +91,20 @@ public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object sock
|
|||||||
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Socket
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object Socket { get; private set; }
|
public object Socket { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the index of the client from which the status change was received
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint ReceivedFromClientIndex { get; private set; }
|
public uint ReceivedFromClientIndex { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ClientStatus
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SocketStatus ClientStatus { get; set; }
|
public SocketStatus ClientStatus { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="socket"></param>
|
/// <param name="socket"></param>
|
||||||
/// <param name="clientStatus"></param>
|
/// <param name="clientStatus"></param>
|
||||||
@@ -117,7 +115,7 @@ public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="socket"></param>
|
/// <param name="socket"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
@@ -140,12 +138,12 @@ public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
|||||||
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the index of the client from which the text was received
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint ReceivedFromClientIndex { get; private set; }
|
public uint ReceivedFromClientIndex { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the index of the client from which the text was received as a ushort
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort ReceivedFromClientIndexShort
|
public ushort ReceivedFromClientIndexShort
|
||||||
{
|
{
|
||||||
@@ -161,7 +159,7 @@ public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
|||||||
public string Text { get; private set; }
|
public string Text { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
public GenericTcpServerCommMethodReceiveTextArgs(string text)
|
public GenericTcpServerCommMethodReceiveTextArgs(string text)
|
||||||
@@ -170,7 +168,7 @@ public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
@@ -191,19 +189,18 @@ public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
|||||||
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets IsReady
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsReady;
|
public bool IsReady;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="isReady"></param>
|
/// <param name="isReady"></param>
|
||||||
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
|
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
|
||||||
{
|
{
|
||||||
IsReady = isReady;
|
IsReady = isReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// S+ Constructor
|
/// S+ Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -216,12 +213,11 @@ public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
|||||||
public class GenericUdpConnectedEventArgs : EventArgs
|
public class GenericUdpConnectedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the UConnected
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort UConnected;
|
public ushort UConnected;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Connected status
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Connected;
|
public bool Connected;
|
||||||
|
|
||||||
@@ -231,7 +227,7 @@ public class GenericUdpConnectedEventArgs : EventArgs
|
|||||||
public GenericUdpConnectedEventArgs() { }
|
public GenericUdpConnectedEventArgs() { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uconnected"></param>
|
/// <param name="uconnected"></param>
|
||||||
public GenericUdpConnectedEventArgs(ushort uconnected)
|
public GenericUdpConnectedEventArgs(ushort uconnected)
|
||||||
@@ -240,7 +236,7 @@ public class GenericUdpConnectedEventArgs : EventArgs
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="connected"></param>
|
/// <param name="connected"></param>
|
||||||
public GenericUdpConnectedEventArgs(bool connected)
|
public GenericUdpConnectedEventArgs(bool connected)
|
||||||
@@ -250,3 +246,6 @@ public class GenericUdpConnectedEventArgs : EventArgs
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,13 +3,12 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Timers;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class to handle secure TCP/IP communications with a server
|
/// A class to handle secure TCP/IP communications with a server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -80,7 +79,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Port on server
|
/// Gets or sets the Port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
@@ -150,7 +149,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
|
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// bool to track if auto reconnect should be set on the socket
|
/// Gets or sets the AutoReconnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutoReconnect { get; set; }
|
public bool AutoReconnect { get; set; }
|
||||||
|
|
||||||
@@ -182,14 +181,14 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
// private Timer for auto reconnect
|
// private Timer for auto reconnect
|
||||||
private Timer RetryTimer;
|
private CTimer RetryTimer;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region GenericSecureTcpIpClient properties
|
#region GenericSecureTcpIpClient properties
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bool to show whether the server requires a preshared key. This is used in the DynamicTCPServer class
|
/// Gets or sets the SharedKeyRequired
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SharedKeyRequired { get; set; }
|
public bool SharedKeyRequired { get; set; }
|
||||||
|
|
||||||
@@ -208,7 +207,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
/// Gets or sets the SharedKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SharedKey { get; set; }
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
@@ -223,7 +222,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
bool IsTryingToConnect;
|
bool IsTryingToConnect;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bool showing if socket is ready for communication after shared key exchange
|
/// Gets or sets the IsReadyForCommunication
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsReadyForCommunication { get; set; }
|
public bool IsReadyForCommunication { get; set; }
|
||||||
|
|
||||||
@@ -265,12 +264,12 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
|
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
|
||||||
|
|
||||||
Timer HeartbeatSendTimer;
|
CTimer HeartbeatSendTimer;
|
||||||
Timer HeartbeatAckTimer;
|
CTimer HeartbeatAckTimer;
|
||||||
|
|
||||||
// Used to force disconnection on a dead connect attempt
|
// Used to force disconnection on a dead connect attempt
|
||||||
Timer ConnectFailTimer;
|
CTimer ConnectFailTimer;
|
||||||
Timer WaitForSharedKey;
|
CTimer WaitForSharedKey;
|
||||||
private int ConnectionCount;
|
private int ConnectionCount;
|
||||||
|
|
||||||
bool ProgramIsStopping;
|
bool ProgramIsStopping;
|
||||||
@@ -278,7 +277,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue lock
|
/// Queue lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly object _dequeueLock = new();
|
CCriticalSection DequeueLock = new CCriticalSection();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
||||||
@@ -343,7 +342,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Just to help S+ set the key
|
/// Initialize method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Initialize(string key)
|
public void Initialize(string key)
|
||||||
{
|
{
|
||||||
@@ -358,7 +357,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
{
|
{
|
||||||
if (config == null)
|
if (config == null)
|
||||||
{
|
{
|
||||||
this.LogWarning( "Could not initialize client with key: {0}", Key);
|
Debug.Console(0, this, "Could not initialize client with key: {0}", Key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
@@ -398,7 +397,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError("Exception initializing client with key: {0}\rException: {1}", Key, ex);
|
Debug.Console(0, this, "Exception initializing client with key: {0}\rException: {1}", Key, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +410,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
{
|
{
|
||||||
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
||||||
{
|
{
|
||||||
this.LogInformation("Program stopping. Closing _client connection");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing _client connection");
|
||||||
ProgramIsStopping = true;
|
ProgramIsStopping = true;
|
||||||
Disconnect();
|
Disconnect();
|
||||||
}
|
}
|
||||||
@@ -422,6 +421,9 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
/// Deactivate the client
|
/// Deactivate the client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// Deactivate method
|
||||||
|
/// </summary>
|
||||||
public override bool Deactivate()
|
public override bool Deactivate()
|
||||||
{
|
{
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
@@ -433,22 +435,22 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
|
/// Connect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Connect()
|
public void Connect()
|
||||||
{
|
{
|
||||||
ConnectionCount++;
|
ConnectionCount++;
|
||||||
this.LogVerbose("Attempting connect Count:{0}", ConnectionCount);
|
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
|
||||||
|
|
||||||
|
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
{
|
{
|
||||||
this.LogInformation("Already connected. Ignoring.");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (IsTryingToConnect)
|
if (IsTryingToConnect)
|
||||||
{
|
{
|
||||||
this.LogInformation("Already trying to connect. Ignoring.");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
@@ -461,17 +463,17 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
if (string.IsNullOrEmpty(Hostname))
|
||||||
{
|
{
|
||||||
this.LogWarning("DynamicTcpClient: No address set");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
this.LogWarning("DynamicTcpClient: Invalid port");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
{
|
{
|
||||||
this.LogWarning("DynamicTcpClient: No Shared Key set");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,10 +494,9 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
|
|
||||||
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||||
|
|
||||||
ConnectFailTimer = new Timer(30000) { AutoReset = false };
|
ConnectFailTimer = new CTimer(o =>
|
||||||
ConnectFailTimer.Elapsed += (s, e) =>
|
|
||||||
{
|
{
|
||||||
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||||
if (IsTryingToConnect)
|
if (IsTryingToConnect)
|
||||||
{
|
{
|
||||||
IsTryingToConnect = false;
|
IsTryingToConnect = false;
|
||||||
@@ -507,13 +508,12 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
//SecureClient.DisconnectFromServer();
|
//SecureClient.DisconnectFromServer();
|
||||||
//CheckClosedAndTryReconnect();
|
//CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
};
|
}, 30000);
|
||||||
ConnectFailTimer.Start();
|
|
||||||
|
|
||||||
this.LogVerbose("Making Connection Count:{0}", ConnectionCount);
|
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
|
||||||
_client.ConnectToServerAsync(o =>
|
_client.ConnectToServerAsync(o =>
|
||||||
{
|
{
|
||||||
this.LogVerbose("ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||||
|
|
||||||
if (ConnectFailTimer != null)
|
if (ConnectFailTimer != null)
|
||||||
{
|
{
|
||||||
@@ -523,22 +523,22 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
|
|
||||||
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
this.LogVerbose("_client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
Debug.Console(2, this, "_client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||||
o.ReceiveDataAsync(Receive);
|
o.ReceiveDataAsync(Receive);
|
||||||
|
|
||||||
if (SharedKeyRequired)
|
if (SharedKeyRequired)
|
||||||
{
|
{
|
||||||
WaitingForSharedKeyResponse = true;
|
WaitingForSharedKeyResponse = true;
|
||||||
WaitForSharedKey = new Timer(15000) { AutoReset = false };
|
WaitForSharedKey = new CTimer(timer =>
|
||||||
WaitForSharedKey.Elapsed += (s, e) =>
|
|
||||||
{
|
{
|
||||||
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||||
|
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
||||||
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
||||||
o.DisconnectFromServer();
|
o.DisconnectFromServer();
|
||||||
//CheckClosedAndTryReconnect();
|
//CheckClosedAndTryReconnect();
|
||||||
//OnClientReadyForcommunications(false); // Should send false event
|
//OnClientReadyForcommunications(false); // Should send false event
|
||||||
};
|
}, 15000);
|
||||||
WaitForSharedKey.Start();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -552,21 +552,21 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogWarning("Connect attempt failed {0}", o.ClientStatus);
|
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
||||||
CheckClosedAndTryReconnect();
|
CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError("_client connection exception: {0}", ex.Message);
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "_client connection exception: {0}", ex.Message);
|
||||||
IsTryingToConnect = false;
|
IsTryingToConnect = false;
|
||||||
CheckClosedAndTryReconnect();
|
CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Disconnect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Disconnect()
|
public void Disconnect()
|
||||||
{
|
{
|
||||||
@@ -589,20 +589,20 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Does the actual disconnect business
|
/// DisconnectClient method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DisconnectClient()
|
public void DisconnectClient()
|
||||||
{
|
{
|
||||||
if (_client == null) return;
|
if (_client == null) return;
|
||||||
|
|
||||||
this.LogInformation("Disconnecting client");
|
Debug.Console(1, this, "Disconnecting client");
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
_client.DisconnectFromServer();
|
_client.DisconnectFromServer();
|
||||||
|
|
||||||
// close up client. ALWAYS use this when disconnecting.
|
// close up client. ALWAYS use this when disconnecting.
|
||||||
IsTryingToConnect = false;
|
IsTryingToConnect = false;
|
||||||
|
|
||||||
this.LogVerbose("Disconnecting _client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
Debug.Console(2, this, "Disconnecting _client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||||
_client.SocketStatusChange -= Client_SocketStatusChange;
|
_client.SocketStatusChange -= Client_SocketStatusChange;
|
||||||
_client.Dispose();
|
_client.Dispose();
|
||||||
_client = null;
|
_client = null;
|
||||||
@@ -623,14 +623,14 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
{
|
{
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Cleaning up remotely closed/failed connection.");
|
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
|
||||||
Disconnect();
|
Disconnect();
|
||||||
}
|
}
|
||||||
if (!DisconnectCalledByUser && AutoReconnect)
|
if (!DisconnectCalledByUser && AutoReconnect)
|
||||||
{
|
{
|
||||||
var halfInterval = AutoReconnectIntervalMs / 2;
|
var halfInterval = AutoReconnectIntervalMs / 2;
|
||||||
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
||||||
this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime);
|
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
|
||||||
if (RetryTimer != null)
|
if (RetryTimer != null)
|
||||||
{
|
{
|
||||||
RetryTimer.Stop();
|
RetryTimer.Stop();
|
||||||
@@ -638,9 +638,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
if (AutoReconnectTriggered != null)
|
if (AutoReconnectTriggered != null)
|
||||||
AutoReconnectTriggered(this, new EventArgs());
|
AutoReconnectTriggered(this, new EventArgs());
|
||||||
RetryTimer = new Timer(rndTime) { AutoReset = false };
|
RetryTimer = new CTimer(o => Connect(), rndTime);
|
||||||
RetryTimer.Elapsed += (s, e) => Connect();
|
|
||||||
RetryTimer.Start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,13 +657,13 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
{
|
{
|
||||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||||
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||||
this.LogVerbose("_client Received:\r--------\r{0}\r--------", str);
|
Debug.Console(2, this, "_client Received:\r--------\r{0}\r--------", str);
|
||||||
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
||||||
{
|
{
|
||||||
|
|
||||||
if (SharedKeyRequired && str == "SharedKey:")
|
if (SharedKeyRequired && str == "SharedKey:")
|
||||||
{
|
{
|
||||||
this.LogVerbose("Server asking for shared key, sending");
|
Debug.Console(2, this, "Server asking for shared key, sending");
|
||||||
SendText(SharedKey + "\n");
|
SendText(SharedKey + "\n");
|
||||||
}
|
}
|
||||||
else if (SharedKeyRequired && str == "Shared Key Match")
|
else if (SharedKeyRequired && str == "Shared Key Match")
|
||||||
@@ -673,7 +671,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
StopWaitForSharedKeyTimer();
|
StopWaitForSharedKeyTimer();
|
||||||
|
|
||||||
|
|
||||||
this.LogVerbose("Shared key confirmed. Ready for communication");
|
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
|
||||||
OnClientReadyForcommunications(true); // Successful key exchange
|
OnClientReadyForcommunications(true); // Successful key exchange
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -693,7 +691,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError("Error receiving data: {1}. Error: {0}", str, ex.Message);
|
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
||||||
}
|
}
|
||||||
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
client.ReceiveDataAsync(Receive);
|
client.ReceiveDataAsync(Receive);
|
||||||
@@ -701,8 +699,9 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
{
|
{
|
||||||
if (System.Threading.Monitor.TryEnter(_dequeueLock))
|
var gotLock = DequeueLock.TryEnter();
|
||||||
System.Threading.Tasks.Task.Run(() => DequeueEvent());
|
if (gotLock)
|
||||||
|
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
||||||
@@ -712,7 +711,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
|
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
||||||
/// It will dequeue items as they are enqueued automatically.
|
/// It will dequeue items as they are enqueued automatically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void DequeueEvent()
|
void DequeueEvent()
|
||||||
@@ -732,10 +731,13 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
this.LogError(e, "DequeueEvent error: {0}", e.Message);
|
this.LogException(e, "DequeueEvent error");
|
||||||
|
}
|
||||||
|
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
|
||||||
|
if (DequeueLock != null)
|
||||||
|
{
|
||||||
|
DequeueLock.Leave();
|
||||||
}
|
}
|
||||||
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
|
|
||||||
System.Threading.Monitor.Exit(_dequeueLock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeartbeatStart()
|
void HeartbeatStart()
|
||||||
@@ -746,15 +748,11 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
if (HeartbeatSendTimer == null)
|
if (HeartbeatSendTimer == null)
|
||||||
{
|
{
|
||||||
|
|
||||||
HeartbeatSendTimer = new Timer(HeartbeatInterval) { AutoReset = true };
|
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
||||||
HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null);
|
|
||||||
HeartbeatSendTimer.Start();
|
|
||||||
}
|
}
|
||||||
if (HeartbeatAckTimer == null)
|
if (HeartbeatAckTimer == null)
|
||||||
{
|
{
|
||||||
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
|
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
|
||||||
HeartbeatAckTimer.Start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -764,13 +762,13 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
|
|
||||||
if (HeartbeatSendTimer != null)
|
if (HeartbeatSendTimer != null)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Stoping Heartbeat Send");
|
Debug.Console(2, this, "Stoping Heartbeat Send");
|
||||||
HeartbeatSendTimer.Stop();
|
HeartbeatSendTimer.Stop();
|
||||||
HeartbeatSendTimer = null;
|
HeartbeatSendTimer = null;
|
||||||
}
|
}
|
||||||
if (HeartbeatAckTimer != null)
|
if (HeartbeatAckTimer != null)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Stoping Heartbeat Ack");
|
Debug.Console(2, this, "Stoping Heartbeat Ack");
|
||||||
HeartbeatAckTimer.Stop();
|
HeartbeatAckTimer.Stop();
|
||||||
HeartbeatAckTimer = null;
|
HeartbeatAckTimer = null;
|
||||||
}
|
}
|
||||||
@@ -779,7 +777,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
void SendHeartbeat(object notused)
|
void SendHeartbeat(object notused)
|
||||||
{
|
{
|
||||||
this.SendText(HeartbeatString);
|
this.SendText(HeartbeatString);
|
||||||
this.LogVerbose("Sending Heartbeat");
|
Debug.Console(2, this, "Sending Heartbeat");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,17 +796,13 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
{
|
{
|
||||||
if (HeartbeatAckTimer != null)
|
if (HeartbeatAckTimer != null)
|
||||||
{
|
{
|
||||||
HeartbeatAckTimer.Stop();
|
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
||||||
HeartbeatAckTimer.Interval = HeartbeatInterval * 2;
|
|
||||||
HeartbeatAckTimer.Start();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
|
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
|
||||||
HeartbeatAckTimer.Start();
|
|
||||||
}
|
}
|
||||||
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
|
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||||
return remainingText;
|
return remainingText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -816,7 +810,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError(ex, "Error checking heartbeat: {0}", ex.Message);
|
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
return received;
|
return received;
|
||||||
}
|
}
|
||||||
@@ -830,7 +824,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
|
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
{
|
{
|
||||||
this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||||
SendText("Heartbeat not received by server, closing connection");
|
SendText("Heartbeat not received by server, closing connection");
|
||||||
CheckClosedAndTryReconnect();
|
CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
@@ -838,7 +832,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError(ex, "Heartbeat timeout Error on _client: {0}, {1}", Key, ex.Message);
|
ErrorLog.Error("Heartbeat timeout Error on _client: {0}, {1}", Key, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,7 +849,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// General send method
|
/// SendText method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendText(string text)
|
public void SendText(string text)
|
||||||
{
|
{
|
||||||
@@ -871,20 +865,20 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
||||||
if (n <= 0)
|
if (n <= 0)
|
||||||
{
|
{
|
||||||
this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key);
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError(ex, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// SendBytes method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendBytes(byte[] bytes)
|
public void SendBytes(byte[] bytes)
|
||||||
{
|
{
|
||||||
@@ -897,7 +891,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError(ex, "Error sending bytes. Error: {0}", ex.Message);
|
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -916,7 +910,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.LogVerbose("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||||
|
|
||||||
OnConnectionChange();
|
OnConnectionChange();
|
||||||
// The client could be null or disposed by this time...
|
// The client could be null or disposed by this time...
|
||||||
@@ -929,7 +923,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError(ex, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -960,3 +954,5 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,15 +15,12 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Timers;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generic secure TCP/IP client for server
|
/// Generic secure TCP/IP client for server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -83,7 +80,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
public string Hostname { get; set; }
|
public string Hostname { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The port number on which the server is listening.
|
/// Gets or sets the Port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
@@ -116,7 +113,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SharedKey is sent for verification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
/// Gets or sets the SharedKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SharedKey { get; set; }
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
@@ -126,7 +123,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
private bool WaitingForSharedKeyResponse { get; set; }
|
private bool WaitingForSharedKeyResponse { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defaults to 2000
|
/// Gets or sets the BufferSize
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int BufferSize { get; set; }
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
@@ -224,7 +221,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// private Timer for auto reconnect
|
/// private Timer for auto reconnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
System.Timers.Timer RetryTimer;
|
CTimer RetryTimer;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -256,13 +253,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
|
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
|
||||||
|
|
||||||
System.Timers.Timer HeartbeatSendTimer;
|
CTimer HeartbeatSendTimer;
|
||||||
System.Timers.Timer HeartbeatAckTimer;
|
CTimer HeartbeatAckTimer;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to force disconnection on a dead connect attempt
|
/// Used to force disconnection on a dead connect attempt
|
||||||
/// </summary>
|
/// </summary>
|
||||||
System.Timers.Timer ConnectFailTimer;
|
CTimer ConnectFailTimer;
|
||||||
System.Timers.Timer WaitForSharedKey;
|
CTimer WaitForSharedKey;
|
||||||
private int ConnectionCount;
|
private int ConnectionCount;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal secure client
|
/// Internal secure client
|
||||||
@@ -274,7 +271,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue lock
|
/// Queue lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly object _dequeueLock = new();
|
CCriticalSection DequeueLock = new CCriticalSection();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
||||||
@@ -339,9 +336,8 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
#region Methods
|
#region Methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the client's key property, which is used to identify this client instance.
|
/// Initialize method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The unique key that identifies this client instance.</param>
|
|
||||||
public void Initialize(string key)
|
public void Initialize(string key)
|
||||||
{
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
@@ -350,7 +346,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client.
|
/// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clientConfigObject">The configuration object containing the client's settings.</param>
|
/// <param name="clientConfigObject"></param>
|
||||||
public void Initialize(TcpClientConfigObject clientConfigObject)
|
public void Initialize(TcpClientConfigObject clientConfigObject)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -391,7 +387,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
{
|
{
|
||||||
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
||||||
{
|
{
|
||||||
this.LogInformation("Program stopping. Closing Client connection");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection");
|
||||||
ProgramIsStopping = true;
|
ProgramIsStopping = true;
|
||||||
Disconnect();
|
Disconnect();
|
||||||
}
|
}
|
||||||
@@ -399,22 +395,22 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
|
/// Connect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Connect()
|
public void Connect()
|
||||||
{
|
{
|
||||||
ConnectionCount++;
|
ConnectionCount++;
|
||||||
this.LogVerbose("Attempting connect Count:{0}", ConnectionCount);
|
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
|
||||||
|
|
||||||
|
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
{
|
{
|
||||||
this.LogInformation("Already connected. Ignoring.");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (IsTryingToConnect)
|
if (IsTryingToConnect)
|
||||||
{
|
{
|
||||||
this.LogInformation("Already trying to connect. Ignoring.");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
@@ -427,17 +423,17 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
if (string.IsNullOrEmpty(Hostname))
|
||||||
{
|
{
|
||||||
this.LogWarning("DynamicTcpClient: No address set");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
this.LogWarning("DynamicTcpClient: Invalid port");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
{
|
{
|
||||||
this.LogWarning("DynamicTcpClient: No Shared Key set");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,10 +454,9 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
|
|
||||||
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||||
|
|
||||||
ConnectFailTimer = new System.Timers.Timer(30000) { AutoReset = false };
|
ConnectFailTimer = new CTimer(o =>
|
||||||
ConnectFailTimer.Elapsed += (s, e) =>
|
|
||||||
{
|
{
|
||||||
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||||
if (IsTryingToConnect)
|
if (IsTryingToConnect)
|
||||||
{
|
{
|
||||||
IsTryingToConnect = false;
|
IsTryingToConnect = false;
|
||||||
@@ -473,13 +468,12 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
//SecureClient.DisconnectFromServer();
|
//SecureClient.DisconnectFromServer();
|
||||||
//CheckClosedAndTryReconnect();
|
//CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
};
|
}, 30000);
|
||||||
ConnectFailTimer.Start();
|
|
||||||
|
|
||||||
this.LogVerbose("Making Connection Count:{0}", ConnectionCount);
|
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
|
||||||
Client.ConnectToServerAsync(o =>
|
Client.ConnectToServerAsync(o =>
|
||||||
{
|
{
|
||||||
this.LogVerbose("ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||||
|
|
||||||
if (ConnectFailTimer != null)
|
if (ConnectFailTimer != null)
|
||||||
{
|
{
|
||||||
@@ -489,22 +483,22 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
|
|
||||||
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||||
o.ReceiveDataAsync(Receive);
|
o.ReceiveDataAsync(Receive);
|
||||||
|
|
||||||
if (SharedKeyRequired)
|
if (SharedKeyRequired)
|
||||||
{
|
{
|
||||||
WaitingForSharedKeyResponse = true;
|
WaitingForSharedKeyResponse = true;
|
||||||
WaitForSharedKey = new System.Timers.Timer(15000) { AutoReset = false };
|
WaitForSharedKey = new CTimer(timer =>
|
||||||
WaitForSharedKey.Elapsed += (s, e) =>
|
|
||||||
{
|
{
|
||||||
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||||
|
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
||||||
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
||||||
o.DisconnectFromServer();
|
o.DisconnectFromServer();
|
||||||
//CheckClosedAndTryReconnect();
|
//CheckClosedAndTryReconnect();
|
||||||
//OnClientReadyForcommunications(false); // Should send false event
|
//OnClientReadyForcommunications(false); // Should send false event
|
||||||
};
|
}, 15000);
|
||||||
WaitForSharedKey.Start();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -518,21 +512,21 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogWarning("Connect attempt failed {0}", o.ClientStatus);
|
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
||||||
CheckClosedAndTryReconnect();
|
CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError("Client connection exception: {0}", ex.Message);
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message);
|
||||||
IsTryingToConnect = false;
|
IsTryingToConnect = false;
|
||||||
CheckClosedAndTryReconnect();
|
CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Disconnect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Disconnect()
|
public void Disconnect()
|
||||||
{
|
{
|
||||||
@@ -562,7 +556,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
if (Client != null)
|
if (Client != null)
|
||||||
{
|
{
|
||||||
//SecureClient.DisconnectFromServer();
|
//SecureClient.DisconnectFromServer();
|
||||||
this.LogVerbose("Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||||
Client.SocketStatusChange -= Client_SocketStatusChange;
|
Client.SocketStatusChange -= Client_SocketStatusChange;
|
||||||
Client.Dispose();
|
Client.Dispose();
|
||||||
Client = null;
|
Client = null;
|
||||||
@@ -584,14 +578,14 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
{
|
{
|
||||||
if (Client != null)
|
if (Client != null)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Cleaning up remotely closed/failed connection.");
|
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
|
||||||
Cleanup();
|
Cleanup();
|
||||||
}
|
}
|
||||||
if (!DisconnectCalledByUser && AutoReconnect)
|
if (!DisconnectCalledByUser && AutoReconnect)
|
||||||
{
|
{
|
||||||
var halfInterval = AutoReconnectIntervalMs / 2;
|
var halfInterval = AutoReconnectIntervalMs / 2;
|
||||||
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
||||||
this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime);
|
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
|
||||||
if (RetryTimer != null)
|
if (RetryTimer != null)
|
||||||
{
|
{
|
||||||
RetryTimer.Stop();
|
RetryTimer.Stop();
|
||||||
@@ -599,9 +593,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
if(AutoReconnectTriggered != null)
|
if(AutoReconnectTriggered != null)
|
||||||
AutoReconnectTriggered(this, new EventArgs());
|
AutoReconnectTriggered(this, new EventArgs());
|
||||||
RetryTimer = new System.Timers.Timer(rndTime) { AutoReset = false };
|
RetryTimer = new CTimer(o => Connect(), rndTime);
|
||||||
RetryTimer.Elapsed += (s, e) => Connect();
|
|
||||||
RetryTimer.Start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,13 +612,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
{
|
{
|
||||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||||
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||||
this.LogVerbose("Client Received:\r--------\r{0}\r--------", str);
|
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
|
||||||
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
||||||
{
|
{
|
||||||
|
|
||||||
if (SharedKeyRequired && str == "SharedKey:")
|
if (SharedKeyRequired && str == "SharedKey:")
|
||||||
{
|
{
|
||||||
this.LogVerbose("Server asking for shared key, sending");
|
Debug.Console(2, this, "Server asking for shared key, sending");
|
||||||
SendText(SharedKey + "\n");
|
SendText(SharedKey + "\n");
|
||||||
}
|
}
|
||||||
else if (SharedKeyRequired && str == "Shared Key Match")
|
else if (SharedKeyRequired && str == "Shared Key Match")
|
||||||
@@ -634,7 +626,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
StopWaitForSharedKeyTimer();
|
StopWaitForSharedKeyTimer();
|
||||||
|
|
||||||
|
|
||||||
this.LogVerbose("Shared key confirmed. Ready for communication");
|
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
|
||||||
OnClientReadyForcommunications(true); // Successful key exchange
|
OnClientReadyForcommunications(true); // Successful key exchange
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -654,7 +646,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError("Error receiving data: {1}. Error: {0}", ex.Message, str);
|
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
||||||
}
|
}
|
||||||
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
client.ReceiveDataAsync(Receive);
|
client.ReceiveDataAsync(Receive);
|
||||||
@@ -662,8 +654,9 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
{
|
{
|
||||||
if (Monitor.TryEnter(_dequeueLock))
|
var gotLock = DequeueLock.TryEnter();
|
||||||
Task.Run(() => DequeueEvent());
|
if (gotLock)
|
||||||
|
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
||||||
@@ -673,7 +666,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
|
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
||||||
/// It will dequeue items as they are enqueued automatically.
|
/// It will dequeue items as they are enqueued automatically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void DequeueEvent()
|
void DequeueEvent()
|
||||||
@@ -693,29 +686,28 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
this.LogError("DequeueEvent error: {0}", e.Message, e);
|
this.LogException(e, "DequeueEvent error");
|
||||||
|
}
|
||||||
|
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
|
||||||
|
if (DequeueLock != null)
|
||||||
|
{
|
||||||
|
DequeueLock.Leave();
|
||||||
}
|
}
|
||||||
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
|
|
||||||
Monitor.Exit(_dequeueLock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeartbeatStart()
|
void HeartbeatStart()
|
||||||
{
|
{
|
||||||
if (HeartbeatEnabled)
|
if (HeartbeatEnabled)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Starting Heartbeat");
|
Debug.Console(2, this, "Starting Heartbeat");
|
||||||
if (HeartbeatSendTimer == null)
|
if (HeartbeatSendTimer == null)
|
||||||
{
|
{
|
||||||
|
|
||||||
HeartbeatSendTimer = new System.Timers.Timer(HeartbeatInterval) { AutoReset = true };
|
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
||||||
HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null);
|
|
||||||
HeartbeatSendTimer.Start();
|
|
||||||
}
|
}
|
||||||
if (HeartbeatAckTimer == null)
|
if (HeartbeatAckTimer == null)
|
||||||
{
|
{
|
||||||
HeartbeatAckTimer = new System.Timers.Timer(HeartbeatInterval * 2) { AutoReset = true };
|
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
|
||||||
HeartbeatAckTimer.Start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,13 +717,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
|
|
||||||
if (HeartbeatSendTimer != null)
|
if (HeartbeatSendTimer != null)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Stopping Heartbeat Send");
|
Debug.Console(2, this, "Stoping Heartbeat Send");
|
||||||
HeartbeatSendTimer.Stop();
|
HeartbeatSendTimer.Stop();
|
||||||
HeartbeatSendTimer = null;
|
HeartbeatSendTimer = null;
|
||||||
}
|
}
|
||||||
if (HeartbeatAckTimer != null)
|
if (HeartbeatAckTimer != null)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Stopping Heartbeat Ack");
|
Debug.Console(2, this, "Stoping Heartbeat Ack");
|
||||||
HeartbeatAckTimer.Stop();
|
HeartbeatAckTimer.Stop();
|
||||||
HeartbeatAckTimer = null;
|
HeartbeatAckTimer = null;
|
||||||
}
|
}
|
||||||
@@ -740,7 +732,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
void SendHeartbeat(object notused)
|
void SendHeartbeat(object notused)
|
||||||
{
|
{
|
||||||
this.SendText(HeartbeatString);
|
this.SendText(HeartbeatString);
|
||||||
this.LogVerbose("Sending Heartbeat");
|
Debug.Console(2, this, "Sending Heartbeat");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,17 +751,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
{
|
{
|
||||||
if (HeartbeatAckTimer != null)
|
if (HeartbeatAckTimer != null)
|
||||||
{
|
{
|
||||||
HeartbeatAckTimer.Stop();
|
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
||||||
HeartbeatAckTimer.Interval = HeartbeatInterval * 2;
|
|
||||||
HeartbeatAckTimer.Start();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HeartbeatAckTimer = new System.Timers.Timer(HeartbeatInterval * 2) { AutoReset = true };
|
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
|
||||||
HeartbeatAckTimer.Start();
|
|
||||||
}
|
}
|
||||||
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
|
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||||
return remainingText;
|
return remainingText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -777,7 +765,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError("Error checking heartbeat: {0}", ex.Message, ex);
|
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
return received;
|
return received;
|
||||||
}
|
}
|
||||||
@@ -791,7 +779,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
|
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
{
|
{
|
||||||
this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||||
SendText("Heartbeat not received by server, closing connection");
|
SendText("Heartbeat not received by server, closing connection");
|
||||||
CheckClosedAndTryReconnect();
|
CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
@@ -799,7 +787,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
|
ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,7 +804,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// General send method
|
/// SendText method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendText(string text)
|
public void SendText(string text)
|
||||||
{
|
{
|
||||||
@@ -832,20 +820,20 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
||||||
if (n <= 0)
|
if (n <= 0)
|
||||||
{
|
{
|
||||||
this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key);
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError("Error sending text: {1}. Error: {0}", text, ex.Message);
|
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// SendBytes method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendBytes(byte[] bytes)
|
public void SendBytes(byte[] bytes)
|
||||||
{
|
{
|
||||||
@@ -858,7 +846,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError("Error sending bytes. Error: {0}", ex.Message, ex);
|
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -877,7 +865,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.LogDebug("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||||
|
|
||||||
OnConnectionChange();
|
OnConnectionChange();
|
||||||
// The client could be null or disposed by this time...
|
// The client could be null or disposed by this time...
|
||||||
@@ -890,7 +878,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError("Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -917,3 +905,5 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,14 +1,24 @@
|
|||||||
using System;
|
/*PepperDash Technology Corp.
|
||||||
|
JAG
|
||||||
|
Copyright: 2017
|
||||||
|
------------------------------------
|
||||||
|
***Notice of Ownership and Copyright***
|
||||||
|
The material in which this notice appears is the property of PepperDash Technology Corporation,
|
||||||
|
which claims copyright under the laws of the United States of America in the entire body of material
|
||||||
|
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
|
||||||
|
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
|
||||||
|
PepperDash Technology Corporation reserves all rights under applicable laws.
|
||||||
|
------------------------------------ */
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Timers;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generic secure TCP/IP server
|
/// Generic secure TCP/IP server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -48,7 +58,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; }
|
public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Delegate for ServerHasChokedCallbackDelegate
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public delegate void ServerHasChokedCallbackDelegate();
|
public delegate void ServerHasChokedCallbackDelegate();
|
||||||
|
|
||||||
@@ -59,17 +69,12 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server listen lock
|
/// Server listen lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly object _serverLock = new();
|
CCriticalSection ServerCCSection = new CCriticalSection();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue lock
|
/// Queue lock
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly object _dequeueLock = new();
|
CCriticalSection DequeueLock = new CCriticalSection();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Broadcast lock
|
|
||||||
/// </summary>
|
|
||||||
private readonly object _broadcastLock = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
||||||
@@ -91,7 +96,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timer to operate the bandaid monitor client in a loop.
|
/// Timer to operate the bandaid monitor client in a loop.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Timer MonitorClientTimer;
|
CTimer MonitorClientTimer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
@@ -99,7 +104,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
int MonitorClientFailureCount;
|
int MonitorClientFailureCount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3 by default
|
/// Gets or sets the MonitorClientMaxFailureCount
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MonitorClientMaxFailureCount { get; set; }
|
public int MonitorClientMaxFailureCount { get; set; }
|
||||||
|
|
||||||
@@ -185,7 +190,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Port Server should listen on
|
/// Gets or sets the Port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
@@ -218,8 +223,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module.
|
/// Gets or sets the SharedKey
|
||||||
/// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SharedKey { get; set; }
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
@@ -243,7 +247,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
/// Gets or sets the HeartbeatRequiredIntervalMs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int HeartbeatRequiredIntervalMs { get; set; }
|
public int HeartbeatRequiredIntervalMs { get; set; }
|
||||||
|
|
||||||
@@ -253,12 +257,12 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } }
|
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// String to Match for heartbeat. If null or empty any string will reset heartbeat timer
|
/// Gets or sets the HeartbeatStringToMatch
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string HeartbeatStringToMatch { get; set; }
|
public string HeartbeatStringToMatch { get; set; }
|
||||||
|
|
||||||
//private timers for Heartbeats per client
|
//private timers for Heartbeats per client
|
||||||
Dictionary<uint, Timer> HeartbeatTimerDictionary = new Dictionary<uint, Timer>();
|
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||||
|
|
||||||
//flags to show the secure server is waiting for client at index to send the shared key
|
//flags to show the secure server is waiting for client at index to send the shared key
|
||||||
List<uint> WaitingForSharedKey = new List<uint>();
|
List<uint> WaitingForSharedKey = new List<uint>();
|
||||||
@@ -271,7 +275,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
public List<uint> ConnectedClientsIndexes = new List<uint>();
|
public List<uint> ConnectedClientsIndexes = new List<uint>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defaults to 2000
|
/// Gets or sets the BufferSize
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int BufferSize { get; set; }
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
@@ -334,7 +338,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
|
|
||||||
#region Methods - Server Actions
|
#region Methods - Server Actions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disconnects all clients and stops the server
|
/// KillServer method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void KillServer()
|
public void KillServer()
|
||||||
{
|
{
|
||||||
@@ -351,6 +355,9 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize method
|
||||||
|
/// </summary>
|
||||||
public void Initialize(string key)
|
public void Initialize(string key)
|
||||||
{
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
@@ -390,23 +397,22 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start listening on the specified port
|
/// Listen method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Listen()
|
public void Listen()
|
||||||
{
|
{
|
||||||
lock (_serverLock)
|
ServerCCSection.Enter();
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
this.LogError("Server '{0}': Invalid port", Key);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key);
|
||||||
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
{
|
{
|
||||||
this.LogError("Server '{0}': No Shared Key set", Key);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key);
|
||||||
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -430,42 +436,43 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
SocketErrorCodes status = SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
SocketErrorCodes status = SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||||
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
||||||
{
|
{
|
||||||
this.LogError("Error starting WaitForConnectionAsync {0}", status);
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error starting WaitForConnectionAsync {0}", status);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ServerStopped = false;
|
ServerStopped = false;
|
||||||
}
|
}
|
||||||
OnServerStateChange(SecureServer.State);
|
OnServerStateChange(SecureServer.State);
|
||||||
this.LogInformation("Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus);
|
||||||
|
ServerCCSection.Leave();
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
ServerCCSection.Leave();
|
||||||
|
ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
||||||
}
|
}
|
||||||
} // end lock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stop Listeneing
|
/// StopListening method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StopListening()
|
public void StopListening()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.LogVerbose("Stopping Listener");
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
|
||||||
if (SecureServer != null)
|
if (SecureServer != null)
|
||||||
{
|
{
|
||||||
SecureServer.Stop();
|
SecureServer.Stop();
|
||||||
this.LogVerbose("Server State: {0}", SecureServer.State);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", SecureServer.State);
|
||||||
OnServerStateChange(SecureServer.State);
|
OnServerStateChange(SecureServer.State);
|
||||||
}
|
}
|
||||||
ServerStopped = true;
|
ServerStopped = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error stopping server. Error: {0}", ex.Message);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,24 +480,27 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
/// Disconnects Client
|
/// Disconnects Client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client"></param>
|
/// <param name="client"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// DisconnectClient method
|
||||||
|
/// </summary>
|
||||||
public void DisconnectClient(uint client)
|
public void DisconnectClient(uint client)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SecureServer.Disconnect(client);
|
SecureServer.Disconnect(client);
|
||||||
this.LogVerbose("Disconnected client index: {0}", client);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disconnect All Clients
|
/// DisconnectAllClientsForShutdown method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DisconnectAllClientsForShutdown()
|
public void DisconnectAllClientsForShutdown()
|
||||||
{
|
{
|
||||||
this.LogInformation("Disconnecting All Clients");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients");
|
||||||
if (SecureServer != null)
|
if (SecureServer != null)
|
||||||
{
|
{
|
||||||
SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange;
|
SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange;
|
||||||
@@ -502,17 +512,17 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
SecureServer.Disconnect(i);
|
SecureServer.Disconnect(i);
|
||||||
this.LogInformation("Disconnected client index: {0}", i);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.LogInformation("Server Status: {0}", SecureServer.ServerSocketStatus);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", SecureServer.ServerSocketStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.LogInformation("Disconnected All Clients");
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
|
||||||
ConnectedClientsIndexes.Clear();
|
ConnectedClientsIndexes.Clear();
|
||||||
|
|
||||||
if (!ProgramIsStopping)
|
if (!ProgramIsStopping)
|
||||||
@@ -528,10 +538,13 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
/// Broadcast text from server to all connected clients
|
/// Broadcast text from server to all connected clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// BroadcastText method
|
||||||
|
/// </summary>
|
||||||
public void BroadcastText(string text)
|
public void BroadcastText(string text)
|
||||||
{
|
{
|
||||||
lock (_broadcastLock)
|
CCriticalSection CCBroadcast = new CCriticalSection();
|
||||||
{
|
CCBroadcast.Enter();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (ConnectedClientsIndexes.Count > 0)
|
if (ConnectedClientsIndexes.Count > 0)
|
||||||
@@ -547,12 +560,13 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CCBroadcast.Leave();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
CCBroadcast.Leave();
|
||||||
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
} // end lock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -560,6 +574,9 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SendTextToClient method
|
||||||
|
/// </summary>
|
||||||
public void SendTextToClient(string text, uint clientIndex)
|
public void SendTextToClient(string text, uint clientIndex)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -573,7 +590,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,19 +608,13 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
if (noDelimiter.Contains(HeartbeatStringToMatch))
|
if (noDelimiter.Contains(HeartbeatStringToMatch))
|
||||||
{
|
{
|
||||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
{
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary[clientIndex].Stop();
|
|
||||||
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
|
|
||||||
HeartbeatTimerDictionary[clientIndex].Start();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
heartbeatTimer.Start();
|
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
|
||||||
}
|
}
|
||||||
this.LogDebug("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
||||||
// Return Heartbeat
|
// Return Heartbeat
|
||||||
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
||||||
return remainingText;
|
return remainingText;
|
||||||
@@ -612,25 +623,19 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
{
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary[clientIndex].Stop();
|
|
||||||
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
|
|
||||||
HeartbeatTimerDictionary[clientIndex].Start();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
heartbeatTimer.Start();
|
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
|
||||||
}
|
}
|
||||||
this.LogInformation("Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
|
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
return received;
|
return received;
|
||||||
}
|
}
|
||||||
@@ -640,13 +645,16 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// GetClientIPAddress method
|
||||||
|
/// </summary>
|
||||||
public string GetClientIPAddress(uint clientIndex)
|
public string GetClientIPAddress(uint clientIndex)
|
||||||
{
|
{
|
||||||
this.LogInformation("GetClientIPAddress Index: {0}", clientIndex);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex);
|
||||||
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
||||||
{
|
{
|
||||||
var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
this.LogInformation("GetClientIPAddress IPAddreess: {0}", ipa);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa);
|
||||||
return ipa;
|
return ipa;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -669,13 +677,14 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
clientIndex = (uint)o;
|
clientIndex = (uint)o;
|
||||||
address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
|
|
||||||
this.LogInformation("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
||||||
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
||||||
|
|
||||||
if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
||||||
|
|
||||||
var discoResult = SecureServer.Disconnect(clientIndex);
|
var discoResult = SecureServer.Disconnect(clientIndex);
|
||||||
|
//Debug.Console(1, this, "{0}", discoResult);
|
||||||
|
|
||||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
{
|
{
|
||||||
@@ -704,9 +713,11 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
// Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.SecureServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.SecureServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
this.LogInformation("SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber);
|
||||||
|
|
||||||
if (ConnectedClientsIndexes.Contains(clientIndex))
|
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
ConnectedClientsIndexes.Remove(clientIndex);
|
ConnectedClientsIndexes.Remove(clientIndex);
|
||||||
@@ -728,12 +739,12 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex.Message);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
|
||||||
}
|
}
|
||||||
//Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state
|
//Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state
|
||||||
//after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag
|
//after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag
|
||||||
//is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event.
|
//is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event.
|
||||||
System.Threading.Tasks.Task.Run(() => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)));
|
CrestronInvoke.BeginInvoke(o => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -748,7 +759,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.LogInformation("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
||||||
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
||||||
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||||
if (clientIndex != 0)
|
if (clientIndex != 0)
|
||||||
@@ -768,7 +779,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
||||||
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
||||||
this.LogInformation("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -778,10 +789,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
{
|
{
|
||||||
if (!HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (!HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
{
|
{
|
||||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs));
|
||||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
|
||||||
heartbeatTimer.Start();
|
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,19 +798,19 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogError("Client attempt faulty.");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex.Message);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rearm the listner
|
// Rearm the listner
|
||||||
SocketErrorCodes status = server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
SocketErrorCodes status = server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||||
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
||||||
{
|
{
|
||||||
this.LogError("Socket status connect callback status {0}", status);
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Socket status connect callback status {0}", status);
|
||||||
if (status == SocketErrorCodes.SOCKET_CONNECTION_IN_PROGRESS)
|
if (status == SocketErrorCodes.SOCKET_CONNECTION_IN_PROGRESS)
|
||||||
{
|
{
|
||||||
// There is an issue where on a failed negotiation we need to stop and start the server. This should still leave connected clients intact.
|
// There is an issue where on a failed negotiation we need to stop and start the server. This should still leave connected clients intact.
|
||||||
@@ -839,7 +847,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
if (received != SharedKey)
|
if (received != SharedKey)
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
||||||
this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
||||||
mySecureTCPServer.SendData(clientIndex, b, b.Length);
|
mySecureTCPServer.SendData(clientIndex, b, b.Length);
|
||||||
mySecureTCPServer.Disconnect(clientIndex);
|
mySecureTCPServer.Disconnect(clientIndex);
|
||||||
|
|
||||||
@@ -850,7 +858,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
||||||
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
||||||
OnServerClientReadyForCommunications(clientIndex);
|
OnServerClientReadyForCommunications(clientIndex);
|
||||||
this.LogInformation("Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
||||||
{
|
{
|
||||||
@@ -863,7 +871,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex.Message);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
|
||||||
}
|
}
|
||||||
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
||||||
@@ -871,8 +879,9 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
{
|
{
|
||||||
if (System.Threading.Monitor.TryEnter(_dequeueLock))
|
var gotLock = DequeueLock.TryEnter();
|
||||||
System.Threading.Tasks.Task.Run(() => DequeueEvent());
|
if (gotLock)
|
||||||
|
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -882,7 +891,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
|
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
||||||
/// It will dequeue items as they are enqueued automatically.
|
/// It will dequeue items as they are enqueued automatically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void DequeueEvent()
|
void DequeueEvent()
|
||||||
@@ -902,10 +911,13 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
this.LogError(e, "DequeueEvent error");
|
this.LogException(e, "DequeueEvent error");
|
||||||
|
}
|
||||||
|
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
|
||||||
|
if (DequeueLock != null)
|
||||||
|
{
|
||||||
|
DequeueLock.Leave();
|
||||||
}
|
}
|
||||||
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
|
|
||||||
System.Threading.Monitor.Exit(_dequeueLock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -976,7 +988,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
if (MonitorClient != null)
|
if (MonitorClient != null)
|
||||||
MonitorClient.Disconnect();
|
MonitorClient.Disconnect();
|
||||||
|
|
||||||
this.LogInformation("Program stopping. Closing server");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server");
|
||||||
KillServer();
|
KillServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1002,9 +1014,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MonitorClientTimer = new Timer(60000) { AutoReset = false };
|
MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000);
|
||||||
MonitorClientTimer.Elapsed += (s, e) => RunMonitorClient();
|
|
||||||
MonitorClientTimer.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1019,7 +1029,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
||||||
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
||||||
|
|
||||||
this.LogInformation("Starting monitor check");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check");
|
||||||
|
|
||||||
MonitorClient.Connect();
|
MonitorClient.Connect();
|
||||||
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
||||||
@@ -1046,7 +1056,7 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
{
|
{
|
||||||
if (args.IsReady)
|
if (args.IsReady)
|
||||||
{
|
{
|
||||||
this.LogInformation("Monitor client connection success. Disconnecting in 2s");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s");
|
||||||
MonitorClientTimer.Stop();
|
MonitorClientTimer.Stop();
|
||||||
MonitorClientTimer = null;
|
MonitorClientTimer = null;
|
||||||
MonitorClientFailureCount = 0;
|
MonitorClientFailureCount = 0;
|
||||||
@@ -1067,13 +1077,13 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
StopMonitorClient();
|
StopMonitorClient();
|
||||||
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
||||||
{
|
{
|
||||||
this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}",
|
Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
|
||||||
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
||||||
StartMonitorClient();
|
StartMonitorClient();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogError(
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error,
|
||||||
"\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************",
|
"\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************",
|
||||||
MonitorClientMaxFailureCount);
|
MonitorClientMaxFailureCount);
|
||||||
|
|
||||||
@@ -1085,3 +1095,4 @@ public class GenericSecureTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,23 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Timers;
|
using System.Threading;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
|
using Org.BouncyCastle.Utilities;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
using Renci.SshNet;
|
using Renci.SshNet;
|
||||||
using Renci.SshNet.Common;
|
using Renci.SshNet.Common;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// SSH Client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||||
{
|
{
|
||||||
private const string SPlusKey = "Uninitialized SshClient";
|
private const string SPlusKey = "Uninitialized SshClient";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Object to enable stream debugging
|
/// Object to enable stream debugging
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -35,13 +38,8 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||||
|
|
||||||
///// <summary>
|
|
||||||
/////
|
|
||||||
///// </summary>
|
|
||||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Address of server
|
/// Gets or sets the Hostname
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Hostname { get; set; }
|
public string Hostname { get; set; }
|
||||||
|
|
||||||
@@ -51,12 +49,12 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Username for server
|
/// Gets or sets the Username
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// And... Password for server. That was worth documenting!
|
/// Gets or sets the Password
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
@@ -66,7 +64,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
public bool IsConnected
|
public bool IsConnected
|
||||||
{
|
{
|
||||||
// returns false if no client or not connected
|
// returns false if no client or not connected
|
||||||
get { return Client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
get { return client != null && ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -78,20 +76,30 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SSH Client
|
/// Socket status change event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SocketStatus ClientStatus
|
public SocketStatus ClientStatus
|
||||||
{
|
{
|
||||||
get { return _ClientStatus; }
|
get { lock (_stateLock) { return _ClientStatus; } }
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
if (_ClientStatus == value)
|
bool shouldFireEvent = false;
|
||||||
return;
|
lock (_stateLock)
|
||||||
|
{
|
||||||
|
if (_ClientStatus != value)
|
||||||
|
{
|
||||||
_ClientStatus = value;
|
_ClientStatus = value;
|
||||||
|
shouldFireEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fire event outside lock to avoid deadlock
|
||||||
|
if (shouldFireEvent)
|
||||||
OnConnectionChange();
|
OnConnectionChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SocketStatus _ClientStatus;
|
|
||||||
|
private SocketStatus _ClientStatus;
|
||||||
|
private bool _ConnectEnabled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
/// Contains the familiar Simpl analog status values. This drives the ConnectionChange event
|
||||||
@@ -99,7 +107,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort UStatus
|
public ushort UStatus
|
||||||
{
|
{
|
||||||
get { return (ushort)_ClientStatus; }
|
get { lock (_stateLock) { return (ushort)_ClientStatus; } }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -110,7 +118,11 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Will be set and unset by connect and disconnect only
|
/// Will be set and unset by connect and disconnect only
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ConnectEnabled { get; private set; }
|
public bool ConnectEnabled
|
||||||
|
{
|
||||||
|
get { lock (_stateLock) { return _ConnectEnabled; } }
|
||||||
|
private set { lock (_stateLock) { _ConnectEnabled = value; } }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// S+ helper for AutoReconnect
|
/// S+ helper for AutoReconnect
|
||||||
@@ -122,20 +134,29 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Millisecond value, determines the timeout period in between reconnect attempts.
|
/// Gets or sets the AutoReconnectIntervalMs
|
||||||
/// Set to 5000 by default
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int AutoReconnectIntervalMs { get; set; }
|
public int AutoReconnectIntervalMs { get; set; }
|
||||||
|
|
||||||
SshClient Client;
|
private SshClient client;
|
||||||
|
|
||||||
ShellStream TheStream;
|
private ShellStream shellStream;
|
||||||
|
|
||||||
Timer ReconnectTimer;
|
private readonly Timer reconnectTimer;
|
||||||
|
|
||||||
private System.Threading.SemaphoreSlim connectLock = new System.Threading.SemaphoreSlim(1);
|
//Lock object to prevent simulatneous connect/disconnect operations
|
||||||
|
//private CCriticalSection connectLock = new CCriticalSection();
|
||||||
|
private readonly SemaphoreSlim connectLock = new SemaphoreSlim(1);
|
||||||
|
|
||||||
private bool DisconnectLogged = false;
|
// Thread-safety lock for state changes
|
||||||
|
private readonly object _stateLock = new object();
|
||||||
|
|
||||||
|
private bool disconnectLogged = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When true, turns off echo for the SSH session
|
||||||
|
/// </summary>
|
||||||
|
public bool DisableEcho { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Typical constructor.
|
/// Typical constructor.
|
||||||
@@ -152,14 +173,13 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
Password = password;
|
Password = password;
|
||||||
AutoReconnectIntervalMs = 5000;
|
AutoReconnectIntervalMs = 5000;
|
||||||
|
|
||||||
ReconnectTimer = new Timer { AutoReset = false, Enabled = false };
|
reconnectTimer = new Timer(o =>
|
||||||
ReconnectTimer.Elapsed += (s, e) =>
|
|
||||||
{
|
{
|
||||||
if (ConnectEnabled)
|
if (ConnectEnabled) // Now thread-safe property access
|
||||||
{
|
{
|
||||||
Connect();
|
Connect();
|
||||||
}
|
}
|
||||||
};
|
}, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -171,24 +191,23 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
AutoReconnectIntervalMs = 5000;
|
AutoReconnectIntervalMs = 5000;
|
||||||
|
|
||||||
ReconnectTimer = new Timer { AutoReset = false, Enabled = false };
|
reconnectTimer = new Timer(o =>
|
||||||
ReconnectTimer.Elapsed += (s, e) =>
|
|
||||||
{
|
{
|
||||||
if (ConnectEnabled)
|
if (ConnectEnabled) // Now thread-safe property access
|
||||||
{
|
{
|
||||||
Connect();
|
Connect();
|
||||||
}
|
}
|
||||||
};
|
}, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles closing this up when the program shuts down
|
/// Handles closing this up when the program shuts down
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
private void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
{
|
{
|
||||||
if (programEventType == eProgramStatusEventType.Stopping)
|
if (programEventType == eProgramStatusEventType.Stopping)
|
||||||
{
|
{
|
||||||
if (Client != null)
|
if (client != null)
|
||||||
{
|
{
|
||||||
this.LogDebug("Program stopping. Closing connection");
|
this.LogDebug("Program stopping. Closing connection");
|
||||||
Disconnect();
|
Disconnect();
|
||||||
@@ -197,7 +216,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connect to the server, using the provided properties.
|
/// Connect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Connect()
|
public void Connect()
|
||||||
{
|
{
|
||||||
@@ -223,13 +242,10 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
this.LogDebug("Attempting connect");
|
this.LogDebug("Attempting connect");
|
||||||
|
|
||||||
// Cancel reconnect if running.
|
// Cancel reconnect if running.
|
||||||
if (ReconnectTimer != null)
|
StopReconnectTimer();
|
||||||
{
|
|
||||||
ReconnectTimer.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup the old client if it already exists
|
// Cleanup the old client if it already exists
|
||||||
if (Client != null)
|
if (client != null)
|
||||||
{
|
{
|
||||||
this.LogDebug("Cleaning up disconnected client");
|
this.LogDebug("Cleaning up disconnected client");
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||||
@@ -242,81 +258,92 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
|
|
||||||
this.LogDebug("Creating new SshClient");
|
this.LogDebug("Creating new SshClient");
|
||||||
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
|
ConnectionInfo connectionInfo = new ConnectionInfo(Hostname, Port, Username, pauth, kauth);
|
||||||
Client = new SshClient(connectionInfo);
|
client = new SshClient(connectionInfo);
|
||||||
Client.ErrorOccurred += Client_ErrorOccurred;
|
client.ErrorOccurred += Client_ErrorOccurred;
|
||||||
|
|
||||||
//Attempt to connect
|
//Attempt to connect
|
||||||
ClientStatus = SocketStatus.SOCKET_STATUS_WAITING;
|
ClientStatus = SocketStatus.SOCKET_STATUS_WAITING;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Client.Connect();
|
client.Connect();
|
||||||
TheStream = Client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534);
|
|
||||||
if (TheStream.DataAvailable)
|
var modes = new Dictionary<TerminalModes, uint>();
|
||||||
|
|
||||||
|
if (DisableEcho)
|
||||||
|
{
|
||||||
|
modes.Add(TerminalModes.ECHO, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
shellStream = client.CreateShellStream("PDTShell", 0, 0, 0, 0, 65534, modes);
|
||||||
|
if (shellStream.DataAvailable)
|
||||||
{
|
{
|
||||||
// empty the buffer if there is data
|
// empty the buffer if there is data
|
||||||
string str = TheStream.Read();
|
string str = shellStream.Read();
|
||||||
}
|
}
|
||||||
TheStream.DataReceived += Stream_DataReceived;
|
shellStream.DataReceived += Stream_DataReceived;
|
||||||
this.LogInformation("Connected");
|
this.LogInformation("Connected");
|
||||||
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
|
ClientStatus = SocketStatus.SOCKET_STATUS_CONNECTED;
|
||||||
DisconnectLogged = false;
|
disconnectLogged = false;
|
||||||
}
|
}
|
||||||
catch (SshConnectionException e)
|
catch (SshConnectionException e)
|
||||||
{
|
{
|
||||||
var ie = e.InnerException; // The details are inside!!
|
var ie = e.InnerException; // The details are inside!!
|
||||||
|
var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||||
|
|
||||||
if (ie is SocketException)
|
if (ie is SocketException)
|
||||||
{
|
{
|
||||||
this.LogException(ie, "CONNECTION failure: Cannot reach host");
|
this.LogError("CONNECTION failure: Cannot reach host");
|
||||||
|
this.LogVerbose(ie, "Exception details: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ie is System.Net.Sockets.SocketException socketException)
|
if (ie is System.Net.Sockets.SocketException socketException)
|
||||||
{
|
{
|
||||||
this.LogException(ie, "Connection failure: Cannot reach {host} on {port}",
|
this.LogError("Connection failure: Cannot reach {host} on {port}",
|
||||||
Hostname, Port);
|
Hostname, Port);
|
||||||
|
this.LogVerbose(socketException, "SocketException details: ");
|
||||||
}
|
}
|
||||||
if (ie is SshAuthenticationException)
|
if (ie is SshAuthenticationException)
|
||||||
{
|
{
|
||||||
this.LogException(ie, "Authentication failure for username {userName}", Username);
|
this.LogError("Authentication failure for username {userName}", Username);
|
||||||
|
this.LogVerbose(ie, "AuthenticationException details: ");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
this.LogException(ie, "Error on connect");
|
{
|
||||||
|
this.LogError("Error on connect: {error}", ie.Message);
|
||||||
|
this.LogVerbose(ie, "Exception details: ");
|
||||||
|
}
|
||||||
|
|
||||||
DisconnectLogged = true;
|
disconnectLogged = true;
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||||
if (AutoReconnect)
|
if (AutoReconnect)
|
||||||
{
|
{
|
||||||
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
|
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||||
ReconnectTimer.Stop();
|
StartReconnectTimer();
|
||||||
ReconnectTimer.Interval = AutoReconnectIntervalMs;
|
|
||||||
ReconnectTimer.Start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (SshOperationTimeoutException ex)
|
catch (SshOperationTimeoutException ex)
|
||||||
{
|
{
|
||||||
this.LogWarning("Connection attempt timed out: {message}", ex.Message);
|
this.LogWarning("Connection attempt timed out: {message}", ex.Message);
|
||||||
|
|
||||||
DisconnectLogged = true;
|
disconnectLogged = true;
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||||
if (AutoReconnect)
|
if (AutoReconnect)
|
||||||
{
|
{
|
||||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||||
ReconnectTimer.Stop();
|
StartReconnectTimer();
|
||||||
ReconnectTimer.Interval = AutoReconnectIntervalMs;
|
|
||||||
ReconnectTimer.Start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
this.LogException(e, "Unhandled exception on connect");
|
var errorLogLevel = disconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||||
DisconnectLogged = true;
|
this.LogError("Unhandled exception on connect: {error}", e.Message);
|
||||||
|
this.LogVerbose(e, "Exception details: ");
|
||||||
|
disconnectLogged = true;
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||||
if (AutoReconnect)
|
if (AutoReconnect)
|
||||||
{
|
{
|
||||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||||
ReconnectTimer.Stop();
|
StartReconnectTimer();
|
||||||
ReconnectTimer.Interval = AutoReconnectIntervalMs;
|
|
||||||
ReconnectTimer.Start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,7 +361,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
{
|
{
|
||||||
ConnectEnabled = false;
|
ConnectEnabled = false;
|
||||||
// Stop trying reconnects, if we are
|
// Stop trying reconnects, if we are
|
||||||
ReconnectTimer.Stop();
|
StopReconnectTimer();
|
||||||
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||||
}
|
}
|
||||||
@@ -348,12 +375,12 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Client != null)
|
if (client != null)
|
||||||
{
|
{
|
||||||
Client.ErrorOccurred -= Client_ErrorOccurred;
|
client.ErrorOccurred -= Client_ErrorOccurred;
|
||||||
Client.Disconnect();
|
client.Disconnect();
|
||||||
Client.Dispose();
|
client.Dispose();
|
||||||
Client = null;
|
client = null;
|
||||||
ClientStatus = status;
|
ClientStatus = status;
|
||||||
this.LogDebug("Disconnected");
|
this.LogDebug("Disconnected");
|
||||||
}
|
}
|
||||||
@@ -367,16 +394,16 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Kills the stream
|
/// Kills the stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void KillStream()
|
private void KillStream()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (TheStream != null)
|
if (shellStream != null)
|
||||||
{
|
{
|
||||||
TheStream.DataReceived -= Stream_DataReceived;
|
shellStream.DataReceived -= Stream_DataReceived;
|
||||||
TheStream.Close();
|
shellStream.Close();
|
||||||
TheStream.Dispose();
|
shellStream.Dispose();
|
||||||
TheStream = null;
|
shellStream = null;
|
||||||
this.LogDebug("Disconnected stream");
|
this.LogDebug("Disconnected stream");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,7 +416,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles the keyboard interactive authentication, should it be required.
|
/// Handles the keyboard interactive authentication, should it be required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
|
private void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
|
||||||
{
|
{
|
||||||
foreach (AuthenticationPrompt prompt in e.Prompts)
|
foreach (AuthenticationPrompt prompt in e.Prompts)
|
||||||
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
|
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
|
||||||
@@ -399,7 +426,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
|
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Stream_DataReceived(object sender, ShellDataEventArgs e)
|
private void Stream_DataReceived(object sender, ShellDataEventArgs e)
|
||||||
{
|
{
|
||||||
if (((ShellStream)sender).Length <= 0L)
|
if (((ShellStream)sender).Length <= 0L)
|
||||||
{
|
{
|
||||||
@@ -435,9 +462,9 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
|
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
|
||||||
/// event
|
/// event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
private void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
System.Threading.Tasks.Task.Run(() =>
|
CrestronInvoke.BeginInvoke(o =>
|
||||||
{
|
{
|
||||||
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
|
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
|
||||||
this.LogError("Disconnected by remote");
|
this.LogError("Disconnected by remote");
|
||||||
@@ -455,9 +482,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
if (AutoReconnect && ConnectEnabled)
|
if (AutoReconnect && ConnectEnabled)
|
||||||
{
|
{
|
||||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||||
ReconnectTimer.Stop();
|
StartReconnectTimer();
|
||||||
ReconnectTimer.Interval = AutoReconnectIntervalMs;
|
|
||||||
ReconnectTimer.Start();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -465,10 +490,9 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper for ConnectionChange event
|
/// Helper for ConnectionChange event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void OnConnectionChange()
|
private void OnConnectionChange()
|
||||||
{
|
{
|
||||||
if (ConnectionChange != null)
|
ConnectionChange?.Invoke(this, new GenericSocketStatusChageEventArgs(this));
|
||||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IBasicCommunication Members
|
#region IBasicCommunication Members
|
||||||
@@ -476,12 +500,12 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends text to the server
|
/// Sends text to the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text">The text to send</param>
|
||||||
public void SendText(string text)
|
public void SendText(string text)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Client != null && TheStream != null && IsConnected)
|
if (client != null && shellStream != null && IsConnected)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||||
this.LogInformation(
|
this.LogInformation(
|
||||||
@@ -489,8 +513,8 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
text.Length,
|
text.Length,
|
||||||
ComTextHelper.GetDebugText(text));
|
ComTextHelper.GetDebugText(text));
|
||||||
|
|
||||||
TheStream.Write(text);
|
shellStream.Write(text);
|
||||||
TheStream.Flush();
|
shellStream.Flush();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -502,8 +526,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
||||||
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||||
ReconnectTimer.Stop();
|
StopReconnectTimer();
|
||||||
ReconnectTimer.Start();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -514,18 +537,18 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends Bytes to the server
|
/// Sends Bytes to the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes">The bytes to send</param>
|
||||||
public void SendBytes(byte[] bytes)
|
public void SendBytes(byte[] bytes)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Client != null && TheStream != null && IsConnected)
|
if (client != null && shellStream != null && IsConnected)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||||
|
|
||||||
TheStream.Write(bytes, 0, bytes.Length);
|
shellStream.Write(bytes, 0, bytes.Length);
|
||||||
TheStream.Flush();
|
shellStream.Flush();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -537,8 +560,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||||
|
|
||||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||||
ReconnectTimer.Stop();
|
StopReconnectTimer();
|
||||||
ReconnectTimer.Start();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -547,10 +569,89 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Safely starts the reconnect timer with exception handling
|
||||||
|
/// </summary>
|
||||||
|
private void StartReconnectTimer()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
reconnectTimer?.Change(AutoReconnectIntervalMs, System.Threading.Timeout.Infinite);
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// Timer was disposed, ignore
|
||||||
|
this.LogDebug("Attempted to start timer but it was already disposed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Safely stops the reconnect timer with exception handling
|
||||||
|
/// </summary>
|
||||||
|
private void StopReconnectTimer()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
reconnectTimer?.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// Timer was disposed, ignore
|
||||||
|
this.LogDebug("Attempted to stop timer but it was already disposed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deactivate method - properly dispose of resources
|
||||||
|
/// </summary>
|
||||||
|
public override bool Deactivate()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.LogDebug("Deactivating SSH client - disposing resources");
|
||||||
|
|
||||||
|
// Stop trying reconnects
|
||||||
|
ConnectEnabled = false;
|
||||||
|
StopReconnectTimer();
|
||||||
|
|
||||||
|
// Disconnect and cleanup client
|
||||||
|
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||||
|
|
||||||
|
// Dispose timer
|
||||||
|
try
|
||||||
|
{
|
||||||
|
reconnectTimer?.Dispose();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// Already disposed, ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispose semaphore
|
||||||
|
try
|
||||||
|
{
|
||||||
|
connectLock?.Dispose();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// Already disposed, ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Deactivate();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.LogException(ex, "Error during SSH client deactivation");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//*****************************************************************************************************
|
//*****************************************************************************************************
|
||||||
//*****************************************************************************************************
|
//*****************************************************************************************************
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when connection changes
|
/// Represents a SshConnectionChangeEventArgs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SshConnectionChangeEventArgs : EventArgs
|
public class SshConnectionChangeEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
@@ -560,17 +661,17 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
|||||||
public bool IsConnected { get; private set; }
|
public bool IsConnected { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connection Status represented as a ushort
|
/// Gets or sets the UIsConnected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } }
|
public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The client
|
/// Gets or sets the Client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GenericSshClient Client { get; private set; }
|
public GenericSshClient Client { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Socket Status as represented by
|
/// Gets or sets the Status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort Status { get { return Client.UStatus; } }
|
public ushort Status { get { return Client.UStatus; } }
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
|
||||||
using Timer = System.Timers.Timer;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
using Newtonsoft.Json;
|
||||||
using Required = NewtonsoftJson::Newtonsoft.Json.Required;
|
|
||||||
using PepperDash.Core.Logging;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class to handle basic TCP/IP communications with a server
|
/// A class to handle basic TCP/IP communications with a server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -66,7 +59,7 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Port on server
|
/// Gets or sets the Port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
@@ -131,6 +124,12 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ushort representation of client status
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete]
|
||||||
|
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connection failure reason
|
/// Connection failure reason
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -169,10 +168,10 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Lock object to prevent simulatneous connect/disconnect operations
|
//Lock object to prevent simulatneous connect/disconnect operations
|
||||||
private readonly object _connectLock = new();
|
private CCriticalSection connectLock = new CCriticalSection();
|
||||||
|
|
||||||
// private Timer for auto reconnect
|
// private Timer for auto reconnect
|
||||||
private Timer RetryTimer;
|
private CTimer RetryTimer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
@@ -191,7 +190,10 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
Port = port;
|
Port = port;
|
||||||
BufferSize = bufferSize;
|
BufferSize = bufferSize;
|
||||||
|
|
||||||
SetupRetryTimer();
|
RetryTimer = new CTimer(o =>
|
||||||
|
{
|
||||||
|
Reconnect();
|
||||||
|
}, Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -206,7 +208,10 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
AutoReconnectIntervalMs = 5000;
|
AutoReconnectIntervalMs = 5000;
|
||||||
BufferSize = 2000;
|
BufferSize = 2000;
|
||||||
|
|
||||||
SetupRetryTimer();
|
RetryTimer = new CTimer(o =>
|
||||||
|
{
|
||||||
|
Reconnect();
|
||||||
|
}, Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -220,19 +225,14 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
AutoReconnectIntervalMs = 5000;
|
AutoReconnectIntervalMs = 5000;
|
||||||
BufferSize = 2000;
|
BufferSize = 2000;
|
||||||
|
|
||||||
SetupRetryTimer();
|
RetryTimer = new CTimer(o =>
|
||||||
}
|
|
||||||
|
|
||||||
private void SetupRetryTimer()
|
|
||||||
{
|
{
|
||||||
RetryTimer = new Timer { AutoReset = false, Enabled = false };
|
Reconnect();
|
||||||
RetryTimer.Elapsed += (s, e) => Reconnect();
|
}, Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Just to help S+ set the key
|
/// Initialize method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Initialize(string key)
|
public void Initialize(string key)
|
||||||
{
|
{
|
||||||
@@ -246,7 +246,7 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
{
|
{
|
||||||
if (programEventType == eProgramStatusEventType.Stopping)
|
if (programEventType == eProgramStatusEventType.Stopping)
|
||||||
{
|
{
|
||||||
this.LogInformation("Program stopping. Closing connection");
|
Debug.Console(1, this, "Program stopping. Closing connection");
|
||||||
Deactivate();
|
Deactivate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,6 +255,9 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// Deactivate method
|
||||||
|
/// </summary>
|
||||||
public override bool Deactivate()
|
public override bool Deactivate()
|
||||||
{
|
{
|
||||||
RetryTimer.Stop();
|
RetryTimer.Stop();
|
||||||
@@ -268,28 +271,29 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to connect to the server
|
/// Connect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Connect()
|
public void Connect()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
if (string.IsNullOrEmpty(Hostname))
|
||||||
{
|
{
|
||||||
this.LogWarning("GenericTcpIpClient '{0}': No address set", Key);
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
this.LogWarning("GenericTcpIpClient '{0}': Invalid port", Key);
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': Invalid port", Key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_connectLock)
|
try
|
||||||
{
|
{
|
||||||
|
connectLock.Enter();
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
{
|
{
|
||||||
this.LogInformation("Connection already connected. Exiting Connect()");
|
Debug.Console(1, this, "Connection already connected. Exiting Connect()");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -302,6 +306,10 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
_client.ConnectToServerAsync(ConnectToServerCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
connectLock.Leave();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Reconnect()
|
private void Reconnect()
|
||||||
@@ -310,43 +318,53 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lock (_connectLock)
|
try
|
||||||
{
|
{
|
||||||
|
connectLock.Enter();
|
||||||
if (IsConnected || DisconnectCalledByUser == true)
|
if (IsConnected || DisconnectCalledByUser == true)
|
||||||
{
|
{
|
||||||
this.LogInformation("Reconnect no longer needed. Exiting Reconnect()");
|
Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogInformation("Attempting reconnect now");
|
Debug.Console(1, this, "Attempting reconnect now");
|
||||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
_client.ConnectToServerAsync(ConnectToServerCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
connectLock.Leave();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to disconnect the client
|
/// Disconnect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Disconnect()
|
public void Disconnect()
|
||||||
{
|
{
|
||||||
lock (_connectLock)
|
try
|
||||||
{
|
{
|
||||||
|
connectLock.Enter();
|
||||||
DisconnectCalledByUser = true;
|
DisconnectCalledByUser = true;
|
||||||
|
|
||||||
// Stop trying reconnects, if we are
|
// Stop trying reconnects, if we are
|
||||||
RetryTimer.Stop();
|
RetryTimer.Stop();
|
||||||
DisconnectClient();
|
DisconnectClient();
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
connectLock.Leave();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Does the actual disconnect business
|
/// DisconnectClient method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DisconnectClient()
|
public void DisconnectClient()
|
||||||
{
|
{
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
{
|
{
|
||||||
this.LogInformation("Disconnecting client");
|
Debug.Console(1, this, "Disconnecting client");
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
_client.DisconnectFromServer();
|
_client.DisconnectFromServer();
|
||||||
}
|
}
|
||||||
@@ -360,12 +378,12 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
{
|
{
|
||||||
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
this.LogInformation("Server connection result: {0}", c.ClientStatus);
|
Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus);
|
||||||
WaitAndTryReconnect();
|
WaitAndTryReconnect();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogInformation("Server connection result: {0}", c.ClientStatus);
|
Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,19 +392,22 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void WaitAndTryReconnect()
|
void WaitAndTryReconnect()
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
CrestronInvoke.BeginInvoke(o =>
|
||||||
{
|
{
|
||||||
lock (_connectLock)
|
try
|
||||||
{
|
{
|
||||||
|
connectLock.Enter();
|
||||||
if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null)
|
if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null)
|
||||||
{
|
{
|
||||||
DisconnectClient();
|
DisconnectClient();
|
||||||
this.LogInformation("Attempting reconnect, status={0}", _client.ClientStatus);
|
Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus);
|
||||||
RetryTimer.Stop();
|
RetryTimer.Reset(AutoReconnectIntervalMs);
|
||||||
RetryTimer.Interval = AutoReconnectIntervalMs;
|
|
||||||
RetryTimer.Start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
connectLock.Leave();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +428,7 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
{
|
{
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||||
{
|
{
|
||||||
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||||
}
|
}
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||||
}
|
}
|
||||||
@@ -418,7 +439,7 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
|
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||||
{
|
{
|
||||||
this.LogInformation("Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||||
@@ -429,14 +450,14 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// General send method
|
/// SendText method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendText(string text)
|
public void SendText(string text)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
// Check debug level before processing byte array
|
// Check debug level before processing byte array
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||||
this.LogInformation("Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
_client.SendData(bytes, bytes.Length);
|
_client.SendData(bytes, bytes.Length);
|
||||||
}
|
}
|
||||||
@@ -458,10 +479,13 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
/// Sends Bytes to the server
|
/// Sends Bytes to the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SendBytes method
|
||||||
|
/// </summary>
|
||||||
public void SendBytes(byte[] bytes)
|
public void SendBytes(byte[] bytes)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||||
if (_client != null)
|
if (_client != null)
|
||||||
_client.SendData(bytes, bytes.Length);
|
_client.SendData(bytes, bytes.Length);
|
||||||
}
|
}
|
||||||
@@ -475,12 +499,12 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
{
|
{
|
||||||
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
this.LogDebug("Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||||
WaitAndTryReconnect();
|
WaitAndTryReconnect();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogDebug("Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||||
_client.ReceiveDataAsync(Receive);
|
_client.ReceiveDataAsync(Receive);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,7 +515,7 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration properties for TCP/SSH Connections
|
/// Represents a TcpSshPropertiesConfig
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TcpSshPropertiesConfig
|
public class TcpSshPropertiesConfig
|
||||||
{
|
{
|
||||||
@@ -512,7 +536,7 @@ public class TcpSshPropertiesConfig
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Passord credential
|
/// Gets or sets the Password
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
|
||||||
@@ -547,6 +571,7 @@ public class TcpSshPropertiesConfig
|
|||||||
AutoReconnectIntervalMs = 5000;
|
AutoReconnectIntervalMs = 5000;
|
||||||
Username = "";
|
Username = "";
|
||||||
Password = "";
|
Password = "";
|
||||||
|
DisableSshEcho = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,16 @@ PepperDash Technology Corporation reserves all rights under applicable laws.
|
|||||||
------------------------------------ */
|
------------------------------------ */
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Timers;
|
using System.Text.RegularExpressions;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generic TCP/IP client for server
|
/// Generic TCP/IP client for server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -68,7 +69,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
public string Hostname { get; set; }
|
public string Hostname { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Port on server
|
/// Gets or sets the Port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
/// Gets or sets the SharedKey
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SharedKey { get; set; }
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
private bool WaitingForSharedKeyResponse { get; set; }
|
private bool WaitingForSharedKeyResponse { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defaults to 2000
|
/// Gets or sets the BufferSize
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int BufferSize { get; set; }
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
@@ -209,7 +210,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// private Timer for auto reconnect
|
/// private Timer for auto reconnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Timer RetryTimer;
|
CTimer RetryTimer;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -236,13 +237,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int HeartbeatInterval = 50000;
|
public int HeartbeatInterval = 50000;
|
||||||
|
|
||||||
Timer HeartbeatSendTimer;
|
CTimer HeartbeatSendTimer;
|
||||||
Timer HeartbeatAckTimer;
|
CTimer HeartbeatAckTimer;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to force disconnection on a dead connect attempt
|
/// Used to force disconnection on a dead connect attempt
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Timer ConnectFailTimer;
|
CTimer ConnectFailTimer;
|
||||||
Timer WaitForSharedKey;
|
CTimer WaitForSharedKey;
|
||||||
private int ConnectionCount;
|
private int ConnectionCount;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal secure client
|
/// Internal secure client
|
||||||
@@ -288,7 +289,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
#region Methods
|
#region Methods
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Just to help S+ set the key
|
/// Initialize method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Initialize(string key)
|
public void Initialize(string key)
|
||||||
{
|
{
|
||||||
@@ -302,7 +303,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
{
|
{
|
||||||
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
||||||
{
|
{
|
||||||
this.LogInformation("Program stopping. Closing Client connection");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection");
|
||||||
ProgramIsStopping = true;
|
ProgramIsStopping = true;
|
||||||
Disconnect();
|
Disconnect();
|
||||||
}
|
}
|
||||||
@@ -310,22 +311,22 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connect Method. Will return if already connected. Will write errors if missing address, port, or unique key/name.
|
/// Connect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Connect()
|
public void Connect()
|
||||||
{
|
{
|
||||||
ConnectionCount++;
|
ConnectionCount++;
|
||||||
this.LogDebug("Attempting connect Count:{0}", ConnectionCount);
|
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
|
||||||
|
|
||||||
|
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
{
|
{
|
||||||
this.LogInformation("Already connected. Ignoring.");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (IsTryingToConnect)
|
if (IsTryingToConnect)
|
||||||
{
|
{
|
||||||
this.LogInformation("Already trying to connect. Ignoring.");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
@@ -338,17 +339,17 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
if (string.IsNullOrEmpty(Hostname))
|
||||||
{
|
{
|
||||||
this.LogWarning("DynamicTcpClient: No address set");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
this.LogWarning("DynamicTcpClient: Invalid port");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
{
|
{
|
||||||
this.LogWarning("DynamicTcpClient: No Shared Key set");
|
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,10 +370,9 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
|
|
||||||
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||||
|
|
||||||
ConnectFailTimer = new Timer(30000) { AutoReset = false };
|
ConnectFailTimer = new CTimer(o =>
|
||||||
ConnectFailTimer.Elapsed += (s, e) =>
|
|
||||||
{
|
{
|
||||||
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||||
if (IsTryingToConnect)
|
if (IsTryingToConnect)
|
||||||
{
|
{
|
||||||
IsTryingToConnect = false;
|
IsTryingToConnect = false;
|
||||||
@@ -384,13 +384,12 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
//SecureClient.DisconnectFromServer();
|
//SecureClient.DisconnectFromServer();
|
||||||
//CheckClosedAndTryReconnect();
|
//CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
};
|
}, 30000);
|
||||||
ConnectFailTimer.Start();
|
|
||||||
|
|
||||||
this.LogDebug("Making Connection Count:{0}", ConnectionCount);
|
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
|
||||||
Client.ConnectToServerAsync(o =>
|
Client.ConnectToServerAsync(o =>
|
||||||
{
|
{
|
||||||
this.LogDebug("ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||||
|
|
||||||
if (ConnectFailTimer != null)
|
if (ConnectFailTimer != null)
|
||||||
{
|
{
|
||||||
@@ -400,22 +399,22 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
|
|
||||||
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||||
o.ReceiveDataAsync(Receive);
|
o.ReceiveDataAsync(Receive);
|
||||||
|
|
||||||
if (SharedKeyRequired)
|
if (SharedKeyRequired)
|
||||||
{
|
{
|
||||||
WaitingForSharedKeyResponse = true;
|
WaitingForSharedKeyResponse = true;
|
||||||
WaitForSharedKey = new Timer(15000) { AutoReset = false };
|
WaitForSharedKey = new CTimer(timer =>
|
||||||
WaitForSharedKey.Elapsed += (s, e) =>
|
|
||||||
{
|
{
|
||||||
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
|
||||||
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||||
|
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
||||||
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
||||||
o.DisconnectFromServer();
|
o.DisconnectFromServer();
|
||||||
//CheckClosedAndTryReconnect();
|
//CheckClosedAndTryReconnect();
|
||||||
//OnClientReadyForcommunications(false); // Should send false event
|
//OnClientReadyForcommunications(false); // Should send false event
|
||||||
};
|
}, 15000);
|
||||||
WaitForSharedKey.Start();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -429,22 +428,21 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogWarning("Connect attempt failed {0}", o.ClientStatus);
|
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
||||||
CheckClosedAndTryReconnect();
|
CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Client connection exception: {0}", ex.Message);
|
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message);
|
||||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
|
||||||
IsTryingToConnect = false;
|
IsTryingToConnect = false;
|
||||||
CheckClosedAndTryReconnect();
|
CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Disconnect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Disconnect()
|
public void Disconnect()
|
||||||
{
|
{
|
||||||
@@ -474,7 +472,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
if (Client != null)
|
if (Client != null)
|
||||||
{
|
{
|
||||||
//SecureClient.DisconnectFromServer();
|
//SecureClient.DisconnectFromServer();
|
||||||
this.LogVerbose("Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||||
Client.SocketStatusChange -= Client_SocketStatusChange;
|
Client.SocketStatusChange -= Client_SocketStatusChange;
|
||||||
Client.Dispose();
|
Client.Dispose();
|
||||||
Client = null;
|
Client = null;
|
||||||
@@ -496,22 +494,20 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
{
|
{
|
||||||
if (Client != null)
|
if (Client != null)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Cleaning up remotely closed/failed connection.");
|
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
|
||||||
Cleanup();
|
Cleanup();
|
||||||
}
|
}
|
||||||
if (!DisconnectCalledByUser && AutoReconnect)
|
if (!DisconnectCalledByUser && AutoReconnect)
|
||||||
{
|
{
|
||||||
var halfInterval = AutoReconnectIntervalMs / 2;
|
var halfInterval = AutoReconnectIntervalMs / 2;
|
||||||
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
||||||
this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime);
|
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
|
||||||
if (RetryTimer != null)
|
if (RetryTimer != null)
|
||||||
{
|
{
|
||||||
RetryTimer.Stop();
|
RetryTimer.Stop();
|
||||||
RetryTimer = null;
|
RetryTimer = null;
|
||||||
}
|
}
|
||||||
RetryTimer = new Timer(rndTime) { AutoReset = false };
|
RetryTimer = new CTimer(o => Connect(), rndTime);
|
||||||
RetryTimer.Elapsed += (s, e) => Connect();
|
|
||||||
RetryTimer.Start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,18 +526,18 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
{
|
{
|
||||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||||
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||||
this.LogVerbose("Client Received:\r--------\r{0}\r--------", str);
|
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
|
||||||
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
||||||
{
|
{
|
||||||
if (SharedKeyRequired && str == "SharedKey:")
|
if (SharedKeyRequired && str == "SharedKey:")
|
||||||
{
|
{
|
||||||
this.LogVerbose("Server asking for shared key, sending");
|
Debug.Console(2, this, "Server asking for shared key, sending");
|
||||||
SendText(SharedKey + "\n");
|
SendText(SharedKey + "\n");
|
||||||
}
|
}
|
||||||
else if (SharedKeyRequired && str == "Shared Key Match")
|
else if (SharedKeyRequired && str == "Shared Key Match")
|
||||||
{
|
{
|
||||||
StopWaitForSharedKeyTimer();
|
StopWaitForSharedKeyTimer();
|
||||||
this.LogVerbose("Shared key confirmed. Ready for communication");
|
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
|
||||||
OnClientReadyForcommunications(true); // Successful key exchange
|
OnClientReadyForcommunications(true); // Successful key exchange
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -557,8 +553,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
||||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
@@ -569,19 +564,15 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
{
|
{
|
||||||
if (HeartbeatEnabled)
|
if (HeartbeatEnabled)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Starting Heartbeat");
|
Debug.Console(2, this, "Starting Heartbeat");
|
||||||
if (HeartbeatSendTimer == null)
|
if (HeartbeatSendTimer == null)
|
||||||
{
|
{
|
||||||
|
|
||||||
HeartbeatSendTimer = new Timer(HeartbeatInterval) { AutoReset = true };
|
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
||||||
HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null);
|
|
||||||
HeartbeatSendTimer.Start();
|
|
||||||
}
|
}
|
||||||
if (HeartbeatAckTimer == null)
|
if (HeartbeatAckTimer == null)
|
||||||
{
|
{
|
||||||
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
|
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
|
||||||
HeartbeatAckTimer.Start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,13 +582,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
|
|
||||||
if (HeartbeatSendTimer != null)
|
if (HeartbeatSendTimer != null)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Stoping Heartbeat Send");
|
Debug.Console(2, this, "Stoping Heartbeat Send");
|
||||||
HeartbeatSendTimer.Stop();
|
HeartbeatSendTimer.Stop();
|
||||||
HeartbeatSendTimer = null;
|
HeartbeatSendTimer = null;
|
||||||
}
|
}
|
||||||
if (HeartbeatAckTimer != null)
|
if (HeartbeatAckTimer != null)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Stoping Heartbeat Ack");
|
Debug.Console(2, this, "Stoping Heartbeat Ack");
|
||||||
HeartbeatAckTimer.Stop();
|
HeartbeatAckTimer.Stop();
|
||||||
HeartbeatAckTimer = null;
|
HeartbeatAckTimer = null;
|
||||||
}
|
}
|
||||||
@@ -606,7 +597,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
void SendHeartbeat(object notused)
|
void SendHeartbeat(object notused)
|
||||||
{
|
{
|
||||||
this.SendText(HeartbeatString);
|
this.SendText(HeartbeatString);
|
||||||
this.LogVerbose("Sending Heartbeat");
|
Debug.Console(2, this, "Sending Heartbeat");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,17 +616,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
{
|
{
|
||||||
if (HeartbeatAckTimer != null)
|
if (HeartbeatAckTimer != null)
|
||||||
{
|
{
|
||||||
HeartbeatAckTimer.Stop();
|
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
||||||
HeartbeatAckTimer.Interval = HeartbeatInterval * 2;
|
|
||||||
HeartbeatAckTimer.Start();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
|
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
|
||||||
HeartbeatAckTimer.Start();
|
|
||||||
}
|
}
|
||||||
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
|
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||||
return remainingText;
|
return remainingText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -643,8 +630,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
|
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
|
||||||
}
|
}
|
||||||
return received;
|
return received;
|
||||||
}
|
}
|
||||||
@@ -658,7 +644,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
|
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
{
|
{
|
||||||
this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||||
SendText("Heartbeat not received by server, closing connection");
|
SendText("Heartbeat not received by server, closing connection");
|
||||||
CheckClosedAndTryReconnect();
|
CheckClosedAndTryReconnect();
|
||||||
}
|
}
|
||||||
@@ -666,8 +652,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Heartbeat timeout Error on Client: {0}, {1}", Key, ex.Message);
|
ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
|
||||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,7 +669,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// General send method
|
/// SendText method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendText(string text)
|
public void SendText(string text)
|
||||||
{
|
{
|
||||||
@@ -700,21 +685,20 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
||||||
if (n <= 0)
|
if (n <= 0)
|
||||||
{
|
{
|
||||||
this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key);
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
||||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// SendBytes method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendBytes(byte[] bytes)
|
public void SendBytes(byte[] bytes)
|
||||||
{
|
{
|
||||||
@@ -727,8 +711,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error sending bytes. Error: {0}", ex.Message);
|
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
||||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -747,7 +730,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.LogVerbose("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||||
|
|
||||||
OnConnectionChange();
|
OnConnectionChange();
|
||||||
|
|
||||||
@@ -761,8 +744,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error in socket status change callback. Error: {0}", ex.Message);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
||||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -789,3 +771,5 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,13 +13,12 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Timers;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generic TCP/IP server device
|
/// Generic TCP/IP server device
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -53,7 +52,7 @@ public class GenericTcpIpServer : Device
|
|||||||
public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; }
|
public ServerHasChokedCallbackDelegate ServerHasChoked { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Delegate for ServerHasChokedCallbackDelegate
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public delegate void ServerHasChokedCallbackDelegate();
|
public delegate void ServerHasChokedCallbackDelegate();
|
||||||
|
|
||||||
@@ -62,14 +61,9 @@ public class GenericTcpIpServer : Device
|
|||||||
#region Properties/Variables
|
#region Properties/Variables
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server listen lock
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
object _serverLock = new();
|
CCriticalSection ServerCCSection = new CCriticalSection();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Broadcast lock
|
|
||||||
/// </summary>
|
|
||||||
private readonly object _broadcastLock = new();
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -80,7 +74,7 @@ public class GenericTcpIpServer : Device
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timer to operate the bandaid monitor client in a loop.
|
/// Timer to operate the bandaid monitor client in a loop.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Timer MonitorClientTimer;
|
CTimer MonitorClientTimer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
@@ -88,7 +82,7 @@ public class GenericTcpIpServer : Device
|
|||||||
int MonitorClientFailureCount;
|
int MonitorClientFailureCount;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3 by default
|
/// Gets or sets the MonitorClientMaxFailureCount
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MonitorClientMaxFailureCount { get; set; }
|
public int MonitorClientMaxFailureCount { get; set; }
|
||||||
|
|
||||||
@@ -177,7 +171,7 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Port Server should listen on
|
/// Gets or sets the Port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
@@ -210,8 +204,7 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module.
|
/// Gets or sets the SharedKey
|
||||||
/// If SharedKey changes while server is listening or clients are connected, disconnect and stop listening will be called
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SharedKey { get; set; }
|
public string SharedKey { get; set; }
|
||||||
|
|
||||||
@@ -235,7 +228,7 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Milliseconds before server expects another heartbeat. Set by property HeartbeatRequiredIntervalInSeconds which is driven from S+
|
/// Gets or sets the HeartbeatRequiredIntervalMs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int HeartbeatRequiredIntervalMs { get; set; }
|
public int HeartbeatRequiredIntervalMs { get; set; }
|
||||||
|
|
||||||
@@ -245,12 +238,12 @@ public class GenericTcpIpServer : Device
|
|||||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } }
|
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatRequiredIntervalMs = (value * 1000); } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// String to Match for heartbeat. If null or empty any string will reset heartbeat timer
|
/// Gets or sets the HeartbeatStringToMatch
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string HeartbeatStringToMatch { get; set; }
|
public string HeartbeatStringToMatch { get; set; }
|
||||||
|
|
||||||
//private timers for Heartbeats per client
|
//private timers for Heartbeats per client
|
||||||
Dictionary<uint, Timer> HeartbeatTimerDictionary = new Dictionary<uint, Timer>();
|
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||||
|
|
||||||
//flags to show the secure server is waiting for client at index to send the shared key
|
//flags to show the secure server is waiting for client at index to send the shared key
|
||||||
List<uint> WaitingForSharedKey = new List<uint>();
|
List<uint> WaitingForSharedKey = new List<uint>();
|
||||||
@@ -263,7 +256,7 @@ public class GenericTcpIpServer : Device
|
|||||||
public List<uint> ConnectedClientsIndexes = new List<uint>();
|
public List<uint> ConnectedClientsIndexes = new List<uint>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defaults to 2000
|
/// Gets or sets the BufferSize
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int BufferSize { get; set; }
|
public int BufferSize { get; set; }
|
||||||
|
|
||||||
@@ -326,7 +319,7 @@ public class GenericTcpIpServer : Device
|
|||||||
|
|
||||||
#region Methods - Server Actions
|
#region Methods - Server Actions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disconnects all clients and stops the server
|
/// KillServer method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void KillServer()
|
public void KillServer()
|
||||||
{
|
{
|
||||||
@@ -343,6 +336,9 @@ public class GenericTcpIpServer : Device
|
|||||||
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
/// Initialize Key for device using client name from SIMPL+. Called on Listen from SIMPL+
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize method
|
||||||
|
/// </summary>
|
||||||
public void Initialize(string key)
|
public void Initialize(string key)
|
||||||
{
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
@@ -371,32 +367,33 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogError("Could not initialize server with key: {0}", serverConfigObject.Key);
|
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
this.LogError("Could not initialize server with key: {0}", serverConfigObject.Key);
|
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start listening on the specified port
|
/// Listen method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Listen()
|
public void Listen()
|
||||||
{
|
{
|
||||||
lock (_serverLock)
|
ServerCCSection.Enter();
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
this.LogError("Server '{0}': Invalid port", Key);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key);
|
||||||
|
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||||
{
|
{
|
||||||
this.LogError("Server '{0}': No Shared Key set", Key);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key);
|
||||||
|
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (IsListening)
|
if (IsListening)
|
||||||
@@ -422,36 +419,39 @@ public class GenericTcpIpServer : Device
|
|||||||
ServerStopped = false;
|
ServerStopped = false;
|
||||||
myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||||
OnServerStateChange(myTcpServer.State);
|
OnServerStateChange(myTcpServer.State);
|
||||||
this.LogInformation("TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus);
|
||||||
|
|
||||||
// StartMonitorClient();
|
// StartMonitorClient();
|
||||||
|
|
||||||
|
|
||||||
|
ServerCCSection.Leave();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error with Dynamic Server: {0}", ex.Message);
|
ServerCCSection.Leave();
|
||||||
|
ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
||||||
}
|
}
|
||||||
} // end lock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stop Listening
|
/// StopListening method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StopListening()
|
public void StopListening()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.LogDebug("Stopping Listener");
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
|
||||||
if (myTcpServer != null)
|
if (myTcpServer != null)
|
||||||
{
|
{
|
||||||
myTcpServer.Stop();
|
myTcpServer.Stop();
|
||||||
this.LogDebug("Server State: {0}", myTcpServer.State);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State);
|
||||||
OnServerStateChange(myTcpServer.State);
|
OnServerStateChange(myTcpServer.State);
|
||||||
}
|
}
|
||||||
ServerStopped = true;
|
ServerStopped = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error stopping server. Error: {0}", ex.Message);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,24 +459,27 @@ public class GenericTcpIpServer : Device
|
|||||||
/// Disconnects Client
|
/// Disconnects Client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client"></param>
|
/// <param name="client"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// DisconnectClient method
|
||||||
|
/// </summary>
|
||||||
public void DisconnectClient(uint client)
|
public void DisconnectClient(uint client)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
myTcpServer.Disconnect(client);
|
myTcpServer.Disconnect(client);
|
||||||
this.LogVerbose("Disconnected client index: {0}", client);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disconnect All Clients
|
/// DisconnectAllClientsForShutdown method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DisconnectAllClientsForShutdown()
|
public void DisconnectAllClientsForShutdown()
|
||||||
{
|
{
|
||||||
this.LogInformation("Disconnecting All Clients");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients");
|
||||||
if (myTcpServer != null)
|
if (myTcpServer != null)
|
||||||
{
|
{
|
||||||
myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange;
|
myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange;
|
||||||
@@ -488,17 +491,17 @@ public class GenericTcpIpServer : Device
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
myTcpServer.Disconnect(i);
|
myTcpServer.Disconnect(i);
|
||||||
this.LogVerbose("Disconnected client index: {0}", i);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.LogVerbose("Server Status: {0}", myTcpServer.ServerSocketStatus);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", myTcpServer.ServerSocketStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.LogInformation("Disconnected All Clients");
|
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
|
||||||
ConnectedClientsIndexes.Clear();
|
ConnectedClientsIndexes.Clear();
|
||||||
|
|
||||||
if (!ProgramIsStopping)
|
if (!ProgramIsStopping)
|
||||||
@@ -514,10 +517,13 @@ public class GenericTcpIpServer : Device
|
|||||||
/// Broadcast text from server to all connected clients
|
/// Broadcast text from server to all connected clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// BroadcastText method
|
||||||
|
/// </summary>
|
||||||
public void BroadcastText(string text)
|
public void BroadcastText(string text)
|
||||||
{
|
{
|
||||||
lock (_broadcastLock)
|
CCriticalSection CCBroadcast = new CCriticalSection();
|
||||||
{
|
CCBroadcast.Enter();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (ConnectedClientsIndexes.Count > 0)
|
if (ConnectedClientsIndexes.Count > 0)
|
||||||
@@ -533,12 +539,13 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CCBroadcast.Leave();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
CCBroadcast.Leave();
|
||||||
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
} // end lock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -546,6 +553,9 @@ public class GenericTcpIpServer : Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SendTextToClient method
|
||||||
|
/// </summary>
|
||||||
public void SendTextToClient(string text, uint clientIndex)
|
public void SendTextToClient(string text, uint clientIndex)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -559,7 +569,7 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,19 +587,13 @@ public class GenericTcpIpServer : Device
|
|||||||
if (noDelimiter.Contains(HeartbeatStringToMatch))
|
if (noDelimiter.Contains(HeartbeatStringToMatch))
|
||||||
{
|
{
|
||||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
{
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary[clientIndex].Stop();
|
|
||||||
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
|
|
||||||
HeartbeatTimerDictionary[clientIndex].Start();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
heartbeatTimer.Start();
|
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
|
||||||
}
|
}
|
||||||
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
||||||
// Return Heartbeat
|
// Return Heartbeat
|
||||||
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
||||||
return remainingText;
|
return remainingText;
|
||||||
@@ -598,25 +602,19 @@ public class GenericTcpIpServer : Device
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
{
|
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||||
HeartbeatTimerDictionary[clientIndex].Stop();
|
|
||||||
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
|
|
||||||
HeartbeatTimerDictionary[clientIndex].Start();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||||
heartbeatTimer.Start();
|
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
|
||||||
}
|
}
|
||||||
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
|
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
return received;
|
return received;
|
||||||
}
|
}
|
||||||
@@ -626,13 +624,16 @@ public class GenericTcpIpServer : Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clientIndex"></param>
|
/// <param name="clientIndex"></param>
|
||||||
/// <returns>IP address of the client</returns>
|
/// <returns>IP address of the client</returns>
|
||||||
|
/// <summary>
|
||||||
|
/// GetClientIPAddress method
|
||||||
|
/// </summary>
|
||||||
public string GetClientIPAddress(uint clientIndex)
|
public string GetClientIPAddress(uint clientIndex)
|
||||||
{
|
{
|
||||||
this.LogVerbose("GetClientIPAddress Index: {0}", clientIndex);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex);
|
||||||
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
||||||
{
|
{
|
||||||
var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
this.LogVerbose("GetClientIPAddress IPAddreess: {0}", ipa);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa);
|
||||||
return ipa;
|
return ipa;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -655,13 +656,14 @@ public class GenericTcpIpServer : Device
|
|||||||
clientIndex = (uint)o;
|
clientIndex = (uint)o;
|
||||||
address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||||
|
|
||||||
this.LogWarning("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
||||||
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
||||||
|
|
||||||
if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
||||||
|
|
||||||
var discoResult = myTcpServer.Disconnect(clientIndex);
|
var discoResult = myTcpServer.Disconnect(clientIndex);
|
||||||
|
//Debug.Console(1, this, "{0}", discoResult);
|
||||||
|
|
||||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
{
|
{
|
||||||
@@ -672,8 +674,7 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message);
|
ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key);
|
||||||
this.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,7 +692,7 @@ public class GenericTcpIpServer : Device
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
this.LogInformation("SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
{
|
{
|
||||||
if (ConnectedClientsIndexes.Contains(clientIndex))
|
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||||
@@ -710,7 +711,7 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
|
||||||
}
|
}
|
||||||
onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||||
}
|
}
|
||||||
@@ -727,7 +728,7 @@ public class GenericTcpIpServer : Device
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.LogDebug("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
||||||
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
||||||
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||||
if (clientIndex != 0)
|
if (clientIndex != 0)
|
||||||
@@ -747,21 +748,17 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
||||||
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
||||||
this.LogDebug("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogDebug("Client at index {0} is ready for communications", clientIndex);
|
|
||||||
OnServerClientReadyForCommunications(clientIndex);
|
OnServerClientReadyForCommunications(clientIndex);
|
||||||
}
|
}
|
||||||
if (HeartbeatRequired)
|
if (HeartbeatRequired)
|
||||||
{
|
{
|
||||||
if (!HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
if (!HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||||
{
|
{
|
||||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs));
|
||||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
|
||||||
heartbeatTimer.Start();
|
|
||||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,7 +767,7 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogError("Client attempt faulty.");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty.");
|
||||||
if (!ServerStopped)
|
if (!ServerStopped)
|
||||||
{
|
{
|
||||||
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||||
@@ -780,15 +777,15 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex.Message);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
|
||||||
this.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
|
||||||
}
|
}
|
||||||
|
//Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))",
|
||||||
// server.State,
|
// server.State,
|
||||||
// MaxClients,
|
// MaxClients,
|
||||||
// ServerStopped);
|
// ServerStopped);
|
||||||
if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||||
{
|
{
|
||||||
this.LogDebug("Waiting for next connection");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Waiting for next connection");
|
||||||
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -819,7 +816,7 @@ public class GenericTcpIpServer : Device
|
|||||||
if (received != SharedKey)
|
if (received != SharedKey)
|
||||||
{
|
{
|
||||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
||||||
this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
||||||
myTCPServer.SendData(clientIndex, b, b.Length);
|
myTCPServer.SendData(clientIndex, b, b.Length);
|
||||||
myTCPServer.Disconnect(clientIndex);
|
myTCPServer.Disconnect(clientIndex);
|
||||||
return;
|
return;
|
||||||
@@ -829,7 +826,7 @@ public class GenericTcpIpServer : Device
|
|||||||
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
||||||
myTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
myTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
||||||
OnServerClientReadyForCommunications(clientIndex);
|
OnServerClientReadyForCommunications(clientIndex);
|
||||||
this.LogDebug("Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
||||||
@@ -837,7 +834,7 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex);
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
|
||||||
}
|
}
|
||||||
if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||||
myTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
|
myTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
|
||||||
@@ -918,7 +915,7 @@ public class GenericTcpIpServer : Device
|
|||||||
if (MonitorClient != null)
|
if (MonitorClient != null)
|
||||||
MonitorClient.Disconnect();
|
MonitorClient.Disconnect();
|
||||||
|
|
||||||
this.LogInformation("Program stopping. Closing server");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server");
|
||||||
KillServer();
|
KillServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -944,9 +941,7 @@ public class GenericTcpIpServer : Device
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MonitorClientTimer = new Timer(60000) { AutoReset = false };
|
MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000);
|
||||||
MonitorClientTimer.Elapsed += (s, e) => RunMonitorClient();
|
|
||||||
MonitorClientTimer.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -961,7 +956,7 @@ public class GenericTcpIpServer : Device
|
|||||||
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
||||||
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
||||||
|
|
||||||
this.LogDebug("Starting monitor check");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check");
|
||||||
|
|
||||||
MonitorClient.Connect();
|
MonitorClient.Connect();
|
||||||
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
||||||
@@ -988,7 +983,7 @@ public class GenericTcpIpServer : Device
|
|||||||
{
|
{
|
||||||
if (args.IsReady)
|
if (args.IsReady)
|
||||||
{
|
{
|
||||||
this.LogInformation("Monitor client connection success. Disconnecting in 2s");
|
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s");
|
||||||
MonitorClientTimer.Stop();
|
MonitorClientTimer.Stop();
|
||||||
MonitorClientTimer = null;
|
MonitorClientTimer = null;
|
||||||
MonitorClientFailureCount = 0;
|
MonitorClientFailureCount = 0;
|
||||||
@@ -1009,13 +1004,13 @@ public class GenericTcpIpServer : Device
|
|||||||
StopMonitorClient();
|
StopMonitorClient();
|
||||||
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
||||||
{
|
{
|
||||||
this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}",
|
Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
|
||||||
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
||||||
StartMonitorClient();
|
StartMonitorClient();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogError(
|
Debug.Console(2, this, Debug.ErrorLogLevel.Error,
|
||||||
"\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************",
|
"\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************",
|
||||||
MonitorClientMaxFailureCount);
|
MonitorClientMaxFailureCount);
|
||||||
|
|
||||||
@@ -1027,3 +1022,4 @@ public class GenericTcpIpServer : Device
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
|
|
||||||
extern alias NewtonsoftJson;
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
using Newtonsoft.Json;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
using Required = NewtonsoftJson::Newtonsoft.Json.Required;
|
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generic UDP Server device
|
/// Generic UDP Server device
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -60,7 +57,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a GenericUdpReceiveTextExtraArgs
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort UStatus
|
public ushort UStatus
|
||||||
{
|
{
|
||||||
@@ -134,14 +131,14 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="address"></param>
|
/// <param name="address"></param>
|
||||||
/// <param name="port"></param>
|
/// <param name="port"></param>
|
||||||
/// <param name="buffefSize"></param>
|
/// <param name="bufferSize"></param>
|
||||||
public GenericUdpServer(string key, string address, int port, int buffefSize)
|
public GenericUdpServer(string key, string address, int port, int bufferSize)
|
||||||
: base(key)
|
: base(key)
|
||||||
{
|
{
|
||||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||||
Hostname = address;
|
Hostname = address;
|
||||||
Port = port;
|
Port = port;
|
||||||
BufferSize = buffefSize;
|
BufferSize = bufferSize;
|
||||||
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||||
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
|
||||||
@@ -153,6 +150,9 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="address"></param>
|
/// <param name="address"></param>
|
||||||
/// <param name="port"></param>
|
/// <param name="port"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize method
|
||||||
|
/// </summary>
|
||||||
public void Initialize(string key, string address, ushort port)
|
public void Initialize(string key, string address, ushort port)
|
||||||
{
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
@@ -183,36 +183,50 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
if (programEventType != eProgramStatusEventType.Stopping)
|
if (programEventType != eProgramStatusEventType.Stopping)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.LogInformation("Program stopping. Disabling Server");
|
Debug.Console(1, this, "Program stopping. Disabling Server");
|
||||||
Disconnect();
|
Disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables the UDP Server
|
/// Connect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Connect()
|
public void Connect()
|
||||||
{
|
{
|
||||||
if (Server == null)
|
if (Server == null)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var address = IPAddress.Parse(Hostname);
|
||||||
|
|
||||||
|
Server = new UDPServer(address, Port, BufferSize);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.LogError("Error parsing IP Address '{ipAddress}': message: {message}", Hostname, ex.Message);
|
||||||
|
this.LogInformation("Creating UDPServer with default buffersize");
|
||||||
|
|
||||||
Server = new UDPServer();
|
Server = new UDPServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Hostname))
|
if (string.IsNullOrEmpty(Hostname))
|
||||||
{
|
{
|
||||||
this.LogWarning("GenericUdpServer '{0}': No address set", Key);
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Port < 1 || Port > 65535)
|
if (Port < 1 || Port > 65535)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
this.LogWarning("GenericUdpServer '{0}': Invalid port", Key);
|
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var status = Server.EnableUDPServer(Hostname, Port);
|
var status = Server.EnableUDPServer(Hostname, Port);
|
||||||
|
|
||||||
this.LogVerbose("SocketErrorCode: {0}", status);
|
Debug.Console(2, this, "SocketErrorCode: {0}", status);
|
||||||
if (status == SocketErrorCodes.SOCKET_OK)
|
if (status == SocketErrorCodes.SOCKET_OK)
|
||||||
IsConnected = true;
|
IsConnected = true;
|
||||||
|
|
||||||
@@ -225,7 +239,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disabled the UDP Server
|
/// Disconnect method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Disconnect()
|
public void Disconnect()
|
||||||
{
|
{
|
||||||
@@ -247,7 +261,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
/// <param name="numBytes"></param>
|
/// <param name="numBytes"></param>
|
||||||
void Receive(UDPServer server, int numBytes)
|
void Receive(UDPServer server, int numBytes)
|
||||||
{
|
{
|
||||||
this.LogVerbose("Received {0} bytes", numBytes);
|
Debug.Console(2, this, "Received {0} bytes", numBytes);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -263,13 +277,13 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
if (dataRecivedExtra != null)
|
if (dataRecivedExtra != null)
|
||||||
dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes));
|
dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes));
|
||||||
|
|
||||||
this.LogVerbose("Bytes: {0}", bytes.ToString());
|
Debug.Console(2, this, "Bytes: {0}", bytes.ToString());
|
||||||
var bytesHandler = BytesReceived;
|
var bytesHandler = BytesReceived;
|
||||||
if (bytesHandler != null)
|
if (bytesHandler != null)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||||
{
|
{
|
||||||
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||||
}
|
}
|
||||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||||
}
|
}
|
||||||
@@ -277,7 +291,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
if (textHandler != null)
|
if (textHandler != null)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||||
this.LogInformation("Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,6 +309,9 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
/// General send method
|
/// General send method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SendText method
|
||||||
|
/// </summary>
|
||||||
public void SendText(string text)
|
public void SendText(string text)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
@@ -302,7 +319,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
if (IsConnected && Server != null)
|
if (IsConnected && Server != null)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||||
this.LogVerbose("Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||||
|
|
||||||
Server.SendData(bytes, bytes.Length);
|
Server.SendData(bytes, bytes.Length);
|
||||||
}
|
}
|
||||||
@@ -312,10 +329,13 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SendBytes method
|
||||||
|
/// </summary>
|
||||||
public void SendBytes(byte[] bytes)
|
public void SendBytes(byte[] bytes)
|
||||||
{
|
{
|
||||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||||
|
|
||||||
if (IsConnected && Server != null)
|
if (IsConnected && Server != null)
|
||||||
Server.SendData(bytes, bytes.Length);
|
Server.SendData(bytes, bytes.Length);
|
||||||
@@ -324,7 +344,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Represents a GenericUdpReceiveTextExtraArgs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
public class GenericUdpReceiveTextExtraArgs : EventArgs
|
||||||
{
|
{
|
||||||
@@ -396,3 +416,4 @@ public class UdpServerPropertiesConfig
|
|||||||
BufferSize = 32768;
|
BufferSize = 32768;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Extension methods for stream debugging
|
|
||||||
/// </summary>
|
|
||||||
public static class StreamDebuggingExtensions
|
|
||||||
{
|
|
||||||
private static readonly string app = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? $"App {InitialParametersClass.ApplicationNumber}" : $"{InitialParametersClass.RoomId}";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Print the sent bytes to the console
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="comms">comms device</param>
|
|
||||||
/// <param name="bytes">bytes to print</param>
|
|
||||||
public static void PrintSentBytes(this IStreamDebugging comms, byte[] bytes)
|
|
||||||
{
|
|
||||||
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
|
|
||||||
|
|
||||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
|
||||||
|
|
||||||
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Print the received bytes to the console
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="comms">comms device</param>
|
|
||||||
/// <param name="bytes">bytes to print</param>
|
|
||||||
public static void PrintReceivedBytes(this IStreamDebugging comms, byte[] bytes)
|
|
||||||
{
|
|
||||||
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
|
|
||||||
|
|
||||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
|
||||||
|
|
||||||
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Print the sent text to the console
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="comms">comms device</param>
|
|
||||||
/// <param name="text">text to print</param>
|
|
||||||
public static void PrintSentText(this IStreamDebugging comms, string text)
|
|
||||||
{
|
|
||||||
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
|
|
||||||
|
|
||||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
|
||||||
|
|
||||||
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending Text: '{ComTextHelper.GetDebugText(text)}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Print the received text to the console
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="comms">comms device</param>
|
|
||||||
/// <param name="text">text to print</param>
|
|
||||||
public static void PrintReceivedText(this IStreamDebugging comms, string text)
|
|
||||||
{
|
|
||||||
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
|
|
||||||
|
|
||||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
|
||||||
|
|
||||||
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received Text: '{ComTextHelper.GetDebugText(text)}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
extern alias NewtonsoftJson;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat
|
/// Represents a TcpClientConfigObject
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TcpClientConfigObject
|
public class TcpClientConfigObject
|
||||||
{
|
{
|
||||||
@@ -58,3 +56,4 @@ public class TcpClientConfigObject
|
|||||||
[JsonProperty("receiveQueueSize")]
|
[JsonProperty("receiveQueueSize")]
|
||||||
public int ReceiveQueueSize { get; set; }
|
public int ReceiveQueueSize { get; set; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
|
/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -57,3 +57,4 @@ public class TcpServerConfigObject
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int ReceiveQueueSize { get; set; }
|
public int ReceiveQueueSize { get; set; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Crestron Control Methods for a comm object
|
/// Crestron Control Methods for a comm object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -76,11 +76,12 @@ public enum eControlMethod
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
SecureTcpIp,
|
SecureTcpIp,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Crestron COM bridge
|
/// Used when comms needs to be handled in SIMPL and bridged opposite the normal direction
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ComBridge,
|
ComBridge,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Crestron Infinet EX device
|
/// InfinetEX control
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InfinetEx
|
InfinetEx
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The available settings for stream debugging data format types
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
|
||||||
public enum eStreamDebuggingDataTypeSettings
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Debug data in byte format
|
|
||||||
/// </summary>
|
|
||||||
Bytes = 0,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug data in text format
|
|
||||||
/// </summary>
|
|
||||||
Text = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// Debug data in both byte and text formats
|
|
||||||
/// </summary>
|
|
||||||
Both = Bytes | Text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,14 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronSockets;
|
using Crestron.SimplSharp.CrestronSockets;
|
||||||
using JsonConverter = NewtonsoftJson::Newtonsoft.Json.JsonConverterAttribute;
|
using System.Text.RegularExpressions;
|
||||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
using Newtonsoft.Json;
|
||||||
using StringEnumConverter = NewtonsoftJson::Newtonsoft.Json.Converters.StringEnumConverter;
|
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An incoming communication stream
|
/// An incoming communication stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -43,7 +39,7 @@ public interface ICommunicationReceiver : IKeyed
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extends <see cref="ICommunicationReceiver"/> with methods for sending text and bytes to a device.
|
/// Defines the contract for IBasicCommunication
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IBasicCommunication : ICommunicationReceiver
|
public interface IBasicCommunication : ICommunicationReceiver
|
||||||
{
|
{
|
||||||
@@ -71,7 +67,7 @@ public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, I
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a device with stream debugging capablities
|
/// Represents a device with stream debugging capablities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IStreamDebugging : IKeyed
|
public interface IStreamDebugging
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Object to enable stream debugging
|
/// Object to enable stream debugging
|
||||||
@@ -95,7 +91,7 @@ public interface IStreamDebugging : IKeyed
|
|||||||
/// The current socket status of the client
|
/// The current socket status of the client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("clientStatus")]
|
[JsonProperty("clientStatus")]
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||||
SocketStatus ClientStatus { get; }
|
SocketStatus ClientStatus { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,17 +143,17 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
|
|||||||
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event args for bytes received from a communication method
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The bytes received
|
/// Gets or sets the Bytes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] Bytes { get; private set; }
|
public byte[] Bytes { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes"></param>
|
/// <param name="bytes"></param>
|
||||||
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
|
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
|
||||||
@@ -172,21 +168,20 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event args for text received
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GenericCommMethodReceiveTextArgs : EventArgs
|
public class GenericCommMethodReceiveTextArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The text received
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Text { get; private set; }
|
public string Text { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The delimiter used to determine the end of a message, if applicable
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Delimiter { get; private set; }
|
public string Delimiter { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
public GenericCommMethodReceiveTextArgs(string text)
|
public GenericCommMethodReceiveTextArgs(string text)
|
||||||
@@ -214,7 +209,7 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper class to get escaped text for debugging communication streams
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ComTextHelper
|
public class ComTextHelper
|
||||||
{
|
{
|
||||||
@@ -233,6 +228,9 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// GetEscapedText method
|
||||||
|
/// </summary>
|
||||||
public static string GetEscapedText(string text)
|
public static string GetEscapedText(string text)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||||
@@ -244,8 +242,12 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text"></param>
|
/// <param name="text"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// GetDebugText method
|
||||||
|
/// </summary>
|
||||||
public static string GetDebugText(string text)
|
public static string GetDebugText(string text)
|
||||||
{
|
{
|
||||||
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
|
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
using Newtonsoft.Json;
|
||||||
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
|
using Newtonsoft.Json.Linq;
|
||||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
|
||||||
using JToken = NewtonsoftJson::Newtonsoft.Json.Linq.JToken;
|
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core.Config;
|
namespace PepperDash.Core.Config
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads a Portal formatted config file
|
/// Reads a Portal formatted config file
|
||||||
@@ -58,12 +56,12 @@ namespace PepperDash.Core.Config;
|
|||||||
var merged = MergeConfigs(jsonObj);
|
var merged = MergeConfigs(jsonObj);
|
||||||
if (jsonObj[systemUrl] != null)
|
if (jsonObj[systemUrl] != null)
|
||||||
{
|
{
|
||||||
merged[systemUrl] = (string)jsonObj[systemUrl];
|
merged[systemUrl] = jsonObj[systemUrl].Value<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jsonObj[templateUrl] != null)
|
if (jsonObj[templateUrl] != null)
|
||||||
{
|
{
|
||||||
merged[templateUrl] = (string)jsonObj[templateUrl];
|
merged[templateUrl] = jsonObj[templateUrl].Value<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonObj = merged;
|
jsonObj = merged;
|
||||||
@@ -124,37 +122,38 @@ namespace PepperDash.Core.Config;
|
|||||||
Merge(template[destinationLists], system[destinationLists], destinationLists));
|
Merge(template[destinationLists], system[destinationLists], destinationLists));
|
||||||
|
|
||||||
|
|
||||||
if (system["cameraLists"] == null)
|
if (system[cameraLists] == null)
|
||||||
merged.Add("cameraLists", template["cameraLists"]);
|
merged.Add(cameraLists, template[cameraLists]);
|
||||||
else
|
else
|
||||||
merged.Add("cameraLists", Merge(template["cameraLists"], system["cameraLists"], "cameraLists"));
|
merged.Add(cameraLists, Merge(template[cameraLists], system[cameraLists], cameraLists));
|
||||||
|
|
||||||
if (system["audioControlPointLists"] == null)
|
if (system[audioControlPointLists] == null)
|
||||||
merged.Add("audioControlPointLists", template["audioControlPointLists"]);
|
merged.Add(audioControlPointLists, template[audioControlPointLists]);
|
||||||
else
|
else
|
||||||
merged.Add("audioControlPointLists",
|
merged.Add(audioControlPointLists,
|
||||||
Merge(template["audioControlPointLists"], system["audioControlPointLists"], "audioControlPointLists"));
|
Merge(template[audioControlPointLists], system[audioControlPointLists], audioControlPointLists));
|
||||||
|
|
||||||
|
|
||||||
// Template tie lines take precedence. Config tool doesn't do them at system
|
// Template tie lines take precedence. Config tool doesn't do them at system
|
||||||
// level anyway...
|
// level anyway...
|
||||||
if (template["tieLines"] != null)
|
if (template[tieLines] != null)
|
||||||
merged.Add("tieLines", template["tieLines"]);
|
merged.Add(tieLines, template[tieLines]);
|
||||||
else if (system["tieLines"] != null)
|
else if (system[tieLines] != null)
|
||||||
merged.Add("tieLines", system["tieLines"]);
|
merged.Add(tieLines, system[tieLines]);
|
||||||
else
|
else
|
||||||
merged.Add(tieLines, new JArray());
|
merged.Add(tieLines, new JArray());
|
||||||
|
|
||||||
if (template["joinMaps"] != null)
|
if (template[joinMaps] != null)
|
||||||
merged.Add("joinMaps", template["joinMaps"]);
|
merged.Add(joinMaps, template[joinMaps]);
|
||||||
else
|
else
|
||||||
merged.Add("joinMaps", new JObject());
|
merged.Add(joinMaps, new JObject());
|
||||||
|
|
||||||
if (system[global] != null)
|
if (system[global] != null)
|
||||||
merged.Add(global, Merge(template[global], system[global], global));
|
merged.Add(global, Merge(template[global], system[global], global));
|
||||||
else
|
else
|
||||||
merged.Add(global, template[global]);
|
merged.Add(global, template[global]);
|
||||||
|
|
||||||
|
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
|
||||||
return merged;
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,3 +254,4 @@ namespace PepperDash.Core.Config;
|
|||||||
return o1;
|
return o1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -4,18 +4,28 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a EncodingHelper
|
||||||
|
/// </summary>
|
||||||
public class EncodingHelper
|
public class EncodingHelper
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ConvertUtf8ToAscii method
|
||||||
|
/// </summary>
|
||||||
public static string ConvertUtf8ToAscii(string utf8String)
|
public static string ConvertUtf8ToAscii(string utf8String)
|
||||||
{
|
{
|
||||||
return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ConvertUtf8ToUtf16 method
|
||||||
|
/// </summary>
|
||||||
public static string ConvertUtf8ToUtf16(string utf8String)
|
public static string ConvertUtf8ToUtf16(string utf8String)
|
||||||
{
|
{
|
||||||
return Encoding.Unicode.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
return Encoding.Unicode.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,20 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
using Newtonsoft.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique key interface to require a unique key for the class
|
/// Unique key interface to require a unique key for the class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IKeyed
|
public interface IKeyed
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the unique key associated with the object.
|
/// Unique Key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("key")]
|
[JsonProperty("key")]
|
||||||
string Key { get; }
|
string Key { get; }
|
||||||
@@ -28,8 +26,10 @@ namespace PepperDash.Core;
|
|||||||
public interface IKeyName : IKeyed
|
public interface IKeyName : IKeyed
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name associated with the current object.
|
/// Isn't it obvious :)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
//*********************************************************************************************************
|
//*********************************************************************************************************
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Device
|
/// Represents a Device
|
||||||
@@ -188,8 +188,12 @@ public class Device : IKeyName
|
|||||||
/// <remarks>The returned string is formatted as "{Key} - {Name}". If the <c>Name</c> property is
|
/// <remarks>The returned string is formatted as "{Key} - {Name}". If the <c>Name</c> property is
|
||||||
/// null or empty, "---" is used in place of the name.</remarks>
|
/// null or empty, "---" is used in place of the name.</remarks>
|
||||||
/// <returns>A string that represents the object, containing the key and name in the format "{Key} - {Name}".</returns>
|
/// <returns>A string that represents the object, containing the key and name in the format "{Key} - {Name}".</returns>
|
||||||
|
/// <summary>
|
||||||
|
/// ToString method
|
||||||
|
/// </summary>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name);
|
return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
extern alias NewtonsoftJson;
|
using Crestron.SimplSharp;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an EthernetHelper.
|
/// Represents a EthernetHelper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EthernetHelper
|
public class EthernetHelper
|
||||||
{
|
{
|
||||||
@@ -116,3 +114,4 @@ namespace PepperDash.Core;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bool change event args
|
/// Bool change event args
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -169,3 +169,4 @@ namespace PepperDash.Core;
|
|||||||
Index = index;
|
Index = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.GenericRESTfulCommunications
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constants
|
||||||
|
/// </summary>
|
||||||
|
public class GenericRESTfulConstants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic boolean change
|
||||||
|
/// </summary>
|
||||||
|
public const ushort BoolValueChange = 1;
|
||||||
|
/// <summary>
|
||||||
|
/// Generic Ushort change
|
||||||
|
/// </summary>
|
||||||
|
public const ushort UshrtValueChange = 101;
|
||||||
|
/// <summary>
|
||||||
|
/// Response Code Ushort change
|
||||||
|
/// </summary>
|
||||||
|
public const ushort ResponseCodeChange = 102;
|
||||||
|
/// <summary>
|
||||||
|
/// Generic String chagne
|
||||||
|
/// </summary>
|
||||||
|
public const ushort StringValueChange = 201;
|
||||||
|
/// <summary>
|
||||||
|
/// Response string change
|
||||||
|
/// </summary>
|
||||||
|
public const ushort ResponseStringChange = 202;
|
||||||
|
/// <summary>
|
||||||
|
/// Error string change
|
||||||
|
/// </summary>
|
||||||
|
public const ushort ErrorStringChange = 203;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using Crestron.SimplSharp.Net.Http;
|
||||||
|
using Crestron.SimplSharp.Net.Https;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.GenericRESTfulCommunications
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic RESTful communication class
|
||||||
|
/// </summary>
|
||||||
|
public class GenericRESTfulClient
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Boolean event handler
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
||||||
|
/// <summary>
|
||||||
|
/// Ushort event handler
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
||||||
|
/// <summary>
|
||||||
|
/// String event handler
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<StringChangeEventArgs> StringChange;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public GenericRESTfulClient()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generic RESTful submit request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <param name="requestType"></param>
|
||||||
|
/// <param name="username"></param>
|
||||||
|
/// <param name="password"></param>
|
||||||
|
/// <param name="contentType"></param>
|
||||||
|
public void SubmitRequest(string url, ushort port, ushort requestType, string contentType, string username, string password)
|
||||||
|
{
|
||||||
|
if (url.StartsWith("https:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
SubmitRequestHttps(url, port, requestType, contentType, username, password);
|
||||||
|
}
|
||||||
|
else if (url.StartsWith("http:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
SubmitRequestHttp(url, port, requestType, contentType, username, password);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnStringChange(string.Format("Invalid URL {0}", url), 0, GenericRESTfulConstants.ErrorStringChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private HTTP submit request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <param name="requestType"></param>
|
||||||
|
/// <param name="contentType"></param>
|
||||||
|
/// <param name="username"></param>
|
||||||
|
/// <param name="password"></param>
|
||||||
|
private void SubmitRequestHttp(string url, ushort port, ushort requestType, string contentType, string username, string password)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpClient client = new HttpClient();
|
||||||
|
HttpClientRequest request = new HttpClientRequest();
|
||||||
|
HttpClientResponse response;
|
||||||
|
|
||||||
|
client.KeepAlive = false;
|
||||||
|
|
||||||
|
if(port >= 1 || port <= 65535)
|
||||||
|
client.Port = port;
|
||||||
|
else
|
||||||
|
client.Port = 80;
|
||||||
|
|
||||||
|
var authorization = "";
|
||||||
|
if (!string.IsNullOrEmpty(username))
|
||||||
|
authorization = EncodeBase64(username, password);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(authorization))
|
||||||
|
request.Header.SetHeaderValue("Authorization", authorization);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(contentType))
|
||||||
|
request.Header.ContentType = contentType;
|
||||||
|
|
||||||
|
request.Url.Parse(url);
|
||||||
|
request.RequestType = (Crestron.SimplSharp.Net.Http.RequestType)requestType;
|
||||||
|
|
||||||
|
response = client.Dispatch(request);
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine(string.Format("SubmitRequestHttp Response[{0}]: {1}", response.Code, response.ContentString.ToString()));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(response.ContentString.ToString()))
|
||||||
|
OnStringChange(response.ContentString.ToString(), 0, GenericRESTfulConstants.ResponseStringChange);
|
||||||
|
|
||||||
|
if (response.Code > 0)
|
||||||
|
OnUshrtChange((ushort)response.Code, 0, GenericRESTfulConstants.ResponseCodeChange);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//var msg = string.Format("SubmitRequestHttp({0}, {1}, {2}) failed:{3}", url, port, requestType, e.Message);
|
||||||
|
//CrestronConsole.PrintLine(msg);
|
||||||
|
//ErrorLog.Error(msg);
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine(e.Message);
|
||||||
|
OnStringChange(e.Message, 0, GenericRESTfulConstants.ErrorStringChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private HTTPS submit request
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url"></param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <param name="requestType"></param>
|
||||||
|
/// <param name="contentType"></param>
|
||||||
|
/// <param name="username"></param>
|
||||||
|
/// <param name="password"></param>
|
||||||
|
private void SubmitRequestHttps(string url, ushort port, ushort requestType, string contentType, string username, string password)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpsClient client = new HttpsClient();
|
||||||
|
HttpsClientRequest request = new HttpsClientRequest();
|
||||||
|
HttpsClientResponse response;
|
||||||
|
|
||||||
|
client.KeepAlive = false;
|
||||||
|
client.HostVerification = false;
|
||||||
|
client.PeerVerification = false;
|
||||||
|
|
||||||
|
var authorization = "";
|
||||||
|
if (!string.IsNullOrEmpty(username))
|
||||||
|
authorization = EncodeBase64(username, password);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(authorization))
|
||||||
|
request.Header.SetHeaderValue("Authorization", authorization);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(contentType))
|
||||||
|
request.Header.ContentType = contentType;
|
||||||
|
|
||||||
|
request.Url.Parse(url);
|
||||||
|
request.RequestType = (Crestron.SimplSharp.Net.Https.RequestType)requestType;
|
||||||
|
|
||||||
|
response = client.Dispatch(request);
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine(string.Format("SubmitRequestHttp Response[{0}]: {1}", response.Code, response.ContentString.ToString()));
|
||||||
|
|
||||||
|
if(!string.IsNullOrEmpty(response.ContentString.ToString()))
|
||||||
|
OnStringChange(response.ContentString.ToString(), 0, GenericRESTfulConstants.ResponseStringChange);
|
||||||
|
|
||||||
|
if(response.Code > 0)
|
||||||
|
OnUshrtChange((ushort)response.Code, 0, GenericRESTfulConstants.ResponseCodeChange);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//var msg = string.Format("SubmitRequestHttps({0}, {1}, {2}, {3}, {4}) failed:{5}", url, port, requestType, username, password, e.Message);
|
||||||
|
//CrestronConsole.PrintLine(msg);
|
||||||
|
//ErrorLog.Error(msg);
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine(e.Message);
|
||||||
|
OnStringChange(e.Message, 0, GenericRESTfulConstants.ErrorStringChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private method to encode username and password to Base64 string
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username"></param>
|
||||||
|
/// <param name="password"></param>
|
||||||
|
/// <returns>authorization</returns>
|
||||||
|
private string EncodeBase64(string username, string password)
|
||||||
|
{
|
||||||
|
var authorization = "";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(username))
|
||||||
|
{
|
||||||
|
string base64String = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(string.Format("{0}:{1}", username, password)));
|
||||||
|
authorization = string.Format("Basic {0}", base64String);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
var msg = string.Format("EncodeBase64({0}, {1}) failed:\r{2}", username, password, e);
|
||||||
|
CrestronConsole.PrintLine(msg);
|
||||||
|
ErrorLog.Error(msg);
|
||||||
|
return "" ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorization;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Protected method to handle boolean change events
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state"></param>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
protected void OnBoolChange(bool state, ushort index, ushort type)
|
||||||
|
{
|
||||||
|
var handler = BoolChange;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
var args = new BoolChangeEventArgs(state, type);
|
||||||
|
args.Index = index;
|
||||||
|
BoolChange(this, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Protected mehtod to handle ushort change events
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
protected void OnUshrtChange(ushort value, ushort index, ushort type)
|
||||||
|
{
|
||||||
|
var handler = UshrtChange;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
var args = new UshrtChangeEventArgs(value, type);
|
||||||
|
args.Index = index;
|
||||||
|
UshrtChange(this, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Protected method to handle string change events
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
protected void OnStringChange(string value, ushort index, ushort type)
|
||||||
|
{
|
||||||
|
var handler = StringChange;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
var args = new StringChangeEventArgs(value, type);
|
||||||
|
args.Index = index;
|
||||||
|
StringChange(this, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonStandardObjects;
|
namespace PepperDash.Core.JsonStandardObjects
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constants for simpl modules
|
/// Constants for simpl modules
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -74,3 +74,4 @@ namespace PepperDash.Core.JsonStandardObjects;
|
|||||||
Index = index;
|
Index = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using Crestron.SimplSharp;
|
|||||||
using PepperDash.Core.JsonToSimpl;
|
using PepperDash.Core.JsonToSimpl;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonStandardObjects;
|
namespace PepperDash.Core.JsonStandardObjects
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Device class
|
/// Device class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -183,3 +183,4 @@ namespace PepperDash.Core.JsonStandardObjects;
|
|||||||
|
|
||||||
#endregion EventHandler Helpers
|
#endregion EventHandler Helpers
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonStandardObjects;
|
namespace PepperDash.Core.JsonStandardObjects
|
||||||
|
{
|
||||||
/*
|
/*
|
||||||
Convert JSON snippt to C#: http://json2csharp.com/#
|
Convert JSON snippt to C#: http://json2csharp.com/#
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ namespace PepperDash.Core.JsonStandardObjects;
|
|||||||
public class ComParamsConfig
|
public class ComParamsConfig
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the baudRate
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int baudRate { get; set; }
|
public int baudRate { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -87,11 +87,11 @@ namespace PepperDash.Core.JsonStandardObjects;
|
|||||||
|
|
||||||
// convert properties for simpl
|
// convert properties for simpl
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the simplBaudRate
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplBaudRate { get { return Convert.ToUInt16(baudRate); } }
|
public ushort simplBaudRate { get { return Convert.ToUInt16(baudRate); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the simplDataBits
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplDataBits { get { return Convert.ToUInt16(dataBits); } }
|
public ushort simplDataBits { get { return Convert.ToUInt16(dataBits); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -144,11 +144,11 @@ namespace PepperDash.Core.JsonStandardObjects;
|
|||||||
|
|
||||||
// convert properties for simpl
|
// convert properties for simpl
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the simplPort
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplPort { get { return Convert.ToUInt16(port); } }
|
public ushort simplPort { get { return Convert.ToUInt16(port); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the simplAutoReconnect
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplAutoReconnect { get { return (ushort)(autoReconnect ? 1 : 0); } }
|
public ushort simplAutoReconnect { get { return (ushort)(autoReconnect ? 1 : 0); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -193,7 +193,7 @@ namespace PepperDash.Core.JsonStandardObjects;
|
|||||||
|
|
||||||
// convert properties for simpl
|
// convert properties for simpl
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the simplControlPortNumber
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplControlPortNumber { get { return Convert.ToUInt16(controlPortNumber); } }
|
public ushort simplControlPortNumber { get { return Convert.ToUInt16(controlPortNumber); } }
|
||||||
|
|
||||||
@@ -227,11 +227,11 @@ namespace PepperDash.Core.JsonStandardObjects;
|
|||||||
|
|
||||||
// convert properties for simpl
|
// convert properties for simpl
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the simplDeviceId
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplDeviceId { get { return Convert.ToUInt16(deviceId); } }
|
public ushort simplDeviceId { get { return Convert.ToUInt16(deviceId); } }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the simplEnabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort simplEnabled { get { return (ushort)(enabled ? 1 : 0); } }
|
public ushort simplEnabled { get { return (ushort)(enabled ? 1 : 0); } }
|
||||||
|
|
||||||
@@ -254,3 +254,4 @@ namespace PepperDash.Core.JsonStandardObjects;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<DeviceConfig> devices { get; set; }
|
public List<DeviceConfig> devices { get; set; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl;
|
namespace PepperDash.Core.JsonToSimpl
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constants for Simpl modules
|
/// Constants for Simpl modules
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -89,7 +89,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
public class SPlusValueWrapper
|
public class SPlusValueWrapper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the ValueType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SPlusType ValueType { get; private set; }
|
public SPlusType ValueType { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -140,3 +140,4 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
String
|
String
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,8 @@ using Serilog.Events;
|
|||||||
|
|
||||||
//using PepperDash.Core;
|
//using PepperDash.Core;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl;
|
namespace PepperDash.Core.JsonToSimpl
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The global class to manage all the instances of JsonToSimplMaster
|
/// The global class to manage all the instances of JsonToSimplMaster
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -23,6 +23,9 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="master">New master to add</param>
|
/// <param name="master">New master to add</param>
|
||||||
///
|
///
|
||||||
|
/// <summary>
|
||||||
|
/// AddMaster method
|
||||||
|
/// </summary>
|
||||||
public static void AddMaster(JsonToSimplMaster master)
|
public static void AddMaster(JsonToSimplMaster master)
|
||||||
{
|
{
|
||||||
if (master == null)
|
if (master == null)
|
||||||
@@ -57,3 +60,4 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase));
|
return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
|
using Newtonsoft.Json.Linq;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl;
|
namespace PepperDash.Core.JsonToSimpl
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to interact with an array of values with the S+ modules
|
/// Represents a JsonToSimplArrayLookupChild
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase
|
public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase
|
||||||
{
|
{
|
||||||
@@ -79,8 +77,9 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Process all values
|
/// ProcessAll method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <inheritdoc />
|
||||||
public override void ProcessAll()
|
public override void ProcessAll()
|
||||||
{
|
{
|
||||||
if (FindInArray())
|
if (FindInArray())
|
||||||
@@ -130,7 +129,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
var item = array.FirstOrDefault(o =>
|
var item = array.FirstOrDefault(o =>
|
||||||
{
|
{
|
||||||
var prop = o[SearchPropertyName];
|
var prop = o[SearchPropertyName];
|
||||||
return prop != null && ((string)prop)
|
return prop != null && prop.Value<string>()
|
||||||
.Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
|
.Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
|
||||||
});
|
});
|
||||||
if (item == null)
|
if (item == null)
|
||||||
@@ -161,3 +160,4 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using PepperDash.Core.Logging;
|
using Newtonsoft.Json.Linq;
|
||||||
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core.JsonToSimpl
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for JSON objects
|
/// Base class for JSON objects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -37,7 +34,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
public SPlusValuesDelegate SetAllPathsDelegate { get; set; }
|
public SPlusValuesDelegate SetAllPathsDelegate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique identifier for instance
|
/// Gets or sets the Key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Key { get; protected set; }
|
public string Key { get; protected set; }
|
||||||
|
|
||||||
@@ -53,7 +50,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
public string PathSuffix { get; protected set; }
|
public string PathSuffix { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if the instance is linked to an object
|
/// Gets or sets the LinkedToObject
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LinkedToObject { get; protected set; }
|
public bool LinkedToObject { get; protected set; }
|
||||||
|
|
||||||
@@ -92,13 +89,16 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
if (Master != null)
|
if (Master != null)
|
||||||
Master.AddChild(this);
|
Master.AddChild(this);
|
||||||
else
|
else
|
||||||
this.LogWarning("JSON Child [{0}] cannot link to master {1}", key, masterUniqueId);
|
Debug.Console(1, "JSON Child [{0}] cannot link to master {1}", key, masterUniqueId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the path prefix for the object
|
/// Sets the path prefix for the object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pathPrefix"></param>
|
/// <param name="pathPrefix"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SetPathPrefix method
|
||||||
|
/// </summary>
|
||||||
public void SetPathPrefix(string pathPrefix)
|
public void SetPathPrefix(string pathPrefix)
|
||||||
{
|
{
|
||||||
PathPrefix = pathPrefix;
|
PathPrefix = pathPrefix;
|
||||||
@@ -108,7 +108,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetBoolPath(ushort index, string path)
|
public void SetBoolPath(ushort index, string path)
|
||||||
{
|
{
|
||||||
this.LogDebug("JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path);
|
Debug.Console(1, "JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path);
|
||||||
if (path == null || path.Trim() == string.Empty) return;
|
if (path == null || path.Trim() == string.Empty) return;
|
||||||
BoolPaths[index] = path;
|
BoolPaths[index] = path;
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetUshortPath(ushort index, string path)
|
public void SetUshortPath(ushort index, string path)
|
||||||
{
|
{
|
||||||
this.LogDebug("JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
|
Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
|
||||||
if (path == null || path.Trim() == string.Empty) return;
|
if (path == null || path.Trim() == string.Empty) return;
|
||||||
UshortPaths[index] = path;
|
UshortPaths[index] = path;
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetStringPath(ushort index, string path)
|
public void SetStringPath(ushort index, string path)
|
||||||
{
|
{
|
||||||
this.LogDebug("JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
|
Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
|
||||||
if (path == null || path.Trim() == string.Empty) return;
|
if (path == null || path.Trim() == string.Empty) return;
|
||||||
StringPaths[index] = path;
|
StringPaths[index] = path;
|
||||||
}
|
}
|
||||||
@@ -141,13 +141,13 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
{
|
{
|
||||||
if (!LinkedToObject)
|
if (!LinkedToObject)
|
||||||
{
|
{
|
||||||
this.LogDebug("Not linked to object in file. Skipping");
|
Debug.Console(1, this, "Not linked to object in file. Skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SetAllPathsDelegate == null)
|
if (SetAllPathsDelegate == null)
|
||||||
{
|
{
|
||||||
this.LogDebug("No SetAllPathsDelegate set. Ignoring ProcessAll");
|
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring ProcessAll");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SetAllPathsDelegate();
|
SetAllPathsDelegate();
|
||||||
@@ -207,11 +207,11 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
bool Process(string path, out string response)
|
bool Process(string path, out string response)
|
||||||
{
|
{
|
||||||
path = GetFullPath(path);
|
path = GetFullPath(path);
|
||||||
this.LogDebug("JSON Child[{0}] Processing {1}", Key, path);
|
Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path);
|
||||||
response = "";
|
response = "";
|
||||||
if (Master == null)
|
if (Master == null)
|
||||||
{
|
{
|
||||||
this.LogWarning("JSONChild[{0}] cannot process without Master attached", Key);
|
Debug.Console(1, "JSONChild[{0}] cannot process without Master attached", Key);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,8 +233,8 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
if (isCount)
|
if (isCount)
|
||||||
response = (t.HasValues ? t.Children().Count() : 0).ToString();
|
response = (t.HasValues ? t.Children().Count() : 0).ToString();
|
||||||
else
|
else
|
||||||
response = (string)t;
|
response = t.Value<string>();
|
||||||
this.LogDebug(" ='{0}'", response);
|
Debug.Console(1, " ='{0}'", response);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,13 +260,13 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
{
|
{
|
||||||
if (!LinkedToObject)
|
if (!LinkedToObject)
|
||||||
{
|
{
|
||||||
this.LogDebug("Not linked to object in file. Skipping");
|
Debug.Console(1, this, "Not linked to object in file. Skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SetAllPathsDelegate == null)
|
if (SetAllPathsDelegate == null)
|
||||||
{
|
{
|
||||||
this.LogDebug("No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
|
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SetAllPathsDelegate();
|
SetAllPathsDelegate();
|
||||||
@@ -280,6 +280,9 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="theValue"></param>
|
/// <param name="theValue"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// USetBoolValue method
|
||||||
|
/// </summary>
|
||||||
public void USetBoolValue(ushort key, ushort theValue)
|
public void USetBoolValue(ushort key, ushort theValue)
|
||||||
{
|
{
|
||||||
SetBoolValue(key, theValue == 1);
|
SetBoolValue(key, theValue == 1);
|
||||||
@@ -290,6 +293,9 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="theValue"></param>
|
/// <param name="theValue"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SetBoolValue method
|
||||||
|
/// </summary>
|
||||||
public void SetBoolValue(ushort key, bool theValue)
|
public void SetBoolValue(ushort key, bool theValue)
|
||||||
{
|
{
|
||||||
if (BoolPaths.ContainsKey(key))
|
if (BoolPaths.ContainsKey(key))
|
||||||
@@ -301,6 +307,9 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="theValue"></param>
|
/// <param name="theValue"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SetUShortValue method
|
||||||
|
/// </summary>
|
||||||
public void SetUShortValue(ushort key, ushort theValue)
|
public void SetUShortValue(ushort key, ushort theValue)
|
||||||
{
|
{
|
||||||
if (UshortPaths.ContainsKey(key))
|
if (UshortPaths.ContainsKey(key))
|
||||||
@@ -312,6 +321,9 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="theValue"></param>
|
/// <param name="theValue"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SetStringValue method
|
||||||
|
/// </summary>
|
||||||
public void SetStringValue(ushort key, string theValue)
|
public void SetStringValue(ushort key, string theValue)
|
||||||
{
|
{
|
||||||
if (StringPaths.ContainsKey(key))
|
if (StringPaths.ContainsKey(key))
|
||||||
@@ -323,12 +335,15 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="keyPath"></param>
|
/// <param name="keyPath"></param>
|
||||||
/// <param name="valueToSave"></param>
|
/// <param name="valueToSave"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SetValueOnMaster method
|
||||||
|
/// </summary>
|
||||||
public void SetValueOnMaster(string keyPath, JValue valueToSave)
|
public void SetValueOnMaster(string keyPath, JValue valueToSave)
|
||||||
{
|
{
|
||||||
var path = GetFullPath(keyPath);
|
var path = GetFullPath(keyPath);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.LogDebug("JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
|
Debug.Console(1, "JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
|
||||||
|
|
||||||
//var token = Master.JsonObject.SelectToken(path);
|
//var token = Master.JsonObject.SelectToken(path);
|
||||||
//if (token != null) // The path exists in the file
|
//if (token != null) // The path exists in the file
|
||||||
@@ -336,7 +351,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
this.LogDebug("JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
|
Debug.Console(1, "JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,3 +419,4 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,14 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using PepperDash.Core.Logging;
|
using Newtonsoft.Json.Linq;
|
||||||
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
|
||||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
|
||||||
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core.JsonToSimpl
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a JSON file that can be read and written to
|
/// Represents a JSON file that can be read and written to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -25,12 +20,12 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
|||||||
public string Filepath { get; private set; }
|
public string Filepath { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Filepath to the actual file that will be read (Portal or local)
|
/// Gets or sets the ActualFilePath
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ActualFilePath { get; private set; }
|
public string ActualFilePath { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the Filename
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Filename { get; private set; }
|
public string Filename { get; private set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -130,7 +125,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
|||||||
var fileName = Path.GetFileName(Filepath);
|
var fileName = Path.GetFileName(Filepath);
|
||||||
|
|
||||||
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange);
|
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange);
|
||||||
this.LogInformation("Checking '{0}' for '{1}'", fileDirectory, fileName);
|
Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName);
|
||||||
|
|
||||||
if (Directory.Exists(fileDirectory))
|
if (Directory.Exists(fileDirectory))
|
||||||
{
|
{
|
||||||
@@ -144,7 +139,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
|||||||
var msg = string.Format("JSON file not found: {0}", Filepath);
|
var msg = string.Format("JSON file not found: {0}", Filepath);
|
||||||
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
|
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
|
||||||
CrestronConsole.PrintLine(msg);
|
CrestronConsole.PrintLine(msg);
|
||||||
this.LogError(msg);
|
ErrorLog.Error(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,18 +148,18 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
|||||||
ActualFilePath = actualFile.FullName;
|
ActualFilePath = actualFile.FullName;
|
||||||
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
|
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
|
||||||
OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange);
|
OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange);
|
||||||
this.LogInformation("Actual JSON file is {0}", ActualFilePath);
|
Debug.Console(1, "Actual JSON file is {0}", ActualFilePath);
|
||||||
|
|
||||||
Filename = actualFile.Name;
|
Filename = actualFile.Name;
|
||||||
OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange);
|
OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange);
|
||||||
OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange);
|
OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange);
|
||||||
this.LogInformation("JSON Filename is {0}", Filename);
|
Debug.Console(1, "JSON Filename is {0}", Filename);
|
||||||
|
|
||||||
|
|
||||||
FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator);
|
FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator);
|
||||||
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
|
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
|
||||||
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange);
|
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange);
|
||||||
this.LogInformation("JSON File Path is {0}", FilePathName);
|
Debug.Console(1, "JSON File Path is {0}", FilePathName);
|
||||||
|
|
||||||
var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
||||||
|
|
||||||
@@ -177,7 +172,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange);
|
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange);
|
||||||
this.LogError("'{0}' not found", fileDirectory);
|
Debug.Console(1, "'{0}' not found", fileDirectory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -185,12 +180,12 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
|||||||
var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message);
|
var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message);
|
||||||
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
|
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
|
||||||
CrestronConsole.PrintLine(msg);
|
CrestronConsole.PrintLine(msg);
|
||||||
this.LogException(e, "EvaluateFile Exception: {0}", e.Message);
|
ErrorLog.Error(msg);
|
||||||
|
|
||||||
var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
|
var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
|
||||||
OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange);
|
OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange);
|
||||||
CrestronConsole.PrintLine(stackTrace);
|
CrestronConsole.PrintLine(stackTrace);
|
||||||
this.LogVerbose("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
|
ErrorLog.Error(stackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +194,9 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
|||||||
/// Sets the debug level
|
/// Sets the debug level
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="level"></param>
|
/// <param name="level"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// setDebugLevel method
|
||||||
|
/// </summary>
|
||||||
public void setDebugLevel(uint level)
|
public void setDebugLevel(uint level)
|
||||||
{
|
{
|
||||||
Debug.SetDebugLevel(level);
|
Debug.SetDebugLevel(level);
|
||||||
@@ -214,31 +212,63 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
|||||||
// Make each child update their values into master object
|
// Make each child update their values into master object
|
||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
this.LogInformation("Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
||||||
child.UpdateInputsForMaster();
|
child.UpdateInputsForMaster();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
||||||
{
|
{
|
||||||
this.LogInformation("Master [{0}] No updated values to save. Skipping", UniqueID);
|
Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lock (FileLock)
|
lock (FileLock)
|
||||||
{
|
{
|
||||||
this.LogInformation("Saving");
|
Debug.Console(1, "Saving");
|
||||||
foreach (var path in UnsavedValues.Keys)
|
foreach (var path in UnsavedValues.Keys)
|
||||||
{
|
{
|
||||||
var tokenToReplace = JsonObject.SelectToken(path);
|
var tokenToReplace = JsonObject.SelectToken(path);
|
||||||
if (tokenToReplace != null)
|
if (tokenToReplace != null)
|
||||||
{// It's found
|
{// It's found
|
||||||
tokenToReplace.Replace(UnsavedValues[path]);
|
tokenToReplace.Replace(UnsavedValues[path]);
|
||||||
this.LogInformation("JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
||||||
}
|
}
|
||||||
else // No token. Let's make one
|
else // No token. Let's make one
|
||||||
{
|
{
|
||||||
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
|
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
|
||||||
this.LogWarning("JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
|
Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
|
||||||
|
|
||||||
|
// JContainer jpart = JsonObject;
|
||||||
|
// // walk down the path and find where it goes
|
||||||
|
//#warning Does not handle arrays.
|
||||||
|
// foreach (var part in path.Split('.'))
|
||||||
|
// {
|
||||||
|
|
||||||
|
// var openPos = part.IndexOf('[');
|
||||||
|
// if (openPos > -1)
|
||||||
|
// {
|
||||||
|
// openPos++; // move to number
|
||||||
|
// var closePos = part.IndexOf(']');
|
||||||
|
// var arrayName = part.Substring(0, openPos - 1); // get the name
|
||||||
|
// var index = Convert.ToInt32(part.Substring(openPos, closePos - openPos));
|
||||||
|
|
||||||
|
// // Check if the array itself exists and add the item if so
|
||||||
|
// if (jpart[arrayName] != null)
|
||||||
|
// {
|
||||||
|
// var arrayObj = jpart[arrayName] as JArray;
|
||||||
|
// var item = arrayObj[index];
|
||||||
|
// if (item == null)
|
||||||
|
// arrayObj.Add(new JObject());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Debug.Console(0, "IGNORING MISSING ARRAY VALUE FOR NOW");
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// // Build the
|
||||||
|
// if (jpart[part] == null)
|
||||||
|
// jpart.Add(new JProperty(part, new JObject()));
|
||||||
|
// jpart = jpart[part] as JContainer;
|
||||||
|
// }
|
||||||
|
// jpart.Replace(UnsavedValues[path]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
using (StreamWriter sw = new StreamWriter(ActualFilePath))
|
using (StreamWriter sw = new StreamWriter(ActualFilePath))
|
||||||
@@ -251,13 +281,12 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
string err = string.Format("Error writing JSON file:\r{0}", e);
|
string err = string.Format("Error writing JSON file:\r{0}", e);
|
||||||
this.LogException(e, "Error writing JSON file: {0}", e.Message);
|
Debug.Console(0, err);
|
||||||
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
|
ErrorLog.Warn(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl;
|
namespace PepperDash.Core.JsonToSimpl
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Represents a JsonToSimplFixedPathObject
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonToSimplFixedPathObject : JsonToSimplChildObjectBase
|
public class JsonToSimplFixedPathObject : JsonToSimplChildObjectBase
|
||||||
{
|
{
|
||||||
@@ -15,3 +15,4 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
this.LinkedToObject = true;
|
this.LinkedToObject = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,12 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using PepperDash.Core.Logging;
|
using Newtonsoft.Json.Linq;
|
||||||
using Renci.SshNet.Messages;
|
|
||||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
|
||||||
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core.JsonToSimpl
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generic Master
|
/// Represents a JsonToSimplGenericMaster
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonToSimplGenericMaster : JsonToSimplMaster
|
public class JsonToSimplGenericMaster : JsonToSimplMaster
|
||||||
{
|
{
|
||||||
@@ -26,7 +21,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
static object WriteLock = new object();
|
static object WriteLock = new object();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Callback action for saving
|
/// Gets or sets the SaveCallback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<string> SaveCallback { get; set; }
|
public Action<string> SaveCallback { get; set; }
|
||||||
|
|
||||||
@@ -76,8 +71,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
this.LogException(e, "JSON parsing failed:\r{0}", e.Message);
|
Debug.Console(0, this, "JSON parsing failed:\r{0}", e);
|
||||||
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,36 +86,37 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
// Make each child update their values into master object
|
// Make each child update their values into master object
|
||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
this.LogDebug("Master. checking child [{0}] for updates to save", child.Key);
|
Debug.Console(1, this, "Master. checking child [{0}] for updates to save", child.Key);
|
||||||
child.UpdateInputsForMaster();
|
child.UpdateInputsForMaster();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
||||||
{
|
{
|
||||||
this.LogDebug("Master. No updated values to save. Skipping");
|
Debug.Console(1, this, "Master. No updated values to save. Skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (WriteLock)
|
lock (WriteLock)
|
||||||
{
|
{
|
||||||
this.LogDebug("Saving");
|
Debug.Console(1, this, "Saving");
|
||||||
foreach (var path in UnsavedValues.Keys)
|
foreach (var path in UnsavedValues.Keys)
|
||||||
{
|
{
|
||||||
var tokenToReplace = JsonObject.SelectToken(path);
|
var tokenToReplace = JsonObject.SelectToken(path);
|
||||||
if (tokenToReplace != null)
|
if (tokenToReplace != null)
|
||||||
{// It's found
|
{// It's found
|
||||||
tokenToReplace.Replace(UnsavedValues[path]);
|
tokenToReplace.Replace(UnsavedValues[path]);
|
||||||
this.LogDebug("Master Updating '{0}'", path);
|
Debug.Console(1, this, "Master Updating '{0}'", path);
|
||||||
}
|
}
|
||||||
else // No token. Let's make one
|
else // No token. Let's make one
|
||||||
{
|
{
|
||||||
this.LogDebug("Master Cannot write value onto missing property: '{0}'", path);
|
Debug.Console(1, "Master Cannot write value onto missing property: '{0}'", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (SaveCallback != null)
|
if (SaveCallback != null)
|
||||||
SaveCallback(JsonObject.ToString());
|
SaveCallback(JsonObject.ToString());
|
||||||
else
|
else
|
||||||
this.LogDebug("WARNING: No save callback defined.");
|
Debug.Console(0, this, "WARNING: No save callback defined.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
extern alias NewtonsoftJson;
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
|
using Newtonsoft.Json;
|
||||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
using Newtonsoft.Json.Linq;
|
||||||
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
|
||||||
using JsonSerializationException = NewtonsoftJson::Newtonsoft.Json.JsonSerializationException;
|
|
||||||
using JsonTextReader = NewtonsoftJson::Newtonsoft.Json.JsonTextReader;
|
|
||||||
using PepperDash.Core.Logging;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core.JsonToSimpl
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstract base class for JsonToSimpl interactions
|
/// Abstract base class for JsonToSimpl interactions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -45,7 +39,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
public string Key { get { return UniqueID; } }
|
public string Key { get { return UniqueID; } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A unique ID
|
/// Gets or sets the UniqueID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UniqueID { get; protected set; }
|
public string UniqueID { get; protected set; }
|
||||||
|
|
||||||
@@ -89,7 +83,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// Gets or sets the JsonObject
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JObject JsonObject { get; protected set; }
|
public JObject JsonObject { get; protected set; }
|
||||||
|
|
||||||
@@ -143,10 +137,11 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
{
|
{
|
||||||
if (UnsavedValues.ContainsKey(path))
|
if (UnsavedValues.ContainsKey(path))
|
||||||
{
|
{
|
||||||
this.LogWarning("Master[{0}] WARNING - Attempt to add duplicate value for path '{1}'.\r Ingoring. Please ensure that path does not exist on multiple modules.", UniqueID, path);
|
Debug.Console(0, "Master[{0}] WARNING - Attempt to add duplicate value for path '{1}'.\r Ingoring. Please ensure that path does not exist on multiple modules.", UniqueID, path);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
UnsavedValues.Add(path, value);
|
UnsavedValues.Add(path, value);
|
||||||
|
//Debug.Console(0, "Master[{0}] Unsaved size={1}", UniqueID, UnsavedValues.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -167,7 +162,11 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static JObject ParseObject(string json)
|
public static JObject ParseObject(string json)
|
||||||
{
|
{
|
||||||
|
#if NET6_0
|
||||||
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
||||||
|
#else
|
||||||
|
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
var startDepth = reader.Depth;
|
var startDepth = reader.Depth;
|
||||||
var obj = JObject.Load(reader);
|
var obj = JObject.Load(reader);
|
||||||
@@ -182,10 +181,16 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="json"></param>
|
/// <param name="json"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// ParseArray method
|
||||||
|
/// </summary>
|
||||||
public static JArray ParseArray(string json)
|
public static JArray ParseArray(string json)
|
||||||
{
|
{
|
||||||
|
#if NET6_0
|
||||||
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
||||||
|
#else
|
||||||
|
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
var startDepth = reader.Depth;
|
var startDepth = reader.Depth;
|
||||||
var obj = JArray.Load(reader);
|
var obj = JArray.Load(reader);
|
||||||
@@ -244,3 +249,4 @@ namespace PepperDash.Core.JsonToSimpl;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
using Newtonsoft.Json.Linq;
|
||||||
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
|
||||||
using PepperDash.Core.Config;
|
using PepperDash.Core.Config;
|
||||||
using PepperDash.Core.Logging;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.JsonToSimpl;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core.JsonToSimpl
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Portal File Master
|
/// Portal File Master
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -23,7 +19,7 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
|||||||
public string PortalFilepath { get; private set; }
|
public string PortalFilepath { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// File path of the actual file being read (Portal or local)
|
/// Gets or sets the ActualFilePath
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ActualFilePath { get; private set; }
|
public string ActualFilePath { get; private set; }
|
||||||
|
|
||||||
@@ -62,7 +58,7 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
|||||||
// If the portal file is xyz.json, then
|
// If the portal file is xyz.json, then
|
||||||
// the file we want to check for first will be called xyz.local.json
|
// the file we want to check for first will be called xyz.local.json
|
||||||
var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json");
|
var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json");
|
||||||
this.LogInformation("Checking for local file {0}", localFilepath);
|
Debug.Console(0, this, "Checking for local file {0}", localFilepath);
|
||||||
var actualLocalFile = GetActualFileInfoFromPath(localFilepath);
|
var actualLocalFile = GetActualFileInfoFromPath(localFilepath);
|
||||||
|
|
||||||
if (actualLocalFile != null)
|
if (actualLocalFile != null)
|
||||||
@@ -74,7 +70,7 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
|||||||
// and create the local.
|
// and create the local.
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.LogInformation("Local JSON file not found {0}\rLoading portal JSON file", localFilepath);
|
Debug.Console(1, this, "Local JSON file not found {0}\rLoading portal JSON file", localFilepath);
|
||||||
var actualPortalFile = GetActualFileInfoFromPath(portalFilepath);
|
var actualPortalFile = GetActualFileInfoFromPath(portalFilepath);
|
||||||
if (actualPortalFile != null)
|
if (actualPortalFile != null)
|
||||||
{
|
{
|
||||||
@@ -87,13 +83,14 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath);
|
var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath);
|
||||||
this.LogError(msg);
|
Debug.Console(1, this, msg);
|
||||||
|
ErrorLog.Error(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we should have a local file. Do it.
|
// At this point we should have a local file. Do it.
|
||||||
this.LogInformation("Reading local JSON file {0}", ActualFilePath);
|
Debug.Console(1, "Reading local JSON file {0}", ActualFilePath);
|
||||||
|
|
||||||
string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
||||||
|
|
||||||
@@ -107,7 +104,8 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var msg = string.Format("JSON parsing failed:\r{0}", e);
|
var msg = string.Format("JSON parsing failed:\r{0}", e);
|
||||||
this.LogError(msg);
|
CrestronConsole.PrintLine(msg);
|
||||||
|
ErrorLog.Error(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,30 +146,30 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
|||||||
// Make each child update their values into master object
|
// Make each child update their values into master object
|
||||||
foreach (var child in Children)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
this.LogInformation("Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
||||||
child.UpdateInputsForMaster();
|
child.UpdateInputsForMaster();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
||||||
{
|
{
|
||||||
this.LogInformation("Master [{0}] No updated values to save. Skipping", UniqueID);
|
Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lock (FileLock)
|
lock (FileLock)
|
||||||
{
|
{
|
||||||
this.LogInformation("Saving");
|
Debug.Console(1, "Saving");
|
||||||
foreach (var path in UnsavedValues.Keys)
|
foreach (var path in UnsavedValues.Keys)
|
||||||
{
|
{
|
||||||
var tokenToReplace = JsonObject.SelectToken(path);
|
var tokenToReplace = JsonObject.SelectToken(path);
|
||||||
if (tokenToReplace != null)
|
if (tokenToReplace != null)
|
||||||
{// It's found
|
{// It's found
|
||||||
tokenToReplace.Replace(UnsavedValues[path]);
|
tokenToReplace.Replace(UnsavedValues[path]);
|
||||||
this.LogInformation("JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
||||||
}
|
}
|
||||||
else // No token. Let's make one
|
else // No token. Let's make one
|
||||||
{
|
{
|
||||||
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
|
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
|
||||||
this.LogWarning("JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
|
Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,11 +183,12 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
string err = string.Format("Error writing JSON file:\r{0}", e);
|
string err = string.Format("Error writing JSON file:\r{0}", e);
|
||||||
this.LogException(e, "Error writing JSON file: {0}", e.Message);
|
Debug.Console(0, err);
|
||||||
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
|
ErrorLog.Warn(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging;
|
namespace PepperDash.Core.Logging
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enriches log events with Crestron-specific context properties, such as the application name based on the device platform.
|
/// Represents a CrestronEnricher
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CrestronEnricher : ILogEventEnricher
|
public class CrestronEnricher : ILogEventEnricher
|
||||||
{
|
{
|
||||||
@@ -31,10 +31,8 @@ public class CrestronEnricher : ILogEventEnricher
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enriches the log event with Crestron-specific properties.
|
/// Enrich method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logEvent"></param>
|
|
||||||
/// <param name="propertyFactory"></param>
|
|
||||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||||
{
|
{
|
||||||
var property = propertyFactory.CreateProperty("App", _appName);
|
var property = propertyFactory.CreateProperty("App", _appName);
|
||||||
@@ -42,3 +40,4 @@ public class CrestronEnricher : ILogEventEnricher
|
|||||||
logEvent.AddOrUpdateProperty(property);
|
logEvent.AddOrUpdateProperty(property);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,12 +9,18 @@ using System.IO;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a DebugConsoleSink
|
||||||
|
/// </summary>
|
||||||
public class DebugConsoleSink : ILogEventSink
|
public class DebugConsoleSink : ILogEventSink
|
||||||
{
|
{
|
||||||
private readonly ITextFormatter _textFormatter;
|
private readonly ITextFormatter _textFormatter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emit method
|
||||||
|
/// </summary>
|
||||||
public void Emit(LogEvent logEvent)
|
public void Emit(LogEvent logEvent)
|
||||||
{
|
{
|
||||||
if (!Debug.IsRunningOnAppliance) return;
|
if (!Debug.IsRunningOnAppliance) return;
|
||||||
@@ -44,6 +50,9 @@ public class DebugConsoleSink : ILogEventSink
|
|||||||
|
|
||||||
public static class DebugConsoleSinkExtensions
|
public static class DebugConsoleSinkExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// DebugConsoleSink method
|
||||||
|
/// </summary>
|
||||||
public static LoggerConfiguration DebugConsoleSink(
|
public static LoggerConfiguration DebugConsoleSink(
|
||||||
this LoggerSinkConfiguration loggerConfiguration,
|
this LoggerSinkConfiguration loggerConfiguration,
|
||||||
ITextFormatter formatProvider = null)
|
ITextFormatter formatProvider = null)
|
||||||
@@ -51,3 +60,5 @@ public static class DebugConsoleSinkExtensions
|
|||||||
return loggerConfiguration.Sink(new DebugConsoleSink(formatProvider));
|
return loggerConfiguration.Sink(new DebugConsoleSink(formatProvider));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
293
src/PepperDash.Core/Logging/DebugContext.cs
Normal file
293
src/PepperDash.Core/Logging/DebugContext.cs
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a debugging context
|
||||||
|
/// </summary>
|
||||||
|
public class DebugContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Describes the folder location where a given program stores it's debug level memory. By default, the
|
||||||
|
/// file written will be named appNdebug where N is 1-10.
|
||||||
|
/// </summary>
|
||||||
|
public string Key { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the file containing the current debug settings.
|
||||||
|
/// </summary>
|
||||||
|
//string FileName = string.Format(@"\nvram\debug\app{0}Debug.json", InitialParametersClass.ApplicationNumber);
|
||||||
|
|
||||||
|
DebugContextSaveData SaveData;
|
||||||
|
|
||||||
|
int SaveTimeoutMs = 30000;
|
||||||
|
|
||||||
|
CTimer SaveTimer;
|
||||||
|
|
||||||
|
|
||||||
|
static List<DebugContext> Contexts = new List<DebugContext>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates or gets a debug context
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// GetDebugContext method
|
||||||
|
/// </summary>
|
||||||
|
public static DebugContext GetDebugContext(string key)
|
||||||
|
{
|
||||||
|
var context = Contexts.FirstOrDefault(c => c.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
context = new DebugContext(key);
|
||||||
|
Contexts.Add(context);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Do not use. For S+ access.
|
||||||
|
/// </summary>
|
||||||
|
public DebugContext() { }
|
||||||
|
|
||||||
|
DebugContext(string key)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
|
||||||
|
{
|
||||||
|
// Add command to console
|
||||||
|
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
|
||||||
|
"appdebug:P [0-2]: Sets the application's console debug message level",
|
||||||
|
ConsoleAccessLevelEnum.AccessOperator);
|
||||||
|
}
|
||||||
|
|
||||||
|
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
||||||
|
|
||||||
|
LoadMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to save memory when shutting down
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="programEventType"></param>
|
||||||
|
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||||
|
{
|
||||||
|
if (programEventType == eProgramStatusEventType.Stopping)
|
||||||
|
{
|
||||||
|
if (SaveTimer != null)
|
||||||
|
{
|
||||||
|
SaveTimer.Stop();
|
||||||
|
SaveTimer = null;
|
||||||
|
}
|
||||||
|
Console(0, "Saving debug settings");
|
||||||
|
SaveMemory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback for console command
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="levelString"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SetDebugFromConsole method
|
||||||
|
/// </summary>
|
||||||
|
public void SetDebugFromConsole(string levelString)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(levelString.Trim()))
|
||||||
|
{
|
||||||
|
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", SaveData.Level);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDebugLevel(Convert.ToInt32(levelString));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
CrestronConsole.PrintLine("Usage: appdebug:P [0-2]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the debug level
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level"> Valid values 0 (no debug), 1 (critical), 2 (all messages)</param>
|
||||||
|
/// <summary>
|
||||||
|
/// SetDebugLevel method
|
||||||
|
/// </summary>
|
||||||
|
public void SetDebugLevel(int level)
|
||||||
|
{
|
||||||
|
if (level <= 2)
|
||||||
|
{
|
||||||
|
SaveData.Level = level;
|
||||||
|
SaveMemoryOnTimeout();
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine("[Application {0}], Debug level set to {1}",
|
||||||
|
InitialParametersClass.ApplicationNumber, SaveData.Level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prints message to console if current debug level is equal to or higher than the level of this message.
|
||||||
|
/// Uses CrestronConsole.PrintLine.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level"></param>
|
||||||
|
/// <param name="format">Console format string</param>
|
||||||
|
/// <param name="items">Object parameters</param>
|
||||||
|
public void Console(uint level, string format, params object[] items)
|
||||||
|
{
|
||||||
|
if (SaveData.Level >= level)
|
||||||
|
CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber,
|
||||||
|
string.Format(format, items));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Console method
|
||||||
|
/// </summary>
|
||||||
|
public void Console(uint level, IKeyed dev, string format, params object[] items)
|
||||||
|
{
|
||||||
|
if (SaveData.Level >= level)
|
||||||
|
Console(level, "[{0}] {1}", dev.Key, string.Format(format, items));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level"></param>
|
||||||
|
/// <param name="dev"></param>
|
||||||
|
/// <param name="errorLogLevel"></param>
|
||||||
|
/// <param name="format"></param>
|
||||||
|
/// <param name="items"></param>
|
||||||
|
public void Console(uint level, IKeyed dev, Debug.ErrorLogLevel errorLogLevel,
|
||||||
|
string format, params object[] items)
|
||||||
|
{
|
||||||
|
if (SaveData.Level >= level)
|
||||||
|
{
|
||||||
|
var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items));
|
||||||
|
Console(level, str);
|
||||||
|
LogError(errorLogLevel, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level"></param>
|
||||||
|
/// <param name="errorLogLevel"></param>
|
||||||
|
/// <param name="format"></param>
|
||||||
|
/// <param name="items"></param>
|
||||||
|
public void Console(uint level, Debug.ErrorLogLevel errorLogLevel,
|
||||||
|
string format, params object[] items)
|
||||||
|
{
|
||||||
|
if (SaveData.Level >= level)
|
||||||
|
{
|
||||||
|
var str = string.Format(format, items);
|
||||||
|
Console(level, str);
|
||||||
|
LogError(errorLogLevel, str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="errorLogLevel"></param>
|
||||||
|
/// <param name="str"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// LogError method
|
||||||
|
/// </summary>
|
||||||
|
public void LogError(Debug.ErrorLogLevel errorLogLevel, string str)
|
||||||
|
{
|
||||||
|
string msg = string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
|
||||||
|
switch (errorLogLevel)
|
||||||
|
{
|
||||||
|
case Debug.ErrorLogLevel.Error:
|
||||||
|
ErrorLog.Error(msg);
|
||||||
|
break;
|
||||||
|
case Debug.ErrorLogLevel.Warning:
|
||||||
|
ErrorLog.Warn(msg);
|
||||||
|
break;
|
||||||
|
case Debug.ErrorLogLevel.Notice:
|
||||||
|
ErrorLog.Notice(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the memory object after timeout
|
||||||
|
/// </summary>
|
||||||
|
void SaveMemoryOnTimeout()
|
||||||
|
{
|
||||||
|
if (SaveTimer == null)
|
||||||
|
SaveTimer = new CTimer(o =>
|
||||||
|
{
|
||||||
|
SaveTimer = null;
|
||||||
|
SaveMemory();
|
||||||
|
}, SaveTimeoutMs);
|
||||||
|
else
|
||||||
|
SaveTimer.Reset(SaveTimeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the memory - use SaveMemoryOnTimeout
|
||||||
|
/// </summary>
|
||||||
|
void SaveMemory()
|
||||||
|
{
|
||||||
|
using (StreamWriter sw = new StreamWriter(GetMemoryFileName()))
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(SaveData);
|
||||||
|
sw.Write(json);
|
||||||
|
sw.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
void LoadMemory()
|
||||||
|
{
|
||||||
|
var file = GetMemoryFileName();
|
||||||
|
if (File.Exists(file))
|
||||||
|
{
|
||||||
|
using (StreamReader sr = new StreamReader(file))
|
||||||
|
{
|
||||||
|
var data = JsonConvert.DeserializeObject<DebugContextSaveData>(sr.ReadToEnd());
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
SaveData = data;
|
||||||
|
Debug.Console(1, "Debug memory restored from file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SaveData = new DebugContextSaveData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to get the file path for this app's debug memory
|
||||||
|
/// </summary>
|
||||||
|
string GetMemoryFileName()
|
||||||
|
{
|
||||||
|
return string.Format(@"\NVRAM\debugSettings\program{0}-{1}", InitialParametersClass.ApplicationNumber, Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public class DebugContextSaveData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public int Level { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,16 @@ using Crestron.SimplSharp.CrestronLogger;
|
|||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging;
|
namespace PepperDash.Core.Logging
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a DebugCrestronLoggerSink
|
||||||
|
/// </summary>
|
||||||
public class DebugCrestronLoggerSink : ILogEventSink
|
public class DebugCrestronLoggerSink : ILogEventSink
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Emit method
|
||||||
|
/// </summary>
|
||||||
public void Emit(LogEvent logEvent)
|
public void Emit(LogEvent logEvent)
|
||||||
{
|
{
|
||||||
if (!Debug.IsRunningOnAppliance) return;
|
if (!Debug.IsRunningOnAppliance) return;
|
||||||
@@ -26,3 +32,4 @@ public class DebugCrestronLoggerSink : ILogEventSink
|
|||||||
CrestronLogger.Initialize(1, LoggerModeEnum.RM);
|
CrestronLogger.Initialize(1, LoggerModeEnum.RM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging;
|
namespace PepperDash.Core.Logging
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a DebugErrorLogSink
|
||||||
|
/// </summary>
|
||||||
public class DebugErrorLogSink : ILogEventSink
|
public class DebugErrorLogSink : ILogEventSink
|
||||||
{
|
{
|
||||||
private ITextFormatter _formatter;
|
private ITextFormatter _formatter;
|
||||||
@@ -24,6 +27,9 @@ public class DebugErrorLogSink : ILogEventSink
|
|||||||
{LogEventLevel.Error, (msg) => ErrorLog.Error(msg) },
|
{LogEventLevel.Error, (msg) => ErrorLog.Error(msg) },
|
||||||
{LogEventLevel.Fatal, (msg) => ErrorLog.Error(msg) }
|
{LogEventLevel.Fatal, (msg) => ErrorLog.Error(msg) }
|
||||||
};
|
};
|
||||||
|
/// <summary>
|
||||||
|
/// Emit method
|
||||||
|
/// </summary>
|
||||||
public void Emit(LogEvent logEvent)
|
public void Emit(LogEvent logEvent)
|
||||||
{
|
{
|
||||||
string message;
|
string message;
|
||||||
@@ -62,3 +68,4 @@ public class DebugErrorLogSink : ILogEventSink
|
|||||||
_formatter = formatter;
|
_formatter = formatter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,72 +2,112 @@
|
|||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Log = PepperDash.Core.Debug;
|
using Log = PepperDash.Core.Debug;
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging;
|
namespace PepperDash.Core.Logging
|
||||||
|
{
|
||||||
public static class DebugExtensions
|
public static class DebugExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// LogException method
|
||||||
|
/// </summary>
|
||||||
public static void LogException(this IKeyed device, Exception ex, string message, params object[] args)
|
public static void LogException(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(ex, message, device, args);
|
Log.LogMessage(ex, message, device: device, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogVerbose method
|
||||||
|
/// </summary>
|
||||||
public static void LogVerbose(this IKeyed device, Exception ex, string message, params object[] args)
|
public static void LogVerbose(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Verbose, ex, message, device, args);
|
Log.LogVerbose(ex, device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogVerbose method
|
||||||
|
/// </summary>
|
||||||
public static void LogVerbose(this IKeyed device, string message, params object[] args)
|
public static void LogVerbose(this IKeyed device, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Verbose, device, message, args);
|
Log.LogVerbose(device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogDebug method
|
||||||
|
/// </summary>
|
||||||
public static void LogDebug(this IKeyed device, Exception ex, string message, params object[] args)
|
public static void LogDebug(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Debug, ex, message, device, args);
|
Log.LogDebug(ex, device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogDebug method
|
||||||
|
/// </summary>
|
||||||
public static void LogDebug(this IKeyed device, string message, params object[] args)
|
public static void LogDebug(this IKeyed device, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Debug, device, message, args);
|
Log.LogDebug(device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogInformation method
|
||||||
|
/// </summary>
|
||||||
public static void LogInformation(this IKeyed device, Exception ex, string message, params object[] args)
|
public static void LogInformation(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Information, ex, message, device, args);
|
Log.LogInformation(ex, device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogInformation method
|
||||||
|
/// </summary>
|
||||||
public static void LogInformation(this IKeyed device, string message, params object[] args)
|
public static void LogInformation(this IKeyed device, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Information, device, message, args);
|
Log.LogInformation(device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogWarning method
|
||||||
|
/// </summary>
|
||||||
public static void LogWarning(this IKeyed device, Exception ex, string message, params object[] args)
|
public static void LogWarning(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Warning, ex, message, device, args);
|
Log.LogWarning(ex, device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogWarning method
|
||||||
|
/// </summary>
|
||||||
public static void LogWarning(this IKeyed device, string message, params object[] args)
|
public static void LogWarning(this IKeyed device, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Warning, device, message, args);
|
Log.LogWarning(device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogError method
|
||||||
|
/// </summary>
|
||||||
public static void LogError(this IKeyed device, Exception ex, string message, params object[] args)
|
public static void LogError(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Error, ex, message, device, args);
|
Log.LogError(ex, device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogError method
|
||||||
|
/// </summary>
|
||||||
public static void LogError(this IKeyed device, string message, params object[] args)
|
public static void LogError(this IKeyed device, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Error, device, message, args);
|
Log.LogError(device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogFatal method
|
||||||
|
/// </summary>
|
||||||
public static void LogFatal(this IKeyed device, Exception ex, string message, params object[] args)
|
public static void LogFatal(this IKeyed device, Exception ex, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Fatal, ex, message, device, args);
|
Log.LogFatal(ex, device, message, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LogFatal method
|
||||||
|
/// </summary>
|
||||||
public static void LogFatal(this IKeyed device, string message, params object[] args)
|
public static void LogFatal(this IKeyed device, string message, params object[] args)
|
||||||
{
|
{
|
||||||
Log.LogMessage(LogEventLevel.Fatal, device, message, args);
|
Log.LogFatal(device, message, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,26 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace PepperDash.Core.Logging;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Logging
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class to persist current Debug settings across program restarts
|
/// Represents a DebugContextCollection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DebugContextCollection
|
public class DebugContextCollection
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// To prevent threading issues with the DeviceDebugSettings collection
|
/// To prevent threading issues with the DeviceDebugSettings collection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly object _deviceDebugSettingsLock = new();
|
private readonly CCriticalSection _deviceDebugSettingsLock;
|
||||||
|
|
||||||
[JsonProperty("items")]
|
[JsonProperty("items")] private readonly Dictionary<string, DebugContextItem> _items;
|
||||||
private readonly Dictionary<string, DebugContextItem> _items = new Dictionary<string, DebugContextItem>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Collection of the debug settings for each device where the dictionary key is the device key
|
/// Collection of the debug settings for each device where the dictionary key is the device key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("deviceDebugSettings")]
|
[JsonProperty("deviceDebugSettings")]
|
||||||
private Dictionary<string, object> DeviceDebugSettings { get; set; } = new Dictionary<string, object>();
|
private Dictionary<string, object> DeviceDebugSettings { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -32,7 +28,9 @@ public class DebugContextCollection
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DebugContextCollection()
|
public DebugContextCollection()
|
||||||
{
|
{
|
||||||
|
_deviceDebugSettingsLock = new CCriticalSection();
|
||||||
|
DeviceDebugSettings = new Dictionary<string, object>();
|
||||||
|
_items = new Dictionary<string, DebugContextItem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -73,10 +71,15 @@ public class DebugContextCollection
|
|||||||
/// <param name="deviceKey"></param>
|
/// <param name="deviceKey"></param>
|
||||||
/// <param name="settings"></param>
|
/// <param name="settings"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// SetDebugSettingsForKey method
|
||||||
|
/// </summary>
|
||||||
public void SetDebugSettingsForKey(string deviceKey, object settings)
|
public void SetDebugSettingsForKey(string deviceKey, object settings)
|
||||||
{
|
{
|
||||||
lock (_deviceDebugSettingsLock)
|
try
|
||||||
{
|
{
|
||||||
|
_deviceDebugSettingsLock.Enter();
|
||||||
|
|
||||||
if (DeviceDebugSettings.ContainsKey(deviceKey))
|
if (DeviceDebugSettings.ContainsKey(deviceKey))
|
||||||
{
|
{
|
||||||
DeviceDebugSettings[deviceKey] = settings;
|
DeviceDebugSettings[deviceKey] = settings;
|
||||||
@@ -84,6 +87,10 @@ public class DebugContextCollection
|
|||||||
else
|
else
|
||||||
DeviceDebugSettings.Add(deviceKey, settings);
|
DeviceDebugSettings.Add(deviceKey, settings);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_deviceDebugSettingsLock.Leave();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -91,6 +98,9 @@ public class DebugContextCollection
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="deviceKey"></param>
|
/// <param name="deviceKey"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
/// <summary>
|
||||||
|
/// GetDebugSettingsForKey method
|
||||||
|
/// </summary>
|
||||||
public object GetDebugSettingsForKey(string deviceKey)
|
public object GetDebugSettingsForKey(string deviceKey)
|
||||||
{
|
{
|
||||||
return DeviceDebugSettings[deviceKey];
|
return DeviceDebugSettings[deviceKey];
|
||||||
@@ -114,3 +124,4 @@ public class DebugContextItem
|
|||||||
[JsonProperty("doNotLoadOnNextBoot")]
|
[JsonProperty("doNotLoadOnNextBoot")]
|
||||||
public bool DoNotLoadOnNextBoot { get; set; }
|
public bool DoNotLoadOnNextBoot { get; set; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +1,37 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System;
|
using System.Linq;
|
||||||
using Crestron.SimplSharp;
|
using System.Text;
|
||||||
using Org.BouncyCastle.Asn1.X509;
|
using System.Threading.Tasks;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Configuration;
|
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
using Serilog.Formatting;
|
using Serilog.Configuration;
|
||||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
|
||||||
using Serilog.Formatting.Json;
|
|
||||||
using System.IO;
|
|
||||||
using System.Security.Authentication;
|
|
||||||
using WebSocketSharp;
|
|
||||||
using WebSocketSharp.Server;
|
using WebSocketSharp.Server;
|
||||||
using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
|
using Crestron.SimplSharp;
|
||||||
|
using WebSocketSharp;
|
||||||
|
using System.Security.Authentication;
|
||||||
using WebSocketSharp.Net;
|
using WebSocketSharp.Net;
|
||||||
|
using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
|
||||||
|
using System.IO;
|
||||||
|
using Org.BouncyCastle.Asn1.X509;
|
||||||
|
using Serilog.Formatting;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Serilog.Formatting.Json;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected
|
/// Represents a DebugWebsocketSink
|
||||||
/// WebSocket clients.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This class implements the <see cref="ILogEventSink"/> interface and is designed to send
|
public class DebugWebsocketSink : ILogEventSink
|
||||||
/// formatted log events to WebSocket clients connected to a secure WebSocket server. The server is hosted locally
|
|
||||||
/// and uses a self-signed certificate for SSL/TLS encryption.</remarks>
|
|
||||||
public class DebugWebsocketSink : ILogEventSink, IKeyed
|
|
||||||
{
|
{
|
||||||
private HttpServer _httpsServer;
|
private HttpServer _httpsServer;
|
||||||
|
|
||||||
private readonly string _path = "/debug/join/";
|
private string _path = "/debug/join/";
|
||||||
private const string _certificateName = "selfCres";
|
private const string _certificateName = "selfCres";
|
||||||
private const string _certificatePassword = "cres12345";
|
private const string _certificatePassword = "cres12345";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the port number on which the HTTPS server is currently running.
|
|
||||||
/// </summary>
|
|
||||||
public int Port
|
public int Port
|
||||||
{ get
|
{ get
|
||||||
{
|
{
|
||||||
@@ -46,11 +41,6 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the WebSocket URL for the current server instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>The URL is dynamically constructed based on the server's current IP address, port,
|
|
||||||
/// and WebSocket path.</remarks>
|
|
||||||
public string Url
|
public string Url
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -61,30 +51,20 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the HTTPS server is currently listening for incoming connections.
|
/// Gets or sets the IsRunning
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
|
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string Key => "DebugWebsocketSink";
|
|
||||||
|
|
||||||
private readonly ITextFormatter _textFormatter;
|
private readonly ITextFormatter _textFormatter;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="DebugWebsocketSink"/> class with the specified text formatter.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This constructor initializes the WebSocket sink and ensures that a certificate is
|
|
||||||
/// available for secure communication. If the required certificate does not exist, it will be created
|
|
||||||
/// automatically. Additionally, the sink is configured to stop the server when the program is
|
|
||||||
/// stopping.</remarks>
|
|
||||||
/// <param name="formatProvider">The text formatter used to format log messages. If null, a default JSON formatter is used.</param>
|
|
||||||
public DebugWebsocketSink(ITextFormatter formatProvider)
|
public DebugWebsocketSink(ITextFormatter formatProvider)
|
||||||
{
|
{
|
||||||
|
|
||||||
_textFormatter = formatProvider ?? new JsonFormatter();
|
_textFormatter = formatProvider ?? new JsonFormatter();
|
||||||
|
|
||||||
if (!File.Exists($"\\user\\{_certificateName}.pfx"))
|
if (!File.Exists($"\\user\\{_certificateName}.pfx"))
|
||||||
CreateCert();
|
CreateCert(null);
|
||||||
|
|
||||||
CrestronEnvironment.ProgramStatusEventHandler += type =>
|
CrestronEnvironment.ProgramStatusEventHandler += type =>
|
||||||
{
|
{
|
||||||
@@ -95,41 +75,45 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CreateCert()
|
private void CreateCert(string[] args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
//Debug.Console(0,"CreateCert Creating Utility");
|
||||||
|
CrestronConsole.PrintLine("CreateCert Creating Utility");
|
||||||
|
//var utility = new CertificateUtility();
|
||||||
var utility = new BouncyCertificate();
|
var utility = new BouncyCertificate();
|
||||||
|
//Debug.Console(0, "CreateCert Calling CreateCert");
|
||||||
|
CrestronConsole.PrintLine("CreateCert Calling CreateCert");
|
||||||
|
//utility.CreateCert();
|
||||||
var ipAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
|
var ipAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
|
||||||
var hostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
|
var hostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
|
||||||
var domainName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 0);
|
var domainName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 0);
|
||||||
|
|
||||||
Debug.LogInformation("DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress);
|
//Debug.Console(0, "DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress);
|
||||||
|
CrestronConsole.PrintLine(string.Format("DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress));
|
||||||
var certificate = utility.CreateSelfSignedCertificate(string.Format("CN={0}.{1}", hostName, domainName), [string.Format("{0}.{1}", hostName, domainName), ipAddress], [KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth]);
|
|
||||||
|
|
||||||
|
var certificate = utility.CreateSelfSignedCertificate(string.Format("CN={0}.{1}", hostName, domainName), new[] { string.Format("{0}.{1}", hostName, domainName), ipAddress }, new[] { KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth });
|
||||||
//Crestron fails to let us do this...perhaps it should be done through their Dll's but haven't tested
|
//Crestron fails to let us do this...perhaps it should be done through their Dll's but haven't tested
|
||||||
|
//Debug.Print($"CreateCert Storing Certificate To My.LocalMachine");
|
||||||
var separator = Path.DirectorySeparatorChar;
|
//utility.AddCertToStore(certificate, StoreName.My, StoreLocation.LocalMachine);
|
||||||
|
//Debug.Console(0, "CreateCert Saving Cert to \\user\\");
|
||||||
|
CrestronConsole.PrintLine("CreateCert Saving Cert to \\user\\");
|
||||||
utility.CertificatePassword = _certificatePassword;
|
utility.CertificatePassword = _certificatePassword;
|
||||||
utility.WriteCertificate(certificate, @$"{separator}user{separator}", _certificateName);
|
utility.WriteCertificate(certificate, @"\user\", _certificateName);
|
||||||
|
//Debug.Console(0, "CreateCert Ending CreateCert");
|
||||||
|
CrestronConsole.PrintLine("CreateCert Ending CreateCert");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogError(ex, "WSS CreateCert Failed: {0}", ex.Message);
|
//Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace);
|
||||||
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
CrestronConsole.PrintLine(string.Format("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a log event to all connected WebSocket clients.
|
/// Emit method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>The log event is formatted using the configured text formatter and then broadcasted
|
|
||||||
/// to all clients connected to the WebSocket server. If the WebSocket server is not initialized or not
|
|
||||||
/// listening, the method exits without performing any action.</remarks>
|
|
||||||
/// <param name="logEvent">The log event to be formatted and broadcasted. Cannot be null.</param>
|
|
||||||
public void Emit(LogEvent logEvent)
|
public void Emit(LogEvent logEvent)
|
||||||
{
|
{
|
||||||
if (_httpsServer == null || !_httpsServer.IsListening) return;
|
if (_httpsServer == null || !_httpsServer.IsListening) return;
|
||||||
@@ -137,19 +121,16 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
|||||||
var sw = new StringWriter();
|
var sw = new StringWriter();
|
||||||
_textFormatter.Format(logEvent, sw);
|
_textFormatter.Format(logEvent, sw);
|
||||||
|
|
||||||
_httpsServer.WebSocketServices[_path].Sessions.Broadcast(sw.ToString());
|
_httpsServer.WebSocketServices.Broadcast(sw.ToString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts the WebSocket server on the specified port and configures it with the appropriate certificate.
|
/// StartServerAndSetPort method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This method initializes the WebSocket server and binds it to the specified port. It
|
|
||||||
/// also applies the server's certificate for secure communication. Ensure that the port is not already in use
|
|
||||||
/// and that the certificate file is accessible.</remarks>
|
|
||||||
/// <param name="port">The port number on which the WebSocket server will listen. Must be a valid, non-negative port number.</param>
|
|
||||||
public void StartServerAndSetPort(int port)
|
public void StartServerAndSetPort(int port)
|
||||||
{
|
{
|
||||||
Debug.LogInformation("Starting Websocket Server on port: {0}", port);
|
Debug.Console(0, "Starting Websocket Server on port: {0}", port);
|
||||||
|
|
||||||
|
|
||||||
Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
|
Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
|
||||||
@@ -161,24 +142,26 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
|||||||
{
|
{
|
||||||
_httpsServer = new HttpServer(port, true);
|
_httpsServer = new HttpServer(port, true);
|
||||||
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(certPath))
|
if (!string.IsNullOrWhiteSpace(certPath))
|
||||||
{
|
{
|
||||||
Debug.LogInformation("Assigning SSL Configuration");
|
Debug.Console(0, "Assigning SSL Configuration");
|
||||||
|
_httpsServer.SslConfiguration = new ServerSslConfiguration(new X509Certificate2(certPath, certPassword))
|
||||||
_httpsServer.SslConfiguration.ServerCertificate = new X509Certificate2(certPath, certPassword);
|
|
||||||
_httpsServer.SslConfiguration.ClientCertificateRequired = false;
|
|
||||||
_httpsServer.SslConfiguration.CheckCertificateRevocation = false;
|
|
||||||
_httpsServer.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;
|
|
||||||
//this is just to test, you might want to actually validate
|
|
||||||
_httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
|
||||||
{
|
{
|
||||||
Debug.LogInformation("HTTPS ClientCerticateValidation Callback triggered");
|
ClientCertificateRequired = false,
|
||||||
|
CheckCertificateRevocation = false,
|
||||||
|
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
|
||||||
|
//this is just to test, you might want to actually validate
|
||||||
|
ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
||||||
|
{
|
||||||
|
Debug.Console(0, "HTTPS ClientCerticateValidation Callback triggered");
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Debug.LogInformation("Adding Debug Client Service");
|
Debug.Console(0, "Adding Debug Client Service");
|
||||||
_httpsServer.AddWebSocketService<DebugClient>(_path);
|
_httpsServer.AddWebSocketService<DebugClient>(_path);
|
||||||
Debug.LogInformation("Assigning Log Info");
|
Debug.Console(0, "Assigning Log Info");
|
||||||
_httpsServer.Log.Level = LogLevel.Trace;
|
_httpsServer.Log.Level = LogLevel.Trace;
|
||||||
_httpsServer.Log.Output = (d, s) =>
|
_httpsServer.Log.Output = (d, s) =>
|
||||||
{
|
{
|
||||||
@@ -208,49 +191,37 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
|||||||
level = 4;
|
level = 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Debug.LogInformation("{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
|
|
||||||
|
Debug.Console(level, "{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
|
||||||
};
|
};
|
||||||
Debug.LogInformation("Starting");
|
Debug.Console(0, "Starting");
|
||||||
|
|
||||||
_httpsServer.Start();
|
_httpsServer.Start();
|
||||||
Debug.LogInformation("Ready");
|
Debug.Console(0, "Ready");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.LogError(ex, "WebSocket Failed to start {0}", ex.Message);
|
Debug.Console(0, "WebSocket Failed to start {0}", ex.Message);
|
||||||
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops the WebSocket server if it is currently running.
|
/// StopServer method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This method halts the WebSocket server and releases any associated resources. After
|
|
||||||
/// calling this method, the server will no longer accept or process incoming connections.</remarks>
|
|
||||||
public void StopServer()
|
public void StopServer()
|
||||||
{
|
{
|
||||||
Debug.LogInformation("Stopping Websocket Server");
|
Debug.Console(0, "Stopping Websocket Server");
|
||||||
_httpsServer?.Stop();
|
_httpsServer?.Stop();
|
||||||
|
|
||||||
_httpsServer = null;
|
_httpsServer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configures the logger to write log events to a debug WebSocket sink.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This extension method allows you to direct log events to a WebSocket sink for debugging
|
|
||||||
/// purposes.</remarks>
|
|
||||||
public static class DebugWebsocketSinkExtensions
|
public static class DebugWebsocketSinkExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures a logger to write log events to a debug WebSocket sink.
|
/// DebugWebsocketSink method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This method adds a sink that writes log events to a WebSocket for debugging purposes.
|
|
||||||
/// It is typically used during development to stream log events in real-time.</remarks>
|
|
||||||
/// <param name="loggerConfiguration">The logger sink configuration to apply the WebSocket sink to.</param>
|
|
||||||
/// <param name="formatProvider">An optional text formatter to format the log events. If not provided, a default formatter will be used.</param>
|
|
||||||
/// <returns>A <see cref="LoggerConfiguration"/> object that can be used to further configure the logger.</returns>
|
|
||||||
public static LoggerConfiguration DebugWebsocketSink(
|
public static LoggerConfiguration DebugWebsocketSink(
|
||||||
this LoggerSinkConfiguration loggerConfiguration,
|
this LoggerSinkConfiguration loggerConfiguration,
|
||||||
ITextFormatter formatProvider = null)
|
ITextFormatter formatProvider = null)
|
||||||
@@ -260,19 +231,12 @@ public static class DebugWebsocketSinkExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a WebSocket client for debugging purposes, providing connection lifecycle management and message
|
/// Represents a DebugClient
|
||||||
/// handling functionality.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>The <see cref="DebugClient"/> class extends <see cref="WebSocketBehavior"/> to handle
|
|
||||||
/// WebSocket connections, including events for opening, closing, receiving messages, and errors. It tracks the
|
|
||||||
/// duration of the connection and logs relevant events for debugging.</remarks>
|
|
||||||
public class DebugClient : WebSocketBehavior
|
public class DebugClient : WebSocketBehavior
|
||||||
{
|
{
|
||||||
private DateTime _connectionTime;
|
private DateTime _connectionTime;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the duration of time the WebSocket connection has been active.
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan ConnectedDuration
|
public TimeSpan ConnectedDuration
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -288,47 +252,41 @@ public class DebugClient : WebSocketBehavior
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="DebugClient"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public DebugClient()
|
public DebugClient()
|
||||||
{
|
{
|
||||||
Debug.LogInformation("DebugClient Created");
|
Debug.Console(0, "DebugClient Created");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void OnOpen()
|
protected override void OnOpen()
|
||||||
{
|
{
|
||||||
base.OnOpen();
|
base.OnOpen();
|
||||||
|
|
||||||
var url = Context.WebSocket.Url;
|
var url = Context.WebSocket.Url;
|
||||||
Debug.LogInformation("New WebSocket Connection from: {0}", url);
|
Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url);
|
||||||
|
|
||||||
_connectionTime = DateTime.Now;
|
_connectionTime = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void OnMessage(MessageEventArgs e)
|
protected override void OnMessage(MessageEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnMessage(e);
|
base.OnMessage(e);
|
||||||
|
|
||||||
Debug.LogVerbose("WebSocket UiClient Message: {0}", e.Data);
|
Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void OnClose(CloseEventArgs e)
|
protected override void OnClose(CloseEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnClose(e);
|
base.OnClose(e);
|
||||||
|
|
||||||
Debug.LogDebug("WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
|
Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected override void OnError(WebSocketSharp.ErrorEventArgs e)
|
protected override void OnError(WebSocketSharp.ErrorEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnError(e);
|
base.OnError(e);
|
||||||
|
|
||||||
Debug.LogError(e.Exception, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
|
Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
|
||||||
Debug.LogVerbose("Stack Trace:\r{0}", e.Exception.StackTrace);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Not in use
|
/// Not in use
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -18,3 +18,5 @@ namespace PepperDash.Core;
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement;
|
namespace PepperDash.Core.PasswordManagement
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// JSON password configuration
|
/// JSON password configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -23,3 +23,4 @@ namespace PepperDash.Core.PasswordManagement;
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement;
|
namespace PepperDash.Core.PasswordManagement
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constants
|
/// Constants
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -54,3 +54,4 @@ namespace PepperDash.Core.PasswordManagement;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const ushort StringValueChange = 201;
|
public const ushort StringValueChange = 201;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement;
|
namespace PepperDash.Core.PasswordManagement
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class to allow user interaction with the PasswordManager
|
/// Represents a PasswordClient
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PasswordClient
|
public class PasswordClient
|
||||||
{
|
{
|
||||||
@@ -193,3 +193,4 @@ namespace PepperDash.Core.PasswordManagement;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Timers;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.PasswordManagement;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core.PasswordManagement
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allows passwords to be stored and managed
|
/// Represents a PasswordManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PasswordManager
|
public class PasswordManager
|
||||||
{
|
{
|
||||||
@@ -21,7 +21,7 @@ public class PasswordManager
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timer used to wait until password changes have stopped before updating the dictionary
|
/// Timer used to wait until password changes have stopped before updating the dictionary
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Timer PasswordTimer;
|
CTimer PasswordTimer;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timer length
|
/// Timer length
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -79,7 +79,7 @@ public class PasswordManager
|
|||||||
// validate the parameters
|
// validate the parameters
|
||||||
if (key > 0 && string.IsNullOrEmpty(password))
|
if (key > 0 && string.IsNullOrEmpty(password))
|
||||||
{
|
{
|
||||||
Debug.LogDebug("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password);
|
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,52 +92,47 @@ public class PasswordManager
|
|||||||
else
|
else
|
||||||
_passwords.Add(key, password);
|
_passwords.Add(key, password);
|
||||||
|
|
||||||
Debug.LogDebug("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key]);
|
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key]));
|
||||||
|
|
||||||
if (PasswordTimer == null)
|
if (PasswordTimer == null)
|
||||||
{
|
{
|
||||||
PasswordTimer = new Timer(PasswordTimerElapsedMs) { AutoReset = false };
|
PasswordTimer = new CTimer((o) => PasswordTimerElapsed(), PasswordTimerElapsedMs);
|
||||||
PasswordTimer.Elapsed += (s, e) => PasswordTimerElapsed(s, e);
|
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Started"));
|
||||||
PasswordTimer.Start();
|
|
||||||
Debug.LogDebug("PasswordManager.UpdatePassword: Timer Started");
|
|
||||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PasswordTimer.Stop();
|
PasswordTimer.Reset(PasswordTimerElapsedMs);
|
||||||
PasswordTimer.Interval = PasswordTimerElapsedMs;
|
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Reset"));
|
||||||
PasswordTimer.Start();
|
|
||||||
Debug.LogDebug("PasswordManager.UpdatePassword: Timer Reset");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e);
|
var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e);
|
||||||
Debug.LogError(e, msg);
|
Debug.Console(1, msg);
|
||||||
Debug.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timer callback function
|
/// CTimer callback function
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void PasswordTimerElapsed(object sender, ElapsedEventArgs e)
|
private void PasswordTimerElapsed()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PasswordTimer.Stop();
|
PasswordTimer.Stop();
|
||||||
Debug.LogDebug("PasswordManager.PasswordTimerElapsed: Timer Stopped");
|
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: CTimer Stopped"));
|
||||||
OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
||||||
foreach (var pw in _passwords)
|
foreach (var pw in _passwords)
|
||||||
{
|
{
|
||||||
// if key exists, continue
|
// if key exists, continue
|
||||||
if (Passwords.ContainsKey(pw.Key))
|
if (Passwords.ContainsKey(pw.Key))
|
||||||
{
|
{
|
||||||
Debug.LogDebug("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value);
|
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value));
|
||||||
if (Passwords[pw.Key] != _passwords[pw.Key])
|
if (Passwords[pw.Key] != _passwords[pw.Key])
|
||||||
{
|
{
|
||||||
Passwords[pw.Key] = _passwords[pw.Key];
|
Passwords[pw.Key] = _passwords[pw.Key];
|
||||||
Debug.LogDebug("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key]);
|
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key]));
|
||||||
OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange);
|
OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,11 +144,10 @@ public class PasswordManager
|
|||||||
}
|
}
|
||||||
OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
|
OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", ex.Message);
|
var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", e);
|
||||||
Debug.LogError(ex, msg);
|
Debug.Console(1, msg);
|
||||||
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,12 +168,12 @@ public class PasswordManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void ListPasswords()
|
public void ListPasswords()
|
||||||
{
|
{
|
||||||
Debug.LogInformation("PasswordManager.ListPasswords:\r");
|
Debug.Console(0, "PasswordManager.ListPasswords:\r");
|
||||||
foreach (var pw in Passwords)
|
foreach (var pw in Passwords)
|
||||||
Debug.LogInformation("Passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
Debug.Console(0, "Passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
||||||
Debug.LogInformation("\n");
|
Debug.Console(0, "\n");
|
||||||
foreach (var pw in _passwords)
|
foreach (var pw in _passwords)
|
||||||
Debug.LogInformation("_passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
Debug.Console(0, "_passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -250,3 +244,4 @@ public class PasswordManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<RootNamespace>PepperDash.Core</RootNamespace>
|
<RootNamespace>PepperDash.Core</RootNamespace>
|
||||||
<AssemblyName>PepperDashCore</AssemblyName>
|
<AssemblyName>PepperDashCore</AssemblyName>
|
||||||
<TargetFramework>net8</TargetFramework>
|
<TargetFramework>net472</TargetFramework>
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
<NeutralLanguage>en</NeutralLanguage>
|
<NeutralLanguage>en</NeutralLanguage>
|
||||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||||
@@ -37,18 +37,23 @@
|
|||||||
<None Remove="Properties\**" />
|
<None Remove="Properties\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.1" />
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
<PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.128" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4">
|
<Reference Include="System.Net.Http" />
|
||||||
<Aliases>global,NewtonsoftJson</Aliases>
|
</ItemGroup>
|
||||||
</PackageReference>
|
<ItemGroup>
|
||||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />
|
||||||
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
|
<PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.90" />
|
||||||
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Expressions" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
<PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" />
|
||||||
<PackageReference Include="SSH.NET" Version="2025.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||||
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
|
<PackageReference Include="SSH.NET" Version="2024.2.0" />
|
||||||
|
<PackageReference Include="WebSocketSharp" Version="1.0.3-rc11" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net6'">
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Comm\._GenericSshClient.cs" />
|
<Compile Remove="Comm\._GenericSshClient.cs" />
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.SystemInfo;
|
namespace PepperDash.Core.SystemInfo
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constants
|
/// Constants
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -261,3 +261,4 @@ namespace PepperDash.Core.SystemInfo;
|
|||||||
Index = index;
|
Index = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.SystemInfo;
|
namespace PepperDash.Core.SystemInfo
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processor info class
|
/// Processor info class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -201,3 +201,4 @@ namespace PepperDash.Core.SystemInfo;
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
namespace PepperDash.Core.SystemInfo;
|
namespace PepperDash.Core.SystemInfo
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// System Info class
|
/// System Info class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -468,3 +468,4 @@ namespace PepperDash.Core.SystemInfo;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -20,8 +20,8 @@ using Org.BouncyCastle.Crypto.Operators;
|
|||||||
using BigInteger = Org.BouncyCastle.Math.BigInteger;
|
using BigInteger = Org.BouncyCastle.Math.BigInteger;
|
||||||
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
|
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
|
||||||
|
|
||||||
namespace PepperDash.Core;
|
namespace PepperDash.Core
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Taken From https://github.com/rlipscombe/bouncy-castle-csharp/
|
/// Taken From https://github.com/rlipscombe/bouncy-castle-csharp/
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -35,6 +35,9 @@ internal class BouncyCertificate
|
|||||||
return issuerCertificate;
|
return issuerCertificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// IssueCertificate method
|
||||||
|
/// </summary>
|
||||||
public X509Certificate2 IssueCertificate(string subjectName, X509Certificate2 issuerCertificate, string[] subjectAlternativeNames, KeyPurposeID[] usages)
|
public X509Certificate2 IssueCertificate(string subjectName, X509Certificate2 issuerCertificate, string[] subjectAlternativeNames, KeyPurposeID[] usages)
|
||||||
{
|
{
|
||||||
// It's self-signed, so these are the same.
|
// It's self-signed, so these are the same.
|
||||||
@@ -56,6 +59,9 @@ internal class BouncyCertificate
|
|||||||
return ConvertCertificate(certificate, subjectKeyPair, random);
|
return ConvertCertificate(certificate, subjectKeyPair, random);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CreateCertificateAuthorityCertificate method
|
||||||
|
/// </summary>
|
||||||
public X509Certificate2 CreateCertificateAuthorityCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
|
public X509Certificate2 CreateCertificateAuthorityCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
|
||||||
{
|
{
|
||||||
// It's self-signed, so these are the same.
|
// It's self-signed, so these are the same.
|
||||||
@@ -78,6 +84,9 @@ internal class BouncyCertificate
|
|||||||
return ConvertCertificate(certificate, subjectKeyPair, random);
|
return ConvertCertificate(certificate, subjectKeyPair, random);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CreateSelfSignedCertificate method
|
||||||
|
/// </summary>
|
||||||
public X509Certificate2 CreateSelfSignedCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
|
public X509Certificate2 CreateSelfSignedCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
|
||||||
{
|
{
|
||||||
// It's self-signed, so these are the same.
|
// It's self-signed, so these are the same.
|
||||||
@@ -305,6 +314,9 @@ internal class BouncyCertificate
|
|||||||
return convertedCertificate;
|
return convertedCertificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// WriteCertificate method
|
||||||
|
/// </summary>
|
||||||
public void WriteCertificate(X509Certificate2 certificate, string outputDirectory, string certName)
|
public void WriteCertificate(X509Certificate2 certificate, string outputDirectory, string certName)
|
||||||
{
|
{
|
||||||
// This password is the one attached to the PFX file. Use 'null' for no password.
|
// This password is the one attached to the PFX file. Use 'null' for no password.
|
||||||
@@ -332,6 +344,9 @@ internal class BouncyCertificate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// AddCertToStore method
|
||||||
|
/// </summary>
|
||||||
public bool AddCertToStore(X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl)
|
public bool AddCertToStore(X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl)
|
||||||
{
|
{
|
||||||
bool bRet = false;
|
bool bRet = false;
|
||||||
@@ -353,3 +368,4 @@ internal class BouncyCertificate
|
|||||||
return bRet;
|
return bRet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using Crestron.SimplSharp.WebScripting;
|
using Crestron.SimplSharp.WebScripting;
|
||||||
|
|
||||||
namespace PepperDash.Core.Web.RequestHandlers;
|
namespace PepperDash.Core.Web.RequestHandlers
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Web API default request handler
|
/// Represents a DefaultRequestHandler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DefaultRequestHandler : WebApiBaseRequestHandler
|
public class DefaultRequestHandler : WebApiBaseRequestHandler
|
||||||
{
|
{
|
||||||
@@ -14,3 +14,4 @@ namespace PepperDash.Core.Web.RequestHandlers;
|
|||||||
: base(true)
|
: base(true)
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace PepperDash.Core.Web.RequestHandlers;
|
namespace PepperDash.Core.Web.RequestHandlers
|
||||||
|
{
|
||||||
public abstract class WebApiBaseRequestAsyncHandler:IHttpCwsHandler
|
public abstract class WebApiBaseRequestAsyncHandler:IHttpCwsHandler
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, Func<HttpCwsContext, Task>> _handlers;
|
private readonly Dictionary<string, Func<HttpCwsContext, Task>> _handlers;
|
||||||
@@ -142,6 +142,9 @@ public abstract class WebApiBaseRequestAsyncHandler:IHttpCwsHandler
|
|||||||
/// Process request
|
/// Process request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// ProcessRequest method
|
||||||
|
/// </summary>
|
||||||
public void ProcessRequest(HttpCwsContext context)
|
public void ProcessRequest(HttpCwsContext context)
|
||||||
{
|
{
|
||||||
if (!_handlers.TryGetValue(context.Request.HttpMethod, out Func<HttpCwsContext, Task> handler))
|
if (!_handlers.TryGetValue(context.Request.HttpMethod, out Func<HttpCwsContext, Task> handler))
|
||||||
@@ -160,3 +163,4 @@ public abstract class WebApiBaseRequestAsyncHandler:IHttpCwsHandler
|
|||||||
handlerTask.GetAwaiter().GetResult();
|
handlerTask.GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Crestron.SimplSharp.WebScripting;
|
using Crestron.SimplSharp.WebScripting;
|
||||||
|
|
||||||
namespace PepperDash.Core.Web.RequestHandlers;
|
namespace PepperDash.Core.Web.RequestHandlers
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CWS Base Handler, implements IHttpCwsHandler
|
/// CWS Base Handler, implements IHttpCwsHandler
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -165,3 +165,4 @@ namespace PepperDash.Core.Web.RequestHandlers;
|
|||||||
handler(context);
|
handler(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,14 @@
|
|||||||
extern alias NewtonsoftJson;
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.WebScripting;
|
using Crestron.SimplSharp.WebScripting;
|
||||||
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
using Newtonsoft.Json;
|
||||||
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
|
using Newtonsoft.Json.Linq;
|
||||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
|
||||||
using PepperDash.Core.Web.RequestHandlers;
|
using PepperDash.Core.Web.RequestHandlers;
|
||||||
using PepperDash.Core.Logging;
|
|
||||||
|
|
||||||
namespace PepperDash.Core.Web;
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core.Web
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Web API server
|
/// Web API server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -23,7 +18,11 @@ public class WebApiServer : IKeyName
|
|||||||
private const string DefaultName = "Web API Server";
|
private const string DefaultName = "Web API Server";
|
||||||
private const string DefaultBasePath = "/api";
|
private const string DefaultBasePath = "/api";
|
||||||
|
|
||||||
private readonly object _serverLock = new();
|
private const uint DebugTrace = 0;
|
||||||
|
private const uint DebugInfo = 1;
|
||||||
|
private const uint DebugVerbose = 2;
|
||||||
|
|
||||||
|
private readonly CCriticalSection _serverLock = new CCriticalSection();
|
||||||
private HttpCwsServer _server;
|
private HttpCwsServer _server;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -46,6 +45,28 @@ public class WebApiServer : IKeyName
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsRegistered { get; private set; }
|
public bool IsRegistered { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Http request handler
|
||||||
|
/// </summary>
|
||||||
|
//public IHttpCwsHandler HttpRequestHandler
|
||||||
|
//{
|
||||||
|
// get { return _server.HttpRequestHandler; }
|
||||||
|
// set
|
||||||
|
// {
|
||||||
|
// if (_server == null) return;
|
||||||
|
// _server.HttpRequestHandler = value;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Received request event handler
|
||||||
|
/// </summary>
|
||||||
|
//public event EventHandler<HttpCwsRequestEventArgs> ReceivedRequestEvent
|
||||||
|
//{
|
||||||
|
// add { _server.ReceivedRequestEvent += new HttpCwsRequestEventHandler(value); }
|
||||||
|
// remove { _server.ReceivedRequestEvent -= new HttpCwsRequestEventHandler(value); }
|
||||||
|
//}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor for S+. Make sure to set necessary properties using init method
|
/// Constructor for S+. Make sure to set necessary properties using init method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -93,7 +114,7 @@ public class WebApiServer : IKeyName
|
|||||||
{
|
{
|
||||||
if (programEventType != eProgramStatusEventType.Stopping) return;
|
if (programEventType != eProgramStatusEventType.Stopping) return;
|
||||||
|
|
||||||
this.LogInformation("Program stopping. stopping server");
|
Debug.Console(DebugInfo, this, "Program stopping. stopping server");
|
||||||
|
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
@@ -107,11 +128,11 @@ public class WebApiServer : IKeyName
|
|||||||
// Re-enable the server if the link comes back up and the status should be connected
|
// Re-enable the server if the link comes back up and the status should be connected
|
||||||
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered)
|
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered)
|
||||||
{
|
{
|
||||||
this.LogInformation("Ethernet link up. Server is already registered.");
|
Debug.Console(DebugInfo, this, "Ethernet link up. Server is alreedy registered.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.LogInformation("Ethernet link up. Starting server");
|
Debug.Console(DebugInfo, this, "Ethernet link up. Starting server");
|
||||||
|
|
||||||
Start();
|
Start();
|
||||||
}
|
}
|
||||||
@@ -132,7 +153,7 @@ public class WebApiServer : IKeyName
|
|||||||
{
|
{
|
||||||
if (route == null)
|
if (route == null)
|
||||||
{
|
{
|
||||||
this.LogWarning("Failed to add route, route parameter is null");
|
Debug.Console(DebugInfo, this, "Failed to add route, route parameter is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +172,7 @@ public class WebApiServer : IKeyName
|
|||||||
{
|
{
|
||||||
if (route == null)
|
if (route == null)
|
||||||
{
|
{
|
||||||
this.LogWarning("Failed to remove route, route parameter is null");
|
Debug.Console(DebugInfo, this, "Failed to remote route, orute parameter is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,62 +191,73 @@ public class WebApiServer : IKeyName
|
|||||||
/// Starts CWS instance
|
/// Starts CWS instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
|
||||||
lock (_serverLock)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_serverLock.Enter();
|
||||||
|
|
||||||
if (_server == null)
|
if (_server == null)
|
||||||
{
|
{
|
||||||
this.LogDebug("Server is null, unable to start");
|
Debug.Console(DebugInfo, this, "Server is null, unable to start");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsRegistered)
|
if (IsRegistered)
|
||||||
{
|
{
|
||||||
this.LogDebug("Server has already been started");
|
Debug.Console(DebugInfo, this, "Server has already been started");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsRegistered = _server.Register();
|
IsRegistered = _server.Register();
|
||||||
|
|
||||||
this.LogDebug("Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
|
Debug.Console(DebugInfo, this, "Starting server, registration {0}", IsRegistered ? "was successful" : "failed");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Start Exception Message: {0}", ex.Message);
|
Debug.Console(DebugInfo, this, "Start Exception Message: {0}", ex.Message);
|
||||||
this.LogVerbose("Start Exception StackTrace: {0}", ex.StackTrace);
|
Debug.Console(DebugVerbose, this, "Start Exception StackTrace: {0}", ex.StackTrace);
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
Debug.Console(DebugVerbose, this, "Start Exception InnerException: {0}", ex.InnerException);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_serverLock.Leave();
|
||||||
}
|
}
|
||||||
} // end lock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stop method
|
/// Stop method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
|
||||||
lock (_serverLock)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_serverLock.Enter();
|
||||||
|
|
||||||
if (_server == null)
|
if (_server == null)
|
||||||
{
|
{
|
||||||
this.LogDebug("Server is null or has already been stopped");
|
Debug.Console(DebugInfo, this, "Server is null or has already been stopped");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IsRegistered = _server.Unregister() == false;
|
IsRegistered = _server.Unregister() == false;
|
||||||
|
|
||||||
this.LogDebug("Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful");
|
Debug.Console(DebugInfo, this, "Stopping server, unregistration {0}", IsRegistered ? "failed" : "was successful");
|
||||||
|
|
||||||
_server.Dispose();
|
_server.Dispose();
|
||||||
_server = null;
|
_server = null;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "Server Stop Exception Message: {0}", ex.Message);
|
Debug.Console(DebugInfo, this, "Server Stop Exception Message: {0}", ex.Message);
|
||||||
|
Debug.Console(DebugVerbose, this, "Server Stop Exception StackTrace: {0}", ex.StackTrace);
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
Debug.Console(DebugVerbose, this, "Server Stop Exception InnerException: {0}", ex.InnerException);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_serverLock.Leave();
|
||||||
}
|
}
|
||||||
} // end lock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -241,12 +273,15 @@ public class WebApiServer : IKeyName
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
|
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
|
||||||
this.LogVerbose("RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
|
Debug.Console(DebugVerbose, this, "RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogException(ex, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
|
Debug.Console(DebugInfo, this, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
|
||||||
this.LogVerbose("ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
|
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception InnerException: {0}", ex.InnerException);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
87
src/PepperDash.Core/WebApi/Presets/Preset.cs
Normal file
87
src/PepperDash.Core/WebApi/Presets/Preset.cs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.WebApi.Presets
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a Preset
|
||||||
|
/// </summary>
|
||||||
|
public class Preset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ID of preset
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the UserId
|
||||||
|
/// </summary>
|
||||||
|
public int UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the RoomTypeId
|
||||||
|
/// </summary>
|
||||||
|
public int RoomTypeId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the PresetName
|
||||||
|
/// </summary>
|
||||||
|
public string PresetName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the PresetNumber
|
||||||
|
/// </summary>
|
||||||
|
public int PresetNumber { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Data
|
||||||
|
/// </summary>
|
||||||
|
public string Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
public Preset()
|
||||||
|
{
|
||||||
|
PresetName = "";
|
||||||
|
PresetNumber = 1;
|
||||||
|
Data = "{}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a PresetReceivedEventArgs
|
||||||
|
/// </summary>
|
||||||
|
public class PresetReceivedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// True when the preset is found
|
||||||
|
/// </summary>
|
||||||
|
public bool LookupSuccess { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ULookupSuccess
|
||||||
|
/// </summary>
|
||||||
|
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Preset
|
||||||
|
/// </summary>
|
||||||
|
public Preset Preset { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For Simpl+
|
||||||
|
/// </summary>
|
||||||
|
public PresetReceivedEventArgs() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="preset"></param>
|
||||||
|
/// <param name="success"></param>
|
||||||
|
public PresetReceivedEventArgs(Preset preset, bool success)
|
||||||
|
{
|
||||||
|
LookupSuccess = success;
|
||||||
|
Preset = preset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/PepperDash.Core/WebApi/Presets/User.cs
Normal file
93
src/PepperDash.Core/WebApi/Presets/User.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
|
namespace PepperDash.Core.WebApi.Presets
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ExternalId
|
||||||
|
/// </summary>
|
||||||
|
public string ExternalId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the FirstName
|
||||||
|
/// </summary>
|
||||||
|
public string FirstName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the LastName
|
||||||
|
/// </summary>
|
||||||
|
public string LastName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public class UserReceivedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// True when user is found
|
||||||
|
/// </summary>
|
||||||
|
public bool LookupSuccess { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ULookupSuccess
|
||||||
|
/// </summary>
|
||||||
|
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the User
|
||||||
|
/// </summary>
|
||||||
|
public User User { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For Simpl+
|
||||||
|
/// </summary>
|
||||||
|
public UserReceivedEventArgs() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="success"></param>
|
||||||
|
public UserReceivedEventArgs(User user, bool success)
|
||||||
|
{
|
||||||
|
LookupSuccess = success;
|
||||||
|
User = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a UserAndRoomMessage
|
||||||
|
/// </summary>
|
||||||
|
public class UserAndRoomMessage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public int UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the RoomTypeId
|
||||||
|
/// </summary>
|
||||||
|
public int RoomTypeId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the PresetNumber
|
||||||
|
/// </summary>
|
||||||
|
public int PresetNumber { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
282
src/PepperDash.Core/WebApi/Presets/WebApiPasscodeClient.cs
Normal file
282
src/PepperDash.Core/WebApi/Presets/WebApiPasscodeClient.cs
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
using System;
|
||||||
|
using Crestron.SimplSharp; // For Basic SIMPL# Classes
|
||||||
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
|
using Crestron.SimplSharp.Net.Http;
|
||||||
|
using Crestron.SimplSharp.Net.Https;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using PepperDash.Core.JsonToSimpl;
|
||||||
|
|
||||||
|
|
||||||
|
namespace PepperDash.Core.WebApi.Presets
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Passcode client for the WebApi
|
||||||
|
/// </summary>
|
||||||
|
public class WebApiPasscodeClient : IKeyed
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Notifies when user received
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<UserReceivedEventArgs> UserReceived;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Notifies when Preset received
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<PresetReceivedEventArgs> PresetReceived;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the Key
|
||||||
|
/// </summary>
|
||||||
|
public string Key { get; private set; }
|
||||||
|
|
||||||
|
//string JsonMasterKey;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An embedded JsonToSimpl master object.
|
||||||
|
/// </summary>
|
||||||
|
JsonToSimplGenericMaster J2SMaster;
|
||||||
|
|
||||||
|
string UrlBase;
|
||||||
|
|
||||||
|
string DefaultPresetJsonFilePath;
|
||||||
|
|
||||||
|
User CurrentUser;
|
||||||
|
|
||||||
|
Preset CurrentPreset;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SIMPL+ can only execute the default constructor. If you have variables that require initialization, please
|
||||||
|
/// use an Initialize method
|
||||||
|
/// </summary>
|
||||||
|
public WebApiPasscodeClient()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="jsonMasterKey"></param>
|
||||||
|
/// <param name="urlBase"></param>
|
||||||
|
/// <param name="defaultPresetJsonFilePath"></param>
|
||||||
|
public void Initialize(string key, string jsonMasterKey, string urlBase, string defaultPresetJsonFilePath)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
//JsonMasterKey = jsonMasterKey;
|
||||||
|
UrlBase = urlBase;
|
||||||
|
DefaultPresetJsonFilePath = defaultPresetJsonFilePath;
|
||||||
|
|
||||||
|
J2SMaster = new JsonToSimplGenericMaster();
|
||||||
|
J2SMaster.SaveCallback = this.SaveCallback;
|
||||||
|
J2SMaster.Initialize(jsonMasterKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the user for a passcode
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="passcode"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// GetUserForPasscode method
|
||||||
|
/// </summary>
|
||||||
|
public void GetUserForPasscode(string passcode)
|
||||||
|
{
|
||||||
|
// Bullshit duplicate code here... These two cases should be the same
|
||||||
|
// except for https/http and the certificate ignores
|
||||||
|
if (!UrlBase.StartsWith("https"))
|
||||||
|
return;
|
||||||
|
var req = new HttpsClientRequest();
|
||||||
|
req.Url = new UrlParser(UrlBase + "/api/users/dopin");
|
||||||
|
req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post;
|
||||||
|
req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json"));
|
||||||
|
req.Header.AddHeader(new HttpsHeader("Accept", "application/json"));
|
||||||
|
var jo = new JObject();
|
||||||
|
jo.Add("pin", passcode);
|
||||||
|
req.ContentString = jo.ToString();
|
||||||
|
|
||||||
|
var client = new HttpsClient();
|
||||||
|
client.HostVerification = false;
|
||||||
|
client.PeerVerification = false;
|
||||||
|
var resp = client.Dispatch(req);
|
||||||
|
var handler = UserReceived;
|
||||||
|
if (resp.Code == 200)
|
||||||
|
{
|
||||||
|
//CrestronConsole.PrintLine("Received: {0}", resp.ContentString);
|
||||||
|
var user = JsonConvert.DeserializeObject<User>(resp.ContentString);
|
||||||
|
CurrentUser = user;
|
||||||
|
if (handler != null)
|
||||||
|
UserReceived(this, new UserReceivedEventArgs(user, true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (handler != null)
|
||||||
|
UserReceived(this, new UserReceivedEventArgs(null, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roomTypeId"></param>
|
||||||
|
/// <param name="presetNumber"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// GetPresetForThisUser method
|
||||||
|
/// </summary>
|
||||||
|
public void GetPresetForThisUser(int roomTypeId, int presetNumber)
|
||||||
|
{
|
||||||
|
if (CurrentUser == null)
|
||||||
|
{
|
||||||
|
CrestronConsole.PrintLine("GetPresetForThisUser no user loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = new UserAndRoomMessage
|
||||||
|
{
|
||||||
|
UserId = CurrentUser.Id,
|
||||||
|
RoomTypeId = roomTypeId,
|
||||||
|
PresetNumber = presetNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
var handler = PresetReceived;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!UrlBase.StartsWith("https"))
|
||||||
|
return;
|
||||||
|
var req = new HttpsClientRequest();
|
||||||
|
req.Url = new UrlParser(UrlBase + "/api/presets/userandroom");
|
||||||
|
req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post;
|
||||||
|
req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json"));
|
||||||
|
req.Header.AddHeader(new HttpsHeader("Accept", "application/json"));
|
||||||
|
req.ContentString = JsonConvert.SerializeObject(msg);
|
||||||
|
|
||||||
|
var client = new HttpsClient();
|
||||||
|
client.HostVerification = false;
|
||||||
|
client.PeerVerification = false;
|
||||||
|
|
||||||
|
// ask for the preset
|
||||||
|
var resp = client.Dispatch(req);
|
||||||
|
if (resp.Code == 200) // got it
|
||||||
|
{
|
||||||
|
//Debug.Console(1, this, "Received: {0}", resp.ContentString);
|
||||||
|
var preset = JsonConvert.DeserializeObject<Preset>(resp.ContentString);
|
||||||
|
CurrentPreset = preset;
|
||||||
|
|
||||||
|
//if there's no preset data, load the template
|
||||||
|
if (preset.Data == null || preset.Data.Trim() == string.Empty || JObject.Parse(preset.Data).Count == 0)
|
||||||
|
{
|
||||||
|
//Debug.Console(1, this, "Loaded preset has no data. Loading default template.");
|
||||||
|
LoadDefaultPresetData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
J2SMaster.LoadWithJson(preset.Data);
|
||||||
|
if (handler != null)
|
||||||
|
PresetReceived(this, new PresetReceivedEventArgs(preset, true));
|
||||||
|
}
|
||||||
|
else // no existing preset
|
||||||
|
{
|
||||||
|
CurrentPreset = new Preset();
|
||||||
|
LoadDefaultPresetData();
|
||||||
|
if (handler != null)
|
||||||
|
PresetReceived(this, new PresetReceivedEventArgs(null, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpException e)
|
||||||
|
{
|
||||||
|
var resp = e.Response;
|
||||||
|
Debug.Console(1, this, "No preset received (code {0}). Loading default template", resp.Code);
|
||||||
|
LoadDefaultPresetData();
|
||||||
|
if (handler != null)
|
||||||
|
PresetReceived(this, new PresetReceivedEventArgs(null, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadDefaultPresetData()
|
||||||
|
{
|
||||||
|
CurrentPreset = null;
|
||||||
|
if (!File.Exists(DefaultPresetJsonFilePath))
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, "Cannot load default preset file. Saving will not work");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
using (StreamReader sr = new StreamReader(DefaultPresetJsonFilePath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = sr.ReadToEnd();
|
||||||
|
J2SMaster.SetJsonWithoutEvaluating(data);
|
||||||
|
CurrentPreset = new Preset() { Data = data, UserId = CurrentUser.Id };
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.Console(0, this, "Error reading default preset JSON: \r{0}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roomTypeId"></param>
|
||||||
|
/// <param name="presetNumber"></param>
|
||||||
|
/// <summary>
|
||||||
|
/// SavePresetForThisUser method
|
||||||
|
/// </summary>
|
||||||
|
public void SavePresetForThisUser(int roomTypeId, int presetNumber)
|
||||||
|
{
|
||||||
|
if (CurrentPreset == null)
|
||||||
|
LoadDefaultPresetData();
|
||||||
|
//return;
|
||||||
|
|
||||||
|
//// A new preset needs to have its numbers set
|
||||||
|
//if (CurrentPreset.IsNewPreset)
|
||||||
|
//{
|
||||||
|
CurrentPreset.UserId = CurrentUser.Id;
|
||||||
|
CurrentPreset.RoomTypeId = roomTypeId;
|
||||||
|
CurrentPreset.PresetNumber = presetNumber;
|
||||||
|
//}
|
||||||
|
J2SMaster.Save(); // Will trigger callback when ready
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// After save operation on JSON master happens, send it to server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="json"></param>
|
||||||
|
void SaveCallback(string json)
|
||||||
|
{
|
||||||
|
CurrentPreset.Data = json;
|
||||||
|
|
||||||
|
if (!UrlBase.StartsWith("https"))
|
||||||
|
return;
|
||||||
|
var req = new HttpsClientRequest();
|
||||||
|
req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post;
|
||||||
|
req.Url = new UrlParser(string.Format("{0}/api/presets/addorchange", UrlBase));
|
||||||
|
req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json"));
|
||||||
|
req.Header.AddHeader(new HttpsHeader("Accept", "application/json"));
|
||||||
|
req.ContentString = JsonConvert.SerializeObject(CurrentPreset);
|
||||||
|
|
||||||
|
var client = new HttpsClient();
|
||||||
|
client.HostVerification = false;
|
||||||
|
client.PeerVerification = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var resp = client.Dispatch(req);
|
||||||
|
|
||||||
|
// 201=created
|
||||||
|
// 204=empty content
|
||||||
|
if (resp.Code == 201)
|
||||||
|
CrestronConsole.PrintLine("Preset added");
|
||||||
|
else if (resp.Code == 204)
|
||||||
|
CrestronConsole.PrintLine("Preset updated");
|
||||||
|
else if (resp.Code == 209)
|
||||||
|
CrestronConsole.PrintLine("Preset already exists. Cannot save as new.");
|
||||||
|
else
|
||||||
|
CrestronConsole.PrintLine("Preset save failed: {0}\r", resp.Code, resp.ContentString);
|
||||||
|
}
|
||||||
|
catch (HttpException e)
|
||||||
|
{
|
||||||
|
|
||||||
|
CrestronConsole.PrintLine("Preset save exception {0}", e.Response.Code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using PepperDash.Core.Intersystem.Tokens;
|
using PepperDash.Core.Intersystem.Tokens;
|
||||||
|
|
||||||
namespace PepperDash.Core.Intersystem.Serialization;
|
namespace PepperDash.Core.Intersystem.Serialization
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface to determine XSig serialization for an object.
|
/// Interface to determine XSig serialization for an object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -22,3 +22,4 @@ public interface IXSigSerialization
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
T Deserialize<T>(IEnumerable<XSigToken> tokens) where T : class, IXSigSerialization;
|
T Deserialize<T>(IEnumerable<XSigToken> tokens) where T : class, IXSigSerialization;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user