Compare commits

..

1 Commits

Author SHA1 Message Date
Andrew Welker
185410b802 fix: ignore case for appdebug {LogLevel} 2025-05-07 08:08:56 -05:00
733 changed files with 43594 additions and 58919 deletions

View File

@@ -1,13 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.2.4",
"commands": [
"csharpier"
],
"rollForward": false
}
}
}

View File

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

5
.gitignore vendored
View File

@@ -393,7 +393,4 @@ essentials-framework/Essentials Interfaces/PepperDash_Essentials_Interfaces/Pepp
/._PepperDash.Essentials.sln /._PepperDash.Essentials.sln
.vscode/settings.json .vscode/settings.json
_site/ _site/
api/ api/
*.DS_Store
/._PepperDash.Essentials.4Series.sln
dotnet

View File

@@ -1,9 +0,0 @@
{
"recommendations": [
"ms-dotnettools.vscode-dotnet-runtime",
"ms-dotnettools.csharp",
"ms-dotnettools.csdevkit",
"vivaxy.vscode-conventional-commits",
"mhutchie.git-graph"
]
}

View File

@@ -1,282 +0,0 @@
# Crestron Library Usage Analysis - PepperDash Essentials
This document provides a comprehensive analysis of Crestron classes and interfaces used throughout the PepperDash Essentials framework, organized by namespace and library component.
## Executive Summary
The PepperDash Essentials framework extensively leverages Crestron SDK components across 100+ files, providing abstractions for:
- Control system hardware (processors, touchpanels, IO devices)
- Communication interfaces (Serial, TCP/IP, SSH, CEC, IR)
- Device management and routing
- User interface components and smart objects
- System monitoring and diagnostics
## 1. Core Crestron Libraries
### 1.1 Crestron.SimplSharp
**Primary Usage**: Foundational framework components, collections, and basic types.
**Key Files**:
- Multiple files across all projects use `Crestron.SimplSharp` namespaces
- Provides basic C# runtime support for Crestron processors
### 1.2 Crestron.SimplSharpPro
**Primary Usage**: Main hardware abstraction layer for Crestron devices.
**Key Classes Used**:
#### CrestronControlSystem
- **File**: `/src/PepperDash.Essentials/ControlSystem.cs`
- **Usage**: Base class for the main control system implementation
- **Implementation**: `public class ControlSystem : CrestronControlSystem, ILoadConfig`
#### Device (Base Class)
- **Files**: 50+ files inherit from or use this class
- **Key Implementations**:
- `/src/PepperDash.Core/Device.cs` - Core device abstraction
- `/src/PepperDash.Essentials.Core/Devices/EssentialsDevice.cs` - Extended device base
- `/src/PepperDash.Essentials.Core/Room/Room.cs` - Room device implementation
- `/src/PepperDash.Essentials.Core/Devices/CrestronProcessor.cs` - Processor device wrapper
#### BasicTriList
- **Files**: 30+ files use this class extensively
- **Primary Usage**: Touchpanel communication and SIMPL bridging
- **Key Files**:
- `/src/PepperDash.Essentials.Core/Touchpanels/TriListExtensions.cs` - Extension methods for signal handling
- `/src/PepperDash.Essentials.Core/Devices/EssentialsBridgeableDevice.cs` - Bridge interface
- `/src/PepperDash.Essentials.Core/Touchpanels/ModalDialog.cs` - UI dialog implementation
#### BasicTriListWithSmartObject
- **Files**: Multiple touchpanel and UI files
- **Usage**: Enhanced touchpanel support with smart object integration
- **Key Files**:
- `/src/PepperDash.Essentials.Core/Touchpanels/Interfaces.cs` - Interface definitions
- `/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferenceList/SubpageReferenceList.cs`
## 2. Communication Hardware
### 2.1 Serial Communication (ComPort)
**Primary Class**: `ComPort`
**Key Files**:
- `/src/PepperDash.Essentials.Core/Comm and IR/ComPortController.cs`
- `/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs`
**Usage Pattern**:
```csharp
public class ComPortController : Device, IBasicCommunicationWithStreamDebugging
public static ComPort GetComPort(EssentialsControlPropertiesConfig config)
```
**Interface Support**: `IComPorts` - Used for devices that provide multiple COM ports
### 2.2 IR Communication (IROutputPort)
**Primary Class**: `IROutputPort`
**Key Files**:
- `/src/PepperDash.Essentials.Core/Devices/IrOutputPortController.cs`
- `/src/PepperDash.Essentials.Core/Devices/GenericIRController.cs`
- `/src/PepperDash.Essentials.Core/Comm and IR/IRPortHelper.cs`
**Usage Pattern**:
```csharp
public class IrOutputPortController : Device
IROutputPort IrPort;
public IrOutputPortController(string key, IROutputPort port, string irDriverFilepath)
```
### 2.3 CEC Communication (ICec)
**Primary Interface**: `ICec`
**Key Files**:
- `/src/PepperDash.Essentials.Core/Comm and IR/CecPortController.cs`
- `/src/PepperDash.Essentials.Core/Comm and IR/CommFactory.cs`
**Usage Pattern**:
```csharp
public class CecPortController : Device, IBasicCommunicationWithStreamDebugging
public static ICec GetCecPort(ControlPropertiesConfig config)
```
## 3. Input/Output Hardware
### 3.1 Digital Input
**Primary Interface**: `IDigitalInput`
**Key Files**:
- `/src/PepperDash.Essentials.Core/CrestronIO/GenericDigitalInputDevice.cs`
- `/src/PepperDash.Essentials.Core/Microphone Privacy/MicrophonePrivacyController.cs`
**Usage Pattern**:
```csharp
public List<IDigitalInput> Inputs { get; private set; }
void AddInput(IDigitalInput input)
```
### 3.2 Versiport Support
**Key Files**:
- `/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportInputDevice.cs`
- `/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportAnalogInputDevice.cs`
- `/src/PepperDash.Essentials.Core/CrestronIO/GenericVersiportOutputDevice.cs`
**Usage**: Provides flexible I/O port configuration for various signal types
## 4. Touchpanel Hardware
### 4.1 MPC3 Touchpanel
**Primary Class**: `MPC3Basic`
**Key File**: `/src/PepperDash.Essentials.Core/Touchpanels/Mpc3Touchpanel.cs`
**Usage Pattern**:
```csharp
public class Mpc3TouchpanelController : Device
readonly MPC3Basic _touchpanel;
_touchpanel = processor.ControllerTouchScreenSlotDevice as MPC3Basic;
```
### 4.2 TSW Series Support
**Evidence**: References found in messenger files and mobile control components
**Usage**: Integrated through mobile control messaging system for TSW touchpanel features
## 5. Timer and Threading
### 5.1 CTimer
**Primary Class**: `CTimer`
**Key File**: `/src/PepperDash.Core/PasswordManagement/PasswordManager.cs`
**Usage Pattern**:
```csharp
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Started"));
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Reset"));
```
## 6. Networking and Communication
### 6.1 Ethernet Communication
**Libraries Used**:
- `Crestron.SimplSharpPro.EthernetCommunication`
- `Crestron.SimplSharp.Net.Utilities.EthernetHelper`
**Key Files**:
- `/src/PepperDash.Core/Comm/GenericTcpIpClient.cs`
- `/src/PepperDash.Core/Comm/GenericTcpIpServer.cs`
- `/src/PepperDash.Core/Comm/GenericSecureTcpIpClient.cs`
- `/src/PepperDash.Core/Comm/GenericSshClient.cs`
- `/src/PepperDash.Core/Comm/GenericUdpServer.cs`
**Usage Pattern**:
```csharp
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
```
## 7. Device Management Libraries
### 7.1 DeviceSupport
**Library**: `Crestron.SimplSharpPro.DeviceSupport`
**Usage**: Core device support infrastructure used throughout the framework
### 7.2 DM (DigitalMedia)
**Library**: `Crestron.SimplSharpPro.DM`
**Usage**: Digital media routing and switching support
**Evidence**: Found in routing configuration and DM output card references
## 8. User Interface Libraries
### 8.1 UI Components
**Library**: `Crestron.SimplSharpPro.UI`
**Usage**: User interface elements and touchpanel controls
### 8.2 Smart Objects
**Key Files**:
- `/src/PepperDash.Essentials.Core/SmartObjects/SmartObjectDynamicList.cs`
- `/src/PepperDash.Essentials.Core/SmartObjects/SubpageReferenceList/SubpageReferenceList.cs`
**Usage**: Advanced UI components with dynamic content
## 9. System Monitoring and Diagnostics
### 9.1 Diagnostics
**Library**: `Crestron.SimplSharpPro.Diagnostics`
**Usage**: System health monitoring and performance tracking
### 9.2 System Information
**Key Files**:
- `/src/PepperDash.Essentials.Core/Monitoring/SystemMonitorController.cs`
**Usage**: Provides system status, Ethernet information, and program details
## 10. Integration Patterns
### 10.1 SIMPL Bridging
**Pattern**: Extensive use of `BasicTriList` for SIMPL integration
**Files**: Bridge classes throughout the framework implement `LinkToApi` methods:
```csharp
public abstract void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge);
```
### 10.2 Device Factory Pattern
**Implementation**: Factory classes create hardware-specific implementations
**Example**: `CommFactory.cs` provides communication device creation
### 10.3 Extension Methods
**Pattern**: Extensive use of extension methods for Crestron classes
**Example**: `TriListExtensions.cs` adds 30+ extension methods to `BasicTriList`
## 11. Signal Processing
### 11.1 Signal Types
**Bool Signals**: Digital control and feedback
**UShort Signals**: Analog values and numeric data
**String Signals**: Text and configuration data
**Implementation**: Comprehensive signal handling in `TriListExtensions.cs`
## 12. Error Handling and Logging
**Pattern**: Consistent use of Crestron's Debug logging throughout
**Examples**:
```csharp
Debug.LogMessage(LogEventLevel.Information, "Device {0} is not a valid device", dc.PortDeviceKey);
Debug.LogMessage(LogEventLevel.Debug, "Error Waking Panel. Maybe testing with Xpanel?");
```
## 13. Threading and Synchronization
**Components**:
- CTimer for time-based operations
- Thread-safe collections and patterns
- Event-driven programming models
## Conclusion
The PepperDash Essentials framework demonstrates sophisticated integration with the Crestron ecosystem, leveraging:
- **Core Infrastructure**: CrestronControlSystem, Device base classes
- **Communication**: COM, IR, CEC, TCP/IP, SSH protocols
- **Hardware Abstraction**: Touchpanels, I/O devices, processors
- **User Interface**: Smart objects, signal processing, SIMPL bridging
- **System Services**: Monitoring, diagnostics, device management
This analysis shows that Essentials serves as a comprehensive middleware layer, abstracting Crestron hardware complexities while providing modern software development patterns and practices.
---
*Generated: [Current Date]*
*Framework Version: PepperDash Essentials (Based on codebase analysis)*

View File

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

View File

@@ -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
![Architecture overview](~/docs/images/arch-overview.png) ![Architecture overview](~/docs/images/arch-overview.png)
Next: [Configurable lifecycle](~/docs/technical-docs/Arch-lifecycle.md) Next: [Configurable lifecycle](~/docs/Arch-lifecycle.md)

View File

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

View File

@@ -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))
![Lifecycle](~/docs/images/lifecycle.png) ![Lifecycle](~/docs/images/lifecycle.png)
Next: [Activation phases](~/docs/technical-docs/Arch-activate.md) Next: [Activation phases](~/docs/Arch-activate.md)

View File

@@ -16,4 +16,4 @@ The diagram below shows the reference dependencies that exist between the differ
![Architecture drawing](~/docs/images/arch-high-level.png) ![Architecture drawing](~/docs/images/arch-high-level.png)
Next: [Architecture](~/docs/technical-docs/Arch-1.md) Next: [Architecture](~/docs/Arch-1.md)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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: `![Alt text](../images/your-image.png)`
- 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/)

View File

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

View File

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

View File

@@ -1,7 +0,0 @@
{
"runtimeOptions": {
"configProperties": {
"System.Globalization.Invariant": false
}
}
}

View File

@@ -1,6 +1,6 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>3.0.0-local</Version> <Version>2.4.0-local</Version>
<InformationalVersion>$(Version)</InformationalVersion> <InformationalVersion>$(Version)</InformationalVersion>
<Authors>PepperDash Technology</Authors> <Authors>PepperDash Technology</Authors>
<Company>PepperDash Technology</Company> <Company>PepperDash Technology</Company>

View File

@@ -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>
@@ -23,32 +23,23 @@
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz</FileName> <FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz</FileName>
</PropertyGroup> </PropertyGroup>
<Target Name="DeleteCLZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'Library' And $(TargetDir) != ''"> <Target Name="DeleteCLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Library' And $(TargetDir) != '' And Exists($(FileName))">
<ItemGroup> <Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz">
<OldCLZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).clz" />
</ItemGroup>
<Delete Files="@(OldCLZFiles)" Condition="@(OldCLZFiles) != ''">
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/> <Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
</Delete> </Delete>
<Message Text="Deleted old CLZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" /> <Message Text="Deleted files: '@(DeletedList)'" />
</Target> </Target>
<Target Name="DeleteCPZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'Program' And $(TargetDir) != ''"> <Target Name="DeleteCPZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Program' And $(TargetDir) != '' And Exists($(FileName))">
<ItemGroup> <Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz">
<OldCPZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).cpz" />
</ItemGroup>
<Delete Files="@(OldCPZFiles)" Condition="@(OldCPZFiles) != ''">
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/> <Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
</Delete> </Delete>
<Message Text="Deleted old CPZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" /> <Message Text="Deleted files: '@(DeletedList)'" />
</Target> </Target>
<Target Name="DeleteCPLZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''"> <Target Name="DeleteCPLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != '' And Exists($(FileName))">
<ItemGroup> <Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz">
<OldCPLZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).cplz" />
</ItemGroup>
<Delete Files="@(OldCPLZFiles)" Condition="@(OldCPLZFiles) != ''">
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/> <Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
</Delete> </Delete>
<Message Text="Deleted old CPLZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" /> <Message Text="Deleted files: '@(DeletedList)'" />
</Target> </Target>
<Target Name="CreateCPLZ" AfterTargets="Build" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''" DependsOnTargets="DeleteCPLZ"> <Target Name="CreateCPLZ" AfterTargets="Build" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''" DependsOnTargets="DeleteCPLZ">

View File

@@ -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>
@@ -30,7 +30,7 @@ namespace PepperDash.Core;
/// <summary> /// <summary>
/// The communication port that this gathers on /// The communication port that this gathers on
/// </summary> /// </summary>
public ICommunicationReceiver Port { get; private set; } public ICommunicationReceiver Port { get; private set; }
/// <summary> /// <summary>
/// Default false. If true, the delimiter will be included in the line output /// Default false. If true, the delimiter will be included in the line output
@@ -67,26 +67,27 @@ namespace PepperDash.Core;
/// </summary> /// </summary>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="delimiter"></param> /// <param name="delimiter"></param>
public CommunicationGather(ICommunicationReceiver port, string delimiter) public CommunicationGather(ICommunicationReceiver port, string delimiter)
:this(port, new string[] { delimiter} ) :this(port, new string[] { delimiter} )
{ {
} }
/// <summary> /// <summary>
/// Constructor for using an array of string delimiters /// Constructor for using an array of string delimiters
/// </summary> /// </summary>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="delimiters"></param> /// <param name="delimiters"></param>
public CommunicationGather(ICommunicationReceiver port, string[] delimiters) public CommunicationGather(ICommunicationReceiver port, string[] delimiters)
{ {
Port = port; Port = port;
StringDelimiters = delimiters; StringDelimiters = delimiters;
port.TextReceived += Port_TextReceivedStringDelimiter; port.TextReceived += Port_TextReceivedStringDelimiter;
} }
/// <summary> /// <summary>
/// Stop method /// Disconnects this gather from the Port's TextReceived event. This will not fire LineReceived
/// </summary> /// after the this call.
/// </summary>
public void Stop() public void Stop()
{ {
Port.TextReceived -= Port_TextReceived; Port.TextReceived -= Port_TextReceived;
@@ -135,35 +136,35 @@ namespace PepperDash.Core;
ReceiveBuffer.Append(args.Text); ReceiveBuffer.Append(args.Text);
var str = ReceiveBuffer.ToString(); var str = ReceiveBuffer.ToString();
// Case: Receiving DEVICE get version\x0d\0x0a+OK "value":"1234"\x0d\x0a // Case: Receiving DEVICE get version\x0d\0x0a+OK "value":"1234"\x0d\x0a
// RX: DEV // RX: DEV
// Split: (1) "DEV" // Split: (1) "DEV"
// RX: I // RX: I
// Split: (1) "DEVI" // Split: (1) "DEVI"
// RX: CE get version // RX: CE get version
// Split: (1) "DEVICE get version" // Split: (1) "DEVICE get version"
// RX: \x0d\x0a+OK "value":"1234"\x0d\x0a // RX: \x0d\x0a+OK "value":"1234"\x0d\x0a
// Split: (2) DEVICE get version, +OK "value":"1234" // Split: (2) DEVICE get version, +OK "value":"1234"
// Iterate the delimiters and fire an event for any matching delimiter // Iterate the delimiters and fire an event for any matching delimiter
foreach (var delimiter in StringDelimiters) foreach (var delimiter in StringDelimiters)
{
var lines = Regex.Split(str, delimiter);
if (lines.Length == 1)
continue;
for (int i = 0; i < lines.Length - 1; i++)
{ {
string strToSend = null; var lines = Regex.Split(str, delimiter);
if (IncludeDelimiter) if (lines.Length == 1)
strToSend = lines[i] + delimiter; continue;
else
strToSend = lines[i]; for (int i = 0; i < lines.Length - 1; i++)
handler(this, new GenericCommMethodReceiveTextArgs(strToSend, delimiter)); {
string strToSend = null;
if (IncludeDelimiter)
strToSend = lines[i] + delimiter;
else
strToSend = lines[i];
handler(this, new GenericCommMethodReceiveTextArgs(strToSend, delimiter));
}
ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]);
} }
ReceiveBuffer = new StringBuilder(lines[lines.Length - 1]);
}
} }
} }
@@ -174,4 +175,5 @@ namespace PepperDash.Core;
{ {
Stop(); Stop();
} }
} }
}

View File

@@ -2,158 +2,176 @@
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>
/// Controls the ability to disable/enable debugging of TX/RX data sent to/from a device with a built in timer to disable
/// </summary>
public class CommunicationStreamDebugging
{ {
/// <summary> /// <summary>
/// Device Key that this instance configures /// 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>
public string ParentDeviceKey { get; private set; } public class CommunicationStreamDebugging
/// <summary>
/// Timer to disable automatically if not manually disabled
/// </summary>
private Timer DebugExpiryPeriod;
/// <summary>
/// The current debug setting
/// </summary>
public eStreamDebuggingSetting DebugSetting { get; private set; }
private uint _DebugTimeoutInMs;
private const uint _DefaultDebugTimeoutMin = 30;
/// <summary>
/// Timeout in Minutes
/// </summary>
public uint DebugTimeoutMinutes
{ {
get /// <summary>
/// Device Key that this instance configures
/// </summary>
public string ParentDeviceKey { get; private set; }
/// <summary>
/// Timer to disable automatically if not manually disabled
/// </summary>
private CTimer DebugExpiryPeriod;
/// <summary>
/// The current debug setting
/// </summary>
public eStreamDebuggingSetting DebugSetting { get; private set; }
private uint _DebugTimeoutInMs;
private const uint _DefaultDebugTimeoutMin = 30;
/// <summary>
/// Timeout in Minutes
/// </summary>
public uint DebugTimeoutMinutes
{ {
return _DebugTimeoutInMs/60000; get
{
return _DebugTimeoutInMs/60000;
}
}
/// <summary>
/// Indicates that receive stream debugging is enabled
/// </summary>
public bool RxStreamDebuggingIsEnabled{ get; private set; }
/// <summary>
/// Indicates that transmit stream debugging is enabled
/// </summary>
public bool TxStreamDebuggingIsEnabled { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="parentDeviceKey"></param>
public CommunicationStreamDebugging(string parentDeviceKey)
{
ParentDeviceKey = parentDeviceKey;
}
/// <summary>
/// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues
/// </summary>
/// <param name="setting"></param>
public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting)
{
if (setting == eStreamDebuggingSetting.Off)
{
DisableDebugging();
return;
}
SetDebuggingWithSpecificTimeout(setting, _DefaultDebugTimeoutMin);
}
/// <summary>
/// Sets the debugging setting for the specified number of minutes
/// </summary>
/// <param name="setting"></param>
/// <param name="minutes"></param>
public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes)
{
if (setting == eStreamDebuggingSetting.Off)
{
DisableDebugging();
return;
}
_DebugTimeoutInMs = minutes * 60000;
StopDebugTimer();
DebugExpiryPeriod = new CTimer((o) => DisableDebugging(), _DebugTimeoutInMs);
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
RxStreamDebuggingIsEnabled = true;
if ((setting & eStreamDebuggingSetting.Tx) == eStreamDebuggingSetting.Tx)
TxStreamDebuggingIsEnabled = true;
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
}
/// <summary>
/// Disabled debugging
/// </summary>
private void DisableDebugging()
{
StopDebugTimer();
Debug.SetDeviceDebugSettings(ParentDeviceKey, eStreamDebuggingSetting.Off);
}
private void StopDebugTimer()
{
RxStreamDebuggingIsEnabled = false;
TxStreamDebuggingIsEnabled = false;
if (DebugExpiryPeriod == null)
{
return;
}
DebugExpiryPeriod.Stop();
DebugExpiryPeriod.Dispose();
DebugExpiryPeriod = null;
} }
} }
/// <summary> /// <summary>
/// Indicates that receive stream debugging is enabled /// The available settings for stream debugging
/// </summary> /// </summary>
public bool RxStreamDebuggingIsEnabled{ get; private set; } [Flags]
public enum eStreamDebuggingSetting
/// <summary>
/// Indicates that transmit stream debugging is enabled
/// </summary>
public bool TxStreamDebuggingIsEnabled { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="parentDeviceKey"></param>
public CommunicationStreamDebugging(string parentDeviceKey)
{ {
ParentDeviceKey = parentDeviceKey; /// <summary>
} /// Debug off
/// </summary>
Off = 0,
/// <summary> /// <summary>
/// Sets the debugging setting and if not setting to off, assumes the default of 30 mintues /// Debug received data
/// </summary> /// </summary>
/// <param name="setting"></param> Rx = 1,
public void SetDebuggingWithDefaultTimeout(eStreamDebuggingSetting setting) /// <summary>
{ /// Debug transmitted data
if (setting == eStreamDebuggingSetting.Off) /// </summary>
{ Tx = 2,
DisableDebugging(); /// <summary>
return; /// Debug both received and transmitted data
} /// </summary>
Both = Rx | Tx
SetDebuggingWithSpecificTimeout(setting, _DefaultDebugTimeoutMin);
} }
/// <summary> /// <summary>
/// Sets the debugging setting for the specified number of minutes /// The available settings for stream debugging response types
/// </summary> /// </summary>
/// <param name="setting"></param> [Flags]
/// <param name="minutes"></param> public enum eStreamDebuggingDataTypeSettings
public void SetDebuggingWithSpecificTimeout(eStreamDebuggingSetting setting, uint minutes)
{ {
if (setting == eStreamDebuggingSetting.Off) /// <summary>
{ /// Debug data in byte format
DisableDebugging(); /// </summary>
return; Bytes = 0,
} /// <summary>
/// Debug data in text format
_DebugTimeoutInMs = minutes * 60000; /// </summary>
Text = 1,
StopDebugTimer(); /// <summary>
/// Debug data in both byte and text formats
DebugExpiryPeriod = new Timer(_DebugTimeoutInMs) { AutoReset = false }; /// </summary>
DebugExpiryPeriod.Elapsed += (s, e) => DisableDebugging(); Both = Bytes | Text,
DebugExpiryPeriod.Start();
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
RxStreamDebuggingIsEnabled = true;
if ((setting & eStreamDebuggingSetting.Tx) == eStreamDebuggingSetting.Tx)
TxStreamDebuggingIsEnabled = true;
Debug.SetDeviceDebugSettings(ParentDeviceKey, setting);
}
/// <summary>
/// Disabled debugging
/// </summary>
private void DisableDebugging()
{
StopDebugTimer();
Debug.SetDeviceDebugSettings(ParentDeviceKey, eStreamDebuggingSetting.Off);
}
private void StopDebugTimer()
{
RxStreamDebuggingIsEnabled = false;
TxStreamDebuggingIsEnabled = false;
if (DebugExpiryPeriod == null)
{
return;
}
DebugExpiryPeriod.Stop();
DebugExpiryPeriod.Dispose();
DebugExpiryPeriod = null;
} }
} }
/// <summary>
/// The available settings for stream debugging
/// </summary>
[Flags]
public enum eStreamDebuggingSetting
{
/// <summary>
/// Debug off
/// </summary>
Off = 0,
/// <summary>
/// Debug received data
/// </summary>
Rx = 1,
/// <summary>
/// Debug transmitted data
/// </summary>
Tx = 2,
/// <summary>
/// Debug both received and transmitted data
/// </summary>
Both = Rx | Tx
}

View File

@@ -1,97 +1,93 @@
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>
/// Config properties that indicate how to communicate with a device for control
/// </summary>
public class ControlPropertiesConfig
{ {
/// <summary> /// <summary>
/// The method of control /// Config properties that indicate how to communicate with a device for control
/// </summary> /// </summary>
[JsonProperty("method")] public class ControlPropertiesConfig
[JsonConverter(typeof(StringEnumConverter))] {
public eControlMethod Method { get; set; } /// <summary>
/// The method of control
/// </summary>
[JsonProperty("method")]
[JsonConverter(typeof(StringEnumConverter))]
public eControlMethod Method { get; set; }
/// <summary> /// <summary>
/// The key of the device that contains the control port /// The key of the device that contains the control port
/// </summary> /// </summary>
[JsonProperty("controlPortDevKey", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("controlPortDevKey", NullValueHandling = NullValueHandling.Ignore)]
public string ControlPortDevKey { get; set; } public string ControlPortDevKey { get; set; }
/// <summary> /// <summary>
/// The number of the control port on the device specified by ControlPortDevKey /// The number of the control port on the device specified by ControlPortDevKey
/// </summary> /// </summary>
[JsonProperty("controlPortNumber", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value [JsonProperty("controlPortNumber", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
public uint? ControlPortNumber { get; set; } public uint? ControlPortNumber { get; set; }
/// <summary> /// <summary>
/// The name of the control port on the device specified by ControlPortDevKey /// The name of the control port on the device specified by ControlPortDevKey
/// </summary> /// </summary>
[JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value [JsonProperty("controlPortName", NullValueHandling = NullValueHandling.Ignore)] // In case "null" is present in config on this value
public string ControlPortName { get; set; } public string ControlPortName { get; set; }
/// <summary> /// <summary>
/// Properties for ethernet based communications /// Properties for ethernet based communications
/// </summary> /// </summary>
[JsonProperty("tcpSshProperties", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("tcpSshProperties", NullValueHandling = NullValueHandling.Ignore)]
public TcpSshPropertiesConfig TcpSshProperties { get; set; } public TcpSshPropertiesConfig TcpSshProperties { get; set; }
/// <summary> /// <summary>
/// The filename and path for the IR file /// The filename and path for the IR file
/// </summary> /// </summary>
[JsonProperty("irFile", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("irFile", NullValueHandling = NullValueHandling.Ignore)]
public string IrFile { get; set; } public string IrFile { get; set; }
/// <summary> /// <summary>
/// The IpId of a Crestron device /// The IpId of a Crestron device
/// </summary> /// </summary>
[JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("ipId", NullValueHandling = NullValueHandling.Ignore)]
public string IpId { get; set; } public string IpId { get; set; }
/// <summary> /// <summary>
/// Readonly uint representation of the IpId /// Readonly uint representation of the IpId
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } } public uint IpIdInt { get { return Convert.ToUInt32(IpId, 16); } }
/// <summary> /// <summary>
/// Char indicating end of line /// Char indicating end of line
/// </summary> /// </summary>
[JsonProperty("endOfLineChar", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("endOfLineChar", NullValueHandling = NullValueHandling.Ignore)]
public char EndOfLineChar { get; set; } public char EndOfLineChar { get; set; }
/// <summary> /// <summary>
/// Defaults to Environment.NewLine; /// Defaults to Environment.NewLine;
/// </summary> /// </summary>
[JsonProperty("endOfLineString", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("endOfLineString", NullValueHandling = NullValueHandling.Ignore)]
public string EndOfLineString { get; set; } public string EndOfLineString { get; set; }
/// <summary> /// <summary>
/// Indicates /// Indicates
/// </summary> /// </summary>
[JsonProperty("deviceReadyResponsePattern", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("deviceReadyResponsePattern", NullValueHandling = NullValueHandling.Ignore)]
public string DeviceReadyResponsePattern { get; set; } public string DeviceReadyResponsePattern { get; set; }
/// <summary> /// <summary>
/// Used when communcating to programs running in VC-4 /// Used when communcating to programs running in VC-4
/// </summary> /// </summary>
[JsonProperty("roomId", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("roomId", NullValueHandling = NullValueHandling.Ignore)]
public string RoomId { get; set; } public string RoomId { get; set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
public ControlPropertiesConfig() public ControlPropertiesConfig()
{ {
}
} }
} }

View File

@@ -16,237 +16,236 @@ using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronSockets; using Crestron.SimplSharp.CrestronSockets;
namespace PepperDash.Core; namespace PepperDash.Core
/// <summary>
/// Delegate for notifying of socket status changes
/// </summary>
/// <param name="client"></param>
public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
/// <summary>
/// EventArgs class for socket status changes
/// </summary>
public class GenericSocketStatusChageEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// Gets or sets the Client /// Delegate for notifying of socket status changes
/// </summary>
public ISocketStatus Client { get; private set; }
/// <summary>
/// Constructor
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
public GenericSocketStatusChageEventArgs(ISocketStatus client) public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client);
{
Client = client; /// <summary>
/// EventArgs class for socket status changes
/// </summary>
public class GenericSocketStatusChageEventArgs : EventArgs
{
/// <summary>
///
/// </summary>
public ISocketStatus Client { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="client"></param>
public GenericSocketStatusChageEventArgs(ISocketStatus client)
{
Client = client;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericSocketStatusChageEventArgs() { }
} }
/// <summary>
/// S+ Constructor
/// </summary>
public GenericSocketStatusChageEventArgs() { }
}
/// <summary>
/// Delegate for notifying of TCP Server state changes
/// </summary>
/// <param name="state"></param>
public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
/// <summary>
/// EventArgs class for TCP Server state changes
/// </summary>
public class GenericTcpServerStateChangedEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the State
/// </summary>
public ServerState State { get; private set; }
/// <summary> /// <summary>
/// Constructor /// Delegate for notifying of TCP Server state changes
/// </summary> /// </summary>
/// <param name="state"></param> /// <param name="state"></param>
public GenericTcpServerStateChangedEventArgs(ServerState state) public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state);
/// <summary>
/// EventArgs class for TCP Server state changes
/// </summary>
public class GenericTcpServerStateChangedEventArgs : EventArgs
{ {
State = state; /// <summary>
} ///
/// <summary> /// </summary>
/// S+ Constructor public ServerState State { get; private set; }
/// </summary>
public GenericTcpServerStateChangedEventArgs() { }
}
/// <summary> /// <summary>
/// Delegate for TCP Server socket status changes ///
/// </summary> /// </summary>
/// <param name="socket"></param> /// <param name="state"></param>
/// <param name="clientIndex"></param> public GenericTcpServerStateChangedEventArgs(ServerState state)
/// <param name="clientStatus"></param>
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
/// <summary>
/// EventArgs for TCP server socket status changes
/// </summary>
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the Socket
/// </summary>
public object Socket { get; private set; }
/// <summary>
/// Gets or sets the index of the client from which the status change was received
/// </summary>
public uint ReceivedFromClientIndex { get; private set; }
/// <summary>
/// Gets or sets the ClientStatus
/// </summary>
public SocketStatus ClientStatus { get; set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="socket"></param>
/// <param name="clientStatus"></param>
public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
{
Socket = socket;
ClientStatus = clientStatus;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="socket"></param>
/// <param name="clientIndex"></param>
/// <param name="clientStatus"></param>
public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
{
Socket = socket;
ReceivedFromClientIndex = clientIndex;
ClientStatus = clientStatus;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerSocketStatusChangeEventArgs() { }
}
/// <summary>
/// EventArgs for TCP server com method receive text
/// </summary>
public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
{
/// <summary>
/// Gets or sets the index of the client from which the text was received
/// </summary>
public uint ReceivedFromClientIndex { get; private set; }
/// <summary>
/// Gets the index of the client from which the text was received as a ushort
/// </summary>
public ushort ReceivedFromClientIndexShort
{
get
{ {
return (ushort)ReceivedFromClientIndex; State = state;
} }
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerStateChangedEventArgs() { }
} }
/// <summary> /// <summary>
/// Gets or sets the Text /// Delegate for TCP Server socket status changes
/// </summary> /// </summary>
public string Text { get; private set; } /// <param name="socket"></param>
/// <summary>
/// Constructor
/// </summary>
/// <param name="text"></param>
public GenericTcpServerCommMethodReceiveTextArgs(string text)
{
Text = text;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="text"></param>
/// <param name="clientIndex"></param> /// <param name="clientIndex"></param>
public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex) /// <param name="clientStatus"></param>
public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object socket, uint clientIndex, SocketStatus clientStatus);
/// <summary>
/// EventArgs for TCP server socket status changes
/// </summary>
public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
{ {
Text = text; /// <summary>
ReceivedFromClientIndex = clientIndex; ///
} /// </summary>
/// <summary> public object Socket { get; private set; }
/// S+ Constructor /// <summary>
/// </summary> ///
public GenericTcpServerCommMethodReceiveTextArgs() { } /// </summary>
} public uint ReceivedFromClientIndex { get; private set; }
/// <summary>
///
/// </summary>
public SocketStatus ClientStatus { get; set; }
/// <summary> /// <summary>
/// EventArgs for TCP server client ready for communication ///
/// </summary> /// </summary>
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs /// <param name="socket"></param>
{ /// <param name="clientStatus"></param>
/// <summary> public GenericTcpServerSocketStatusChangeEventArgs(object socket, SocketStatus clientStatus)
/// Gets or sets IsReady {
/// </summary> Socket = socket;
public bool IsReady; ClientStatus = clientStatus;
}
/// <summary> /// <summary>
/// Constructor ///
/// </summary> /// </summary>
/// <param name="isReady"></param> /// <param name="socket"></param>
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady) /// <param name="clientIndex"></param>
{ /// <param name="clientStatus"></param>
IsReady = isReady; public GenericTcpServerSocketStatusChangeEventArgs(object socket, uint clientIndex, SocketStatus clientStatus)
{
Socket = socket;
ReceivedFromClientIndex = clientIndex;
ClientStatus = clientStatus;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerSocketStatusChangeEventArgs() { }
} }
/// <summary> /// <summary>
/// S+ Constructor /// EventArgs for TCP server com method receive text
/// </summary> /// </summary>
public GenericTcpServerClientReadyForcommunicationsEventArgs() { } public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
}
/// <summary>
/// EventArgs for UDP connected
/// </summary>
public class GenericUdpConnectedEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the UConnected
/// </summary>
public ushort UConnected;
/// <summary>
/// Gets or sets the Connected status
/// </summary>
public bool Connected;
/// <summary>
/// Constructor
/// </summary>
public GenericUdpConnectedEventArgs() { }
/// <summary>
/// Constructor
/// </summary>
/// <param name="uconnected"></param>
public GenericUdpConnectedEventArgs(ushort uconnected)
{ {
UConnected = uconnected; /// <summary>
///
/// </summary>
public uint ReceivedFromClientIndex { get; private set; }
/// <summary>
///
/// </summary>
public ushort ReceivedFromClientIndexShort
{
get
{
return (ushort)ReceivedFromClientIndex;
}
}
/// <summary>
///
/// </summary>
public string Text { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="text"></param>
public GenericTcpServerCommMethodReceiveTextArgs(string text)
{
Text = text;
}
/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="clientIndex"></param>
public GenericTcpServerCommMethodReceiveTextArgs(string text, uint clientIndex)
{
Text = text;
ReceivedFromClientIndex = clientIndex;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerCommMethodReceiveTextArgs() { }
} }
/// <summary> /// <summary>
/// Constructor /// EventArgs for TCP server client ready for communication
/// </summary> /// </summary>
/// <param name="connected"></param> public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
public GenericUdpConnectedEventArgs(bool connected)
{ {
Connected = connected; /// <summary>
///
/// </summary>
public bool IsReady;
/// <summary>
///
/// </summary>
/// <param name="isReady"></param>
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
{
IsReady = isReady;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
} }
} /// <summary>
/// EventArgs for UDP connected
/// </summary>
public class GenericUdpConnectedEventArgs : EventArgs
{
/// <summary>
///
/// </summary>
public ushort UConnected;
/// <summary>
///
/// </summary>
public bool Connected;
/// <summary>
/// Constructor
/// </summary>
public GenericUdpConnectedEventArgs() { }
/// <summary>
///
/// </summary>
/// <param name="uconnected"></param>
public GenericUdpConnectedEventArgs(ushort uconnected)
{
UConnected = uconnected;
}
/// <summary>
///
/// </summary>
/// <param name="connected"></param>
public GenericUdpConnectedEventArgs(bool connected)
{
Connected = connected;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,398 +1,396 @@
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>
/// Generic UDP Server device
/// </summary>
public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
{ {
private const string SplusKey = "Uninitialized Udp Server";
/// <summary> /// <summary>
/// Object to enable stream debugging /// Generic UDP Server device
/// </summary> /// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; } public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
/// <summary> {
/// private const string SplusKey = "Uninitialized Udp Server";
/// </summary> /// <summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived; /// Object to enable stream debugging
/// </summary>
public CommunicationStreamDebugging StreamDebugging { get; private set; }
/// <summary>
///
/// </summary>
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary> /// <summary>
/// This event will fire when a message is dequeued that includes the source IP and Port info if needed to determine the source of the received data. /// This event will fire when a message is dequeued that includes the source IP and Port info if needed to determine the source of the received data.
/// </summary> /// </summary>
public event EventHandler<GenericUdpReceiveTextExtraArgs> DataRecievedExtra; public event EventHandler<GenericUdpReceiveTextExtraArgs> DataRecievedExtra;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange; public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public event EventHandler<GenericUdpConnectedEventArgs> UpdateConnectionStatus; public event EventHandler<GenericUdpConnectedEventArgs> UpdateConnectionStatus;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public SocketStatus ClientStatus public SocketStatus ClientStatus
{
get
{
return Server.ServerStatus;
}
}
/// <summary>
/// Represents a GenericUdpReceiveTextExtraArgs
/// </summary>
public ushort UStatus
{
get { return (ushort)Server.ServerStatus; }
}
/// <summary>
/// Address of server
/// </summary>
public string Hostname { get; set; }
/// <summary>
/// Port on server
/// </summary>
public int Port { get; set; }
/// <summary>
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints
/// which screws up things
/// </summary>
public ushort UPort
{
get { return Convert.ToUInt16(Port); }
set { Port = Convert.ToInt32(value); }
}
/// <summary>
/// Indicates that the UDP Server is enabled
/// </summary>
public bool IsConnected
{
get;
private set;
}
/// <summary>
/// Numeric value indicating
/// </summary>
public ushort UIsConnected
{
get { return IsConnected ? (ushort)1 : (ushort)0; }
}
/// <summary>
/// Defaults to 2000
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// The server
/// </summary>
public UDPServer Server { get; private set; }
/// <summary>
/// Constructor for S+. Make sure to set key, address, port, and buffersize using init method
/// </summary>
public GenericUdpServer()
: base(SplusKey)
{
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
BufferSize = 5000;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="buffefSize"></param>
public GenericUdpServer(string key, string address, int port, int buffefSize)
: base(key)
{
StreamDebugging = new CommunicationStreamDebugging(key);
Hostname = address;
Port = port;
BufferSize = buffefSize;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
}
/// <summary>
/// Call from S+ to initialize values
/// </summary>
/// <param name="key"></param>
/// <param name="address"></param>
/// <param name="port"></param>
public void Initialize(string key, string address, ushort port)
{
Key = key;
Hostname = address;
UPort = port;
}
/// <summary>
///
/// </summary>
/// <param name="ethernetEventArgs"></param>
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
{
// Re-enable the server if the link comes back up and the status should be connected
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp
&& IsConnected)
{
Connect();
}
}
/// <summary>
///
/// </summary>
/// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType != eProgramStatusEventType.Stopping)
return;
Debug.Console(1, this, "Program stopping. Disabling Server");
Disconnect();
}
/// <summary>
/// Enables the UDP Server
/// </summary>
public void Connect()
{
if (Server == null)
{
Server = new UDPServer();
}
if (string.IsNullOrEmpty(Hostname))
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key);
return;
}
if (Port < 1 || Port > 65535)
{ {
get
{ {
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key); return Server.ServerStatus;
return;
} }
} }
var status = Server.EnableUDPServer(Hostname, Port);
Debug.Console(2, this, "SocketErrorCode: {0}", status);
if (status == SocketErrorCodes.SOCKET_OK)
IsConnected = true;
var handler = UpdateConnectionStatus;
if (handler != null)
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
// Start receiving data
Server.ReceiveDataAsync(Receive);
}
/// <summary>
/// Disabled the UDP Server
/// </summary>
public void Disconnect()
{
if(Server != null)
Server.DisableUDPServer();
IsConnected = false;
var handler = UpdateConnectionStatus;
if (handler != null)
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
}
/// <summary>
/// Recursive method to receive data
/// </summary>
/// <param name="server"></param>
/// <param name="numBytes"></param>
void Receive(UDPServer server, int numBytes)
{
Debug.Console(2, this, "Received {0} bytes", numBytes);
try
{
if (numBytes <= 0)
return;
var sourceIp = Server.IPAddressLastMessageReceivedFrom;
var sourcePort = Server.IPPortLastMessageReceivedFrom;
var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray();
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
var dataRecivedExtra = DataRecievedExtra;
if (dataRecivedExtra != null)
dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes));
Debug.Console(2, this, "Bytes: {0}", bytes.ToString());
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
if (textHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
}
}
catch (Exception ex)
{
this.LogException(ex, "GenericUdpServer Receive error");
}
finally
{
server.ReceiveDataAsync(Receive);
}
}
/// <summary>
/// General send method
/// </summary>
/// <param name="text"></param>
public void SendText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
if (IsConnected && Server != null)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
Server.SendData(bytes, bytes.Length);
}
}
/// <summary>
///
/// </summary>
/// <param name="bytes"></param>
public void SendBytes(byte[] bytes)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
if (IsConnected && Server != null)
Server.SendData(bytes, bytes.Length);
}
}
/// <summary>
///
/// </summary>
public class GenericUdpReceiveTextExtraArgs : EventArgs
{
/// <summary>
///
/// </summary>
public string Text { get; private set; }
/// <summary>
///
/// </summary>
public string IpAddress { get; private set; }
/// <summary>
///
/// </summary>
public int Port { get; private set; }
/// <summary>
///
/// </summary>
public byte[] Bytes { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="bytes"></param>
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
{
Text = text;
IpAddress = ipAddress;
Port = port;
Bytes = bytes;
}
/// <summary> /// <summary>
/// Stupid S+ Constructor ///
/// </summary> /// </summary>
public GenericUdpReceiveTextExtraArgs() { } public ushort UStatus
{
get { return (ushort)Server.ServerStatus; }
}
/// <summary>
/// Address of server
/// </summary>
public string Hostname { get; set; }
/// <summary>
/// Port on server
/// </summary>
public int Port { get; set; }
/// <summary>
/// Another damn S+ helper because S+ seems to treat large port nums as signed ints
/// which screws up things
/// </summary>
public ushort UPort
{
get { return Convert.ToUInt16(Port); }
set { Port = Convert.ToInt32(value); }
}
/// <summary>
/// Indicates that the UDP Server is enabled
/// </summary>
public bool IsConnected
{
get;
private set;
}
/// <summary>
/// Numeric value indicating
/// </summary>
public ushort UIsConnected
{
get { return IsConnected ? (ushort)1 : (ushort)0; }
}
/// <summary>
/// Defaults to 2000
/// </summary>
public int BufferSize { get; set; }
/// <summary>
/// The server
/// </summary>
public UDPServer Server { get; private set; }
/// <summary>
/// Constructor for S+. Make sure to set key, address, port, and buffersize using init method
/// </summary>
public GenericUdpServer()
: base(SplusKey)
{
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
BufferSize = 5000;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="address"></param>
/// <param name="port"></param>
/// <param name="buffefSize"></param>
public GenericUdpServer(string key, string address, int port, int buffefSize)
: base(key)
{
StreamDebugging = new CommunicationStreamDebugging(key);
Hostname = address;
Port = port;
BufferSize = buffefSize;
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
CrestronEnvironment.EthernetEventHandler += new EthernetEventHandler(CrestronEnvironment_EthernetEventHandler);
}
/// <summary>
/// Call from S+ to initialize values
/// </summary>
/// <param name="key"></param>
/// <param name="address"></param>
/// <param name="port"></param>
public void Initialize(string key, string address, ushort port)
{
Key = key;
Hostname = address;
UPort = port;
}
/// <summary>
///
/// </summary>
/// <param name="ethernetEventArgs"></param>
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
{
// Re-enable the server if the link comes back up and the status should be connected
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp
&& IsConnected)
{
Connect();
}
}
/// <summary>
///
/// </summary>
/// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType != eProgramStatusEventType.Stopping)
return;
Debug.Console(1, this, "Program stopping. Disabling Server");
Disconnect();
}
/// <summary>
/// Enables the UDP Server
/// </summary>
public void Connect()
{
if (Server == null)
{
Server = new UDPServer();
}
if (string.IsNullOrEmpty(Hostname))
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key);
return;
}
if (Port < 1 || Port > 65535)
{
{
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key);
return;
}
}
var status = Server.EnableUDPServer(Hostname, Port);
Debug.Console(2, this, "SocketErrorCode: {0}", status);
if (status == SocketErrorCodes.SOCKET_OK)
IsConnected = true;
var handler = UpdateConnectionStatus;
if (handler != null)
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
// Start receiving data
Server.ReceiveDataAsync(Receive);
}
/// <summary>
/// Disabled the UDP Server
/// </summary>
public void Disconnect()
{
if(Server != null)
Server.DisableUDPServer();
IsConnected = false;
var handler = UpdateConnectionStatus;
if (handler != null)
handler(this, new GenericUdpConnectedEventArgs(UIsConnected));
}
/// <summary>
/// Recursive method to receive data
/// </summary>
/// <param name="server"></param>
/// <param name="numBytes"></param>
void Receive(UDPServer server, int numBytes)
{
Debug.Console(2, this, "Received {0} bytes", numBytes);
try
{
if (numBytes <= 0)
return;
var sourceIp = Server.IPAddressLastMessageReceivedFrom;
var sourcePort = Server.IPPortLastMessageReceivedFrom;
var bytes = server.IncomingDataBuffer.Take(numBytes).ToArray();
var str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
var dataRecivedExtra = DataRecievedExtra;
if (dataRecivedExtra != null)
dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes));
Debug.Console(2, this, "Bytes: {0}", bytes.ToString());
var bytesHandler = BytesReceived;
if (bytesHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
{
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
}
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
}
var textHandler = TextReceived;
if (textHandler != null)
{
if (StreamDebugging.RxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
}
}
catch (Exception ex)
{
this.LogException(ex, "GenericUdpServer Receive error");
}
finally
{
server.ReceiveDataAsync(Receive);
}
}
/// <summary>
/// General send method
/// </summary>
/// <param name="text"></param>
public void SendText(string text)
{
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
if (IsConnected && Server != null)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
Server.SendData(bytes, bytes.Length);
}
}
/// <summary>
///
/// </summary>
/// <param name="bytes"></param>
public void SendBytes(byte[] bytes)
{
if (StreamDebugging.TxStreamDebuggingIsEnabled)
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
if (IsConnected && Server != null)
Server.SendData(bytes, bytes.Length);
}
} }
/// <summary>
///
/// </summary>
public class UdpServerPropertiesConfig
{
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] public class GenericUdpReceiveTextExtraArgs : EventArgs
public string Address { get; set; } {
/// <summary>
///
/// </summary>
public string Text { get; private set; }
/// <summary>
///
/// </summary>
public string IpAddress { get; private set; }
/// <summary>
///
/// </summary>
public int Port { get; private set; }
/// <summary>
///
/// </summary>
public byte[] Bytes { get; private set; }
/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="bytes"></param>
public GenericUdpReceiveTextExtraArgs(string text, string ipAddress, int port, byte[] bytes)
{
Text = text;
IpAddress = ipAddress;
Port = port;
Bytes = bytes;
}
/// <summary>
/// Stupid S+ Constructor
/// </summary>
public GenericUdpReceiveTextExtraArgs() { }
}
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] public class UdpServerPropertiesConfig
public int Port { get; set; }
/// <summary>
/// Defaults to 32768
/// </summary>
public int BufferSize { get; set; }
/// <summary>
///
/// </summary>
public UdpServerPropertiesConfig()
{ {
BufferSize = 32768; /// <summary>
///
/// </summary>
[JsonProperty(Required = Required.Always)]
public string Address { get; set; }
/// <summary>
///
/// </summary>
[JsonProperty(Required = Required.Always)]
public int Port { get; set; }
/// <summary>
/// Defaults to 32768
/// </summary>
public int BufferSize { get; set; }
/// <summary>
///
/// </summary>
public UdpServerPropertiesConfig()
{
BufferSize = 32768;
}
} }
} }

View File

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

View File

@@ -1,60 +1,59 @@
extern alias NewtonsoftJson; using Newtonsoft.Json;
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute; namespace PepperDash.Core
namespace PepperDash.Core;
/// <summary>
/// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat
/// </summary>
public class TcpClientConfigObject
{ {
/// <summary> /// <summary>
/// TcpSsh Properties /// Client config object for TCP client with server that inherits from TcpSshPropertiesConfig and adds properties for shared key and heartbeat
/// </summary> /// </summary>
[JsonProperty("control")] public class TcpClientConfigObject
public ControlPropertiesConfig Control { get; set; } {
/// <summary>
/// TcpSsh Properties
/// </summary>
[JsonProperty("control")]
public ControlPropertiesConfig Control { get; set; }
/// <summary> /// <summary>
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic /// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
/// </summary> /// </summary>
[JsonProperty("secure")] [JsonProperty("secure")]
public bool Secure { get; set; } public bool Secure { get; set; }
/// <summary> /// <summary>
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client /// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
/// </summary> /// </summary>
[JsonProperty("sharedKeyRequired")] [JsonProperty("sharedKeyRequired")]
public bool SharedKeyRequired { get; set; } public bool SharedKeyRequired { get; set; }
/// <summary> /// <summary>
/// The shared key that must match on the server and client /// The shared key that must match on the server and client
/// </summary> /// </summary>
[JsonProperty("sharedKey")] [JsonProperty("sharedKey")]
public string SharedKey { get; set; } public string SharedKey { get; set; }
/// <summary> /// <summary>
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received. /// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
/// heartbeats do not raise received events. /// heartbeats do not raise received events.
/// </summary> /// </summary>
[JsonProperty("heartbeatRequired")] [JsonProperty("heartbeatRequired")]
public bool HeartbeatRequired { get; set; } public bool HeartbeatRequired { get; set; }
/// <summary> /// <summary>
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected /// The interval in seconds for the heartbeat from the client. If not received client is disconnected
/// </summary> /// </summary>
[JsonProperty("heartbeatRequiredIntervalInSeconds")] [JsonProperty("heartbeatRequiredIntervalInSeconds")]
public ushort HeartbeatRequiredIntervalInSeconds { get; set; } public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
/// <summary> /// <summary>
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided. /// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
/// </summary> /// </summary>
[JsonProperty("heartbeatStringToMatch")] [JsonProperty("heartbeatStringToMatch")]
public string HeartbeatStringToMatch { get; set; } public string HeartbeatStringToMatch { get; set; }
/// <summary> /// <summary>
/// Receive Queue size must be greater than 20 or defaults to 20 /// Receive Queue size must be greater than 20 or defaults to 20
/// </summary> /// </summary>
[JsonProperty("receiveQueueSize")] [JsonProperty("receiveQueueSize")]
public int ReceiveQueueSize { get; set; } public int ReceiveQueueSize { get; set; }
}
} }

View File

@@ -4,56 +4,57 @@ using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
namespace PepperDash.Core; namespace PepperDash.Core
/// <summary>
/// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
/// </summary>
public class TcpServerConfigObject
{ {
/// <summary> /// <summary>
/// Uique key /// Tcp Server Config object with properties for a tcp server with shared key and heartbeat capabilities
/// </summary> /// </summary>
public string Key { get; set; } public class TcpServerConfigObject
/// <summary> {
/// Max Clients that the server will allow to connect. /// <summary>
/// </summary> /// Uique key
public ushort MaxClients { get; set; } /// </summary>
/// <summary> public string Key { get; set; }
/// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic /// <summary>
/// </summary> /// Max Clients that the server will allow to connect.
public bool Secure { get; set; } /// </summary>
/// <summary> public ushort MaxClients { get; set; }
/// Port for the server to listen on /// <summary>
/// </summary> /// Bool value for secure. Currently not implemented in TCP sockets as they are not dynamic
public int Port { get; set; } /// </summary>
/// <summary> public bool Secure { get; set; }
/// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client /// <summary>
/// </summary> /// Port for the server to listen on
public bool SharedKeyRequired { get; set; } /// </summary>
/// <summary> public int Port { get; set; }
/// The shared key that must match on the server and client /// <summary>
/// </summary> /// Require a shared key that both server and client negotiate. If negotiation fails server disconnects the client
public string SharedKey { get; set; } /// </summary>
/// <summary> public bool SharedKeyRequired { get; set; }
/// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received. /// <summary>
/// heartbeats do not raise received events. /// The shared key that must match on the server and client
/// </summary> /// </summary>
public bool HeartbeatRequired { get; set; } public string SharedKey { get; set; }
/// <summary> /// <summary>
/// The interval in seconds for the heartbeat from the client. If not received client is disconnected /// Require a heartbeat on the client/server connection that will cause the server/client to disconnect if the heartbeat is not received.
/// </summary> /// heartbeats do not raise received events.
public ushort HeartbeatRequiredIntervalInSeconds { get; set; } /// </summary>
/// <summary> public bool HeartbeatRequired { get; set; }
/// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided. /// <summary>
/// </summary> /// The interval in seconds for the heartbeat from the client. If not received client is disconnected
public string HeartbeatStringToMatch { get; set; } /// </summary>
/// <summary> public ushort HeartbeatRequiredIntervalInSeconds { get; set; }
/// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000 /// <summary>
/// </summary> /// HeartbeatString that will be checked against the message received. defaults to heartbeat if no string is provided.
public int BufferSize { get; set; } /// </summary>
/// <summary> public string HeartbeatStringToMatch { get; set; }
/// Receive Queue size must be greater than 20 or defaults to 20 /// <summary>
/// </summary> /// Client buffer size. See Crestron help. defaults to 2000 if not greater than 2000
public int ReceiveQueueSize { get; set; } /// </summary>
public int BufferSize { get; set; }
/// <summary>
/// Receive Queue size must be greater than 20 or defaults to 20
/// </summary>
public int ReceiveQueueSize { get; set; }
}
} }

View File

@@ -4,83 +4,76 @@ using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
namespace PepperDash.Core; namespace PepperDash.Core
/// <summary>
/// Crestron Control Methods for a comm object
/// </summary>
public enum eControlMethod
{ {
/// <summary> /// <summary>
/// /// Crestron Control Methods for a comm object
/// </summary> /// </summary>
None = 0, public enum eControlMethod
/// <summary> {
/// RS232/422/485 /// <summary>
/// </summary> ///
Com, /// </summary>
/// <summary> None = 0,
/// Crestron IpId (most Crestron ethernet devices) /// <summary>
/// </summary> /// RS232/422/485
IpId, /// </summary>
/// <summary> Com,
/// Crestron IpIdTcp (HD-MD series, etc.) /// <summary>
/// </summary> /// Crestron IpId (most Crestron ethernet devices)
IpidTcp, /// </summary>
/// <summary> IpId,
/// Crestron IR control /// <summary>
/// </summary> /// Crestron IpIdTcp (HD-MD series, etc.)
IR, /// </summary>
/// <summary> IpidTcp,
/// SSH client /// <summary>
/// </summary> /// Crestron IR control
Ssh, /// </summary>
/// <summary> IR,
/// TCP/IP client /// <summary>
/// </summary> /// SSH client
Tcpip, /// </summary>
/// <summary> Ssh,
/// Telnet /// <summary>
/// </summary> /// TCP/IP client
Telnet, /// </summary>
/// <summary> Tcpip,
/// Crestnet device /// <summary>
/// </summary> /// Telnet
Cresnet, /// </summary>
/// <summary> Telnet,
/// CEC Control, via a DM HDMI port /// <summary>
/// </summary> /// Crestnet device
Cec, /// </summary>
/// <summary> Cresnet,
/// UDP Server /// <summary>
/// </summary> /// CEC Control, via a DM HDMI port
Udp, /// </summary>
/// <summary> Cec,
/// HTTP client /// <summary>
/// </summary> /// UDP Server
Http, /// </summary>
/// <summary> Udp,
/// HTTPS client /// <summary>
/// </summary> /// HTTP client
Https, /// </summary>
/// <summary> Http,
/// Websocket client /// <summary>
/// </summary> /// HTTPS client
Ws, /// </summary>
/// <summary> Https,
/// Secure Websocket client /// <summary>
/// </summary> /// Websocket client
Wss, /// </summary>
/// <summary> Ws,
/// Secure TCP/IP /// <summary>
/// </summary> /// Secure Websocket client
SecureTcpIp, /// </summary>
/// <summary> Wss,
/// Crestron COM bridge /// <summary>
/// </summary> /// Secure TCP/IP
ComBridge, /// </summary>
/// <summary> SecureTcpIp
/// Crestron Infinet EX device }
/// </summary>
InfinetEx
} }

View File

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

View File

@@ -1,84 +1,80 @@
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>
/// An incoming communication stream
/// </summary>
public interface ICommunicationReceiver : IKeyed
{ {
/// <summary> /// <summary>
/// Notifies of bytes received /// An incoming communication stream
/// </summary> /// </summary>
event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived; public interface ICommunicationReceiver : IKeyed
/// <summary> {
/// Notifies of text received /// <summary>
/// </summary> /// Notifies of bytes received
event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived; /// </summary>
event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
/// <summary>
/// Notifies of text received
/// </summary>
event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
/// <summary> /// <summary>
/// Indicates connection status /// Indicates connection status
/// </summary> /// </summary>
[JsonProperty("isConnected")] [JsonProperty("isConnected")]
bool IsConnected { get; } bool IsConnected { get; }
/// <summary> /// <summary>
/// Connect to the device /// Connect to the device
/// </summary> /// </summary>
void Connect(); void Connect();
/// <summary> /// <summary>
/// Disconnect from the device /// Disconnect from the device
/// </summary> /// </summary>
void Disconnect(); void Disconnect();
}
/// <summary>
/// Extends <see cref="ICommunicationReceiver"/> with methods for sending text and bytes to a device.
/// </summary>
public interface IBasicCommunication : ICommunicationReceiver
{
/// <summary>
/// Send text to the device
/// </summary>
/// <param name="text"></param>
void SendText(string text);
/// <summary>
/// Send bytes to the device
/// </summary>
/// <param name="bytes"></param>
void SendBytes(byte[] bytes);
} }
/// <summary> /// <summary>
/// Represents a device that implements IBasicCommunication and IStreamDebugging /// Represents a device that uses basic connection
/// </summary> /// </summary>
public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging public interface IBasicCommunication : ICommunicationReceiver
{ {
/// <summary>
/// Send text to the device
/// </summary>
/// <param name="text"></param>
void SendText(string text);
} /// <summary>
/// Send bytes to the device
/// </summary>
/// <param name="bytes"></param>
void SendBytes(byte[] bytes);
}
/// <summary>
/// Represents a device with stream debugging capablities
/// </summary>
public interface IStreamDebugging : IKeyed
{
/// <summary> /// <summary>
/// Object to enable stream debugging /// Represents a device that implements IBasicCommunication and IStreamDebugging
/// </summary> /// </summary>
[JsonProperty("streamDebugging")] public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, IStreamDebugging
CommunicationStreamDebugging StreamDebugging { get; } {
}
}
/// <summary>
/// Represents a device with stream debugging capablities
/// </summary>
public interface IStreamDebugging
{
/// <summary>
/// Object to enable stream debugging
/// </summary>
[JsonProperty("streamDebugging")]
CommunicationStreamDebugging StreamDebugging { get; }
}
/// <summary> /// <summary>
/// For IBasicCommunication classes that have SocketStatus. GenericSshClient, /// For IBasicCommunication classes that have SocketStatus. GenericSshClient,
@@ -86,41 +82,41 @@ public interface IStreamDebugging : IKeyed
/// </summary> /// </summary>
public interface ISocketStatus : IBasicCommunication public interface ISocketStatus : IBasicCommunication
{ {
/// <summary> /// <summary>
/// Notifies of socket status changes /// Notifies of socket status changes
/// </summary> /// </summary>
event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange; event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
/// <summary> /// <summary>
/// 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; }
} }
/// <summary> /// <summary>
/// Describes a device that implements ISocketStatus and IStreamDebugging /// Describes a device that implements ISocketStatus and IStreamDebugging
/// </summary> /// </summary>
public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugging
{ {
} }
/// <summary> /// <summary>
/// Describes a device that can automatically attempt to reconnect /// Describes a device that can automatically attempt to reconnect
/// </summary> /// </summary>
public interface IAutoReconnect public interface IAutoReconnect
{ {
/// <summary> /// <summary>
/// Enable automatic recconnect /// Enable automatic recconnect
/// </summary> /// </summary>
[JsonProperty("autoReconnect")] [JsonProperty("autoReconnect")]
bool AutoReconnect { get; set; } bool AutoReconnect { get; set; }
/// <summary> /// <summary>
/// Interval in ms to attempt automatic recconnections /// Interval in ms to attempt automatic recconnections
/// </summary> /// </summary>
[JsonProperty("autoReconnectIntervalMs")] [JsonProperty("autoReconnectIntervalMs")]
int AutoReconnectIntervalMs { get; set; } int AutoReconnectIntervalMs { get; set; }
} }
@@ -129,81 +125,80 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
/// </summary> /// </summary>
public enum eGenericCommMethodStatusChangeType public enum eGenericCommMethodStatusChangeType
{ {
/// <summary> /// <summary>
/// Connected /// Connected
/// </summary> /// </summary>
Connected, Connected,
/// <summary> /// <summary>
/// Disconnected /// Disconnected
/// </summary> /// </summary>
Disconnected Disconnected
} }
/// <summary> /// <summary>
/// This delegate defines handler for IBasicCommunication status changes /// This delegate defines handler for IBasicCommunication status changes
/// </summary> /// </summary>
/// <param name="comm">Device firing the status change</param> /// <param name="comm">Device firing the status change</param>
/// <param name="status"></param> /// <param name="status"></param>
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 ///
/// </summary> /// </summary>
public byte[] Bytes { get; private set; } public byte[] Bytes { get; private set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="bytes"></param>
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
{
Bytes = bytes;
}
/// <summary> /// <summary>
/// S+ Constructor ///
/// </summary> /// </summary>
public GenericCommMethodReceiveBytesArgs() { } /// <param name="bytes"></param>
} public GenericCommMethodReceiveBytesArgs(byte[] bytes)
{
Bytes = bytes;
}
/// <summary>
/// S+ Constructor
/// </summary>
public GenericCommMethodReceiveBytesArgs() { }
}
/// <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)
{ {
Text = text; Text = text;
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="text"></param> /// <param name="text"></param>
/// <param name="delimiter"></param> /// <param name="delimiter"></param>
public GenericCommMethodReceiveTextArgs(string text, string delimiter) public GenericCommMethodReceiveTextArgs(string text, string delimiter)
:this(text) :this(text)
{ {
Delimiter = delimiter; Delimiter = delimiter;
} }
/// <summary> /// <summary>
/// S+ Constructor /// S+ Constructor
@@ -214,38 +209,39 @@ 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
{ {
/// <summary> /// <summary>
/// Gets escaped text for a byte array /// Gets escaped text for a byte array
/// </summary> /// </summary>
/// <param name="bytes"></param> /// <param name="bytes"></param>
/// <returns></returns> /// <returns></returns>
public static string GetEscapedText(byte[] bytes) public static string GetEscapedText(byte[] bytes)
{ {
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
} }
/// <summary> /// <summary>
/// Gets escaped text for a string /// Gets escaped text for a string
/// </summary> /// </summary>
/// <param name="text"></param> /// <param name="text"></param>
/// <returns></returns> /// <returns></returns>
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);
return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray()); return String.Concat(bytes.Select(b => string.Format(@"[{0:X2}]", (int)b)).ToArray());
} }
/// <summary> /// <summary>
/// Gets debug text for a string /// Gets debug text for a string
/// </summary> /// </summary>
/// <param name="text"></param> /// <param name="text"></param>
/// <returns></returns> /// <returns></returns>
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));
} }
} }
}

View File

@@ -1,69 +1,48 @@
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
/// </summary> /// </summary>
public class PortalConfigReader public class PortalConfigReader
{ {
const string template = "template"; /// <summary>
const string system = "system"; /// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
const string systemUrl = "system_url"; /// </summary>
const string templateUrl = "template_url"; /// <returns>JObject of config file</returns>
const string info = "info"; public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
const string devices = "devices";
const string rooms = "rooms";
const string sourceLists = "sourceLists";
const string destinationLists = "destinationLists";
const string cameraLists = "cameraLists";
const string audioControlPointLists = "audioControlPointLists";
const string tieLines = "tieLines";
const string joinMaps = "joinMaps";
const string global = "global";
/// <summary>
/// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
/// </summary>
/// <returns>JObject of config file</returns>
public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
{ {
try try
{ {
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
Debug.LogError( Debug.Console(1, Debug.ErrorLogLevel.Error,
"ERROR: Configuration file not present. Please load file to {0} and reset program", filePath); "ERROR: Configuration file not present. Please load file to {0} and reset program", filePath);
} }
using (StreamReader fs = new StreamReader(filePath)) using (StreamReader fs = new StreamReader(filePath))
{ {
var jsonObj = JObject.Parse(fs.ReadToEnd()); var jsonObj = JObject.Parse(fs.ReadToEnd());
if(jsonObj[template] != null && jsonObj[system] != null) if(jsonObj["template"] != null && jsonObj["system"] != null)
{ {
// it's a double-config, merge it. // it's a double-config, merge it.
var merged = MergeConfigs(jsonObj); var merged = MergeConfigs(jsonObj);
if (jsonObj[systemUrl] != null) if (jsonObj["system_url"] != null)
{ {
merged[systemUrl] = (string)jsonObj[systemUrl]; merged["systemUrl"] = jsonObj["system_url"].Value<string>();
} }
if (jsonObj[templateUrl] != null) if (jsonObj["template_url"] != null)
{ {
merged[templateUrl] = (string)jsonObj[templateUrl]; merged["templateUrl"] = jsonObj["template_url"].Value<string>();
} }
jsonObj = merged; jsonObj = merged;
@@ -88,9 +67,6 @@ namespace PepperDash.Core.Config;
/// </summary> /// </summary>
/// <param name="doubleConfig"></param> /// <param name="doubleConfig"></param>
/// <returns></returns> /// <returns></returns>
/// <summary>
/// MergeConfigs method
/// </summary>
public static JObject MergeConfigs(JObject doubleConfig) public static JObject MergeConfigs(JObject doubleConfig)
{ {
var system = JObject.FromObject(doubleConfig["system"]); var system = JObject.FromObject(doubleConfig["system"]);
@@ -98,62 +74,62 @@ namespace PepperDash.Core.Config;
var merged = new JObject(); var merged = new JObject();
// Put together top-level objects // Put together top-level objects
if (system[info] != null) if (system["info"] != null)
merged.Add(info, Merge(template[info], system[info], info)); merged.Add("info", Merge(template["info"], system["info"], "infO"));
else else
merged.Add(info, template[info]); merged.Add("info", template["info"]);
merged.Add(devices, MergeArraysOnTopLevelProperty(template[devices] as JArray, merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray,
system[devices] as JArray, "key", devices)); system["devices"] as JArray, "key", "devices"));
if (system[rooms] == null) if (system["rooms"] == null)
merged.Add(rooms, template[rooms]); merged.Add("rooms", template["rooms"]);
else else
merged.Add(rooms, MergeArraysOnTopLevelProperty(template[rooms] as JArray, merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray,
system[rooms] as JArray, "key", rooms)); system["rooms"] as JArray, "key", "rooms"));
if (system[sourceLists] == null) if (system["sourceLists"] == null)
merged.Add(sourceLists, template[sourceLists]); merged.Add("sourceLists", template["sourceLists"]);
else else
merged.Add(sourceLists, Merge(template[sourceLists], system[sourceLists], sourceLists)); merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"], "sourceLists"));
if (system[destinationLists] == null) if (system["destinationLists"] == null)
merged.Add(destinationLists, template[destinationLists]); merged.Add("destinationLists", template["destinationLists"]);
else else
merged.Add(destinationLists, merged.Add("destinationLists",
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); //Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
return merged; return merged;
@@ -171,26 +147,26 @@ namespace PepperDash.Core.Config;
return a1; return a1;
else if (a1 != null) else if (a1 != null)
{ {
if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array if (a2[0]["key"] == null) // If the first item in the system array has no key, overwrite the template array
{ // with the system array { // with the system array
return a2; return a2;
} }
else // The arrays are keyed, merge them by key else // The arrays are keyed, merge them by key
{ {
for (int i = 0; i < a1.Count(); i++) for (int i = 0; i < a1.Count(); i++)
{ {
var a1Dev = a1[i]; var a1Dev = a1[i];
// Try to get a system device and if found, merge it onto template // Try to get a system device and if found, merge it onto template
var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value<int>("uid") == tmplDev.Value<int>("uid")); var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value<int>("uid") == tmplDev.Value<int>("uid"));
if (a2Match != null) if (a2Match != null)
{ {
var mergedItem = Merge(a1Dev, a2Match, string.Format("{0}[{1}].", path, i));// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match)); var mergedItem = Merge(a1Dev, a2Match, string.Format("{0}[{1}].", path, i));// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match));
result.Add(mergedItem); result.Add(mergedItem);
} }
else else
result.Add(a1Dev); result.Add(a1Dev);
}
} }
}
} }
return result; return result;
} }
@@ -207,9 +183,9 @@ namespace PepperDash.Core.Config;
/// <summary> /// <summary>
/// Merge o2 onto o1 /// Merge o2 onto o1
/// </summary> /// </summary>
/// <param name="o1"></param> /// <param name="o1"></param>
/// <param name="o2"></param> /// <param name="o2"></param>
/// <param name="path"></param> /// <param name="path"></param>
static JObject Merge(JObject o1, JObject o2, string path) static JObject Merge(JObject o1, JObject o2, string path)
{ {
foreach (var o2Prop in o2) foreach (var o2Prop in o2)
@@ -249,10 +225,11 @@ namespace PepperDash.Core.Config;
} }
catch (Exception e) catch (Exception e)
{ {
Debug.LogError($"Cannot merge items at path {propPath}: \r{e}"); Debug.Console(1, Debug.ErrorLogLevel.Warning, "Cannot merge items at path {0}: \r{1}", propPath, e);
} }
} }
} }
return o1; return o1;
} }
} }
}

View File

@@ -4,18 +4,19 @@ using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
namespace PepperDash.Core; namespace PepperDash.Core
public class EncodingHelper
{ {
public static string ConvertUtf8ToAscii(string utf8String) public class EncodingHelper
{ {
return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length); public static string ConvertUtf8ToAscii(string utf8String)
} {
return Encoding.ASCII.GetString(Encoding.UTF8.GetBytes(utf8String), 0, utf8String.Length);
}
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);
} }
}
} }

View File

@@ -1,35 +1,35 @@
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; }
} }
/// <summary> /// <summary>
/// Named Keyed device interface. Forces the device to have a Unique Key and a name. /// Named Keyed device interface. Forces the device to have a Unique Key and a name.
/// </summary>
public interface IKeyName : IKeyed
{
/// <summary>
/// Gets the name associated with the current object.
/// </summary> /// </summary>
[JsonProperty("name")] public interface IKeyName : IKeyed
{
/// <summary>
/// Isn't it obvious :)
/// </summary>
[JsonProperty("name")]
string Name { get; } string Name { get; }
}
} }

View File

@@ -2,194 +2,178 @@
using System.Collections.Generic; using System.Collections.Generic;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Core; namespace PepperDash.Core
//*********************************************************************************************************
/// <summary>
/// Represents a Device
/// </summary>
public class Device : IKeyName
{ {
//*********************************************************************************************************
/// <summary> /// <summary>
/// Unique Key /// The core event and status-bearing class that most if not all device and connectors can derive from.
/// </summary> /// </summary>
public string Key { get; protected set; } public class Device : IKeyName
/// <summary>
/// Gets or sets the Name
/// </summary>
public string Name { get; protected set; }
/// <summary>
///
/// </summary>
public bool Enabled { get; protected set; }
/// <summary>
/// A place to store reference to the original config object, if any. These values should
/// NOT be used as properties on the device as they are all publicly-settable values.
/// </summary>
//public DeviceConfig Config { get; private set; }
/// <summary>
/// Helper method to check if Config exists
/// </summary>
//public bool HasConfig { get { return Config != null; } }
List<Action> _PreActivationActions;
List<Action> _PostActivationActions;
/// <summary>
///
/// </summary>
public static Device DefaultDevice { get { return _DefaultDevice; } }
static Device _DefaultDevice = new Device("Default", "Default");
/// <summary>
/// Base constructor for all Devices.
/// </summary>
/// <param name="key"></param>
public Device(string key)
{ {
Key = key;
if (key.Contains(".")) Debug.LogMessage(LogEventLevel.Information, "WARNING: Device key should not include '.'", this);
Name = "";
}
/// <summary> /// <summary>
/// Constructor with key and name /// Unique Key
/// </summary> /// </summary>
/// <param name="key"></param> public string Key { get; protected set; }
/// <param name="name"></param> /// <summary>
public Device(string key, string name) : this(key) /// Name of the devie
{ /// </summary>
Name = name; public string Name { get; protected set; }
/// <summary>
///
/// </summary>
public bool Enabled { get; protected set; }
} ///// <summary>
///// A place to store reference to the original config object, if any. These values should
///// NOT be used as properties on the device as they are all publicly-settable values.
///// </summary>
//public DeviceConfig Config { get; private set; }
///// <summary>
///// Helper method to check if Config exists
///// </summary>
//public bool HasConfig { get { return Config != null; } }
//public Device(DeviceConfig config) List<Action> _PreActivationActions;
// : this(config.Key, config.Name) List<Action> _PostActivationActions;
//{
// Config = config;
//}
/// <summary> /// <summary>
/// Adds a pre activation action ///
/// </summary> /// </summary>
/// <param name="act"></param> public static Device DefaultDevice { get { return _DefaultDevice; } }
public void AddPreActivationAction(Action act) static Device _DefaultDevice = new Device("Default", "Default");
{
if (_PreActivationActions == null)
_PreActivationActions = new List<Action>();
_PreActivationActions.Add(act);
}
/// <summary> /// <summary>
/// Adds a post activation action /// Base constructor for all Devices.
/// </summary> /// </summary>
/// <param name="act"></param> /// <param name="key"></param>
/// <summary> public Device(string key)
/// AddPostActivationAction method {
/// </summary> Key = key;
public void AddPostActivationAction(Action act) if (key.Contains(".")) Debug.LogMessage(LogEventLevel.Information, "WARNING: Device key should not include '.'", this);
{ Name = "";
if (_PostActivationActions == null) }
_PostActivationActions = new List<Action>();
_PostActivationActions.Add(act);
}
/// <summary> /// <summary>
/// PreActivate method /// Constructor with key and name
/// </summary> /// </summary>
public void PreActivate() /// <param name="key"></param>
{ /// <param name="name"></param>
if (_PreActivationActions != null) public Device(string key, string name) : this(key)
_PreActivationActions.ForEach(a => {
{ Name = name;
try
{
a.Invoke();
}
catch (Exception e)
{
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
}
});
}
/// <summary> }
/// Activate method
/// </summary>
public bool Activate()
{
//if (_PreActivationActions != null)
// _PreActivationActions.ForEach(a => a.Invoke());
var result = CustomActivate();
//if(result && _PostActivationActions != null)
// _PostActivationActions.ForEach(a => a.Invoke());
return result;
}
/// <summary> //public Device(DeviceConfig config)
/// PostActivate method // : this(config.Key, config.Name)
/// </summary> //{
public void PostActivate() // Config = config;
{ //}
if (_PostActivationActions != null)
_PostActivationActions.ForEach(a =>
{
try
{
a.Invoke();
}
catch (Exception e)
{
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
}
});
}
/// <summary> /// <summary>
/// Called in between Pre and PostActivationActions when Activate() is called. /// Adds a pre activation action
/// Override to provide addtitional setup when calling activation. Overriding classes /// </summary>
/// do not need to call base.CustomActivate() /// <param name="act"></param>
/// </summary> public void AddPreActivationAction(Action act)
/// <returns>true if device activated successfully.</returns> {
/// <summary> if (_PreActivationActions == null)
/// CustomActivate method _PreActivationActions = new List<Action>();
/// </summary> _PreActivationActions.Add(act);
public virtual bool CustomActivate() { return true; } }
/// <summary> /// <summary>
/// Call to deactivate device - unlink events, etc. Overriding classes do not /// Adds a post activation action
/// need to call base.Deactivate() /// </summary>
/// </summary> /// <param name="act"></param>
/// <returns></returns> public void AddPostActivationAction(Action act)
public virtual bool Deactivate() { return true; } {
if (_PostActivationActions == null)
_PostActivationActions = new List<Action>();
_PostActivationActions.Add(act);
}
/// <summary> /// <summary>
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize() /// Executes the preactivation actions
/// </summary> /// </summary>
public virtual void Initialize() public void PreActivate()
{ {
} if (_PreActivationActions != null)
_PreActivationActions.ForEach(a => {
try
{
a.Invoke();
} catch (Exception e)
{
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
}
});
}
/// <summary> /// <summary>
/// Helper method to check object for bool value false and fire an Action method /// Gets this device ready to be used in the system. Runs any added pre-activation items, and
/// </summary> /// all post-activation at end. Classes needing additional logic to
/// <param name="o">Should be of type bool, others will be ignored</param> /// run should override CustomActivate()
/// <param name="a">Action to be run when o is false</param> /// </summary>
public void OnFalse(object o, Action a) public bool Activate()
{ {
if (o is bool && !(bool)o) a(); //if (_PreActivationActions != null)
} // _PreActivationActions.ForEach(a => a.Invoke());
var result = CustomActivate();
//if(result && _PostActivationActions != null)
// _PostActivationActions.ForEach(a => a.Invoke());
return result;
}
/// <summary>
/// Executes the postactivation actions
/// </summary>
public void PostActivate()
{
if (_PostActivationActions != null)
_PostActivationActions.ForEach(a => {
try
{
a.Invoke();
}
catch (Exception e)
{
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
}
});
}
/// <summary>
/// Called in between Pre and PostActivationActions when Activate() is called.
/// Override to provide addtitional setup when calling activation. Overriding classes
/// do not need to call base.CustomActivate()
/// </summary>
/// <returns>true if device activated successfully.</returns>
public virtual bool CustomActivate() { return true; }
/// <summary>
/// Call to deactivate device - unlink events, etc. Overriding classes do not
/// need to call base.Deactivate()
/// </summary>
/// <returns></returns>
public virtual bool Deactivate() { return true; }
/// <summary>
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
/// </summary>
public virtual void Initialize()
{
}
/// <summary>
/// Helper method to check object for bool value false and fire an Action method
/// </summary>
/// <param name="o">Should be of type bool, others will be ignored</param>
/// <param name="a">Action to be run when o is false</param>
public void OnFalse(object o, Action a)
{
if (o is bool && !(bool)o) a();
}
/// <summary>
/// Returns a string representation of the object, including its key and name.
/// </summary>
/// <remarks>The returned string is formatted as "{Key} - {Name}". If the <c>Name</c> property is
/// null or empty, "---" is used in place of the name.</remarks>
/// <returns>A string that represents the object, containing the key and name in the format "{Key} - {Name}".</returns>
public override string ToString()
{
return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name);
} }
} }

View File

@@ -1,14 +1,12 @@
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. /// Class to help with accessing values from the CrestronEthernetHelper class
/// </summary> /// </summary>
public class EthernetHelper public class EthernetHelper
{ {
/// <summary> /// <summary>
@@ -26,9 +24,9 @@ namespace PepperDash.Core;
// ADD OTHER HELPERS HERE // ADD OTHER HELPERS HERE
/// <summary> /// <summary>
/// Gets or sets the PortNumber ///
/// </summary> /// </summary>
public int PortNumber { get; private set; } public int PortNumber { get; private set; }
private EthernetHelper(int portNumber) private EthernetHelper(int portNumber)
@@ -115,4 +113,5 @@ namespace PepperDash.Core;
CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0); CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_ROUTER, 0);
} }
} }
} }
}

View File

@@ -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>
@@ -16,19 +16,19 @@ namespace PepperDash.Core;
/// </summary> /// </summary>
public bool State { get; set; } public bool State { get; set; }
/// <summary> /// <summary>
/// Gets or sets the IntValue /// Boolean ushort value property
/// </summary> /// </summary>
public ushort IntValue { get { return (ushort)(State ? 1 : 0); } } public ushort IntValue { get { return (ushort)(State ? 1 : 0); } }
/// <summary> /// <summary>
/// Gets or sets the Type /// Boolean change event args type
/// </summary> /// </summary>
public ushort Type { get; set; } public ushort Type { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Index /// Boolean change event args index
/// </summary> /// </summary>
public ushort Index { get; set; } public ushort Index { get; set; }
/// <summary> /// <summary>
@@ -64,9 +64,9 @@ namespace PepperDash.Core;
} }
} }
/// <summary> /// <summary>
/// Represents a UshrtChangeEventArgs /// Ushort change event args
/// </summary> /// </summary>
public class UshrtChangeEventArgs : EventArgs public class UshrtChangeEventArgs : EventArgs
{ {
/// <summary> /// <summary>
@@ -74,14 +74,14 @@ namespace PepperDash.Core;
/// </summary> /// </summary>
public ushort IntValue { get; set; } public ushort IntValue { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Type /// Ushort change event args type
/// </summary> /// </summary>
public ushort Type { get; set; } public ushort Type { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Index /// Ushort change event args index
/// </summary> /// </summary>
public ushort Index { get; set; } public ushort Index { get; set; }
/// <summary> /// <summary>
@@ -117,9 +117,9 @@ namespace PepperDash.Core;
} }
} }
/// <summary> /// <summary>
/// Represents a StringChangeEventArgs /// String change event args
/// </summary> /// </summary>
public class StringChangeEventArgs : EventArgs public class StringChangeEventArgs : EventArgs
{ {
/// <summary> /// <summary>
@@ -127,14 +127,14 @@ namespace PepperDash.Core;
/// </summary> /// </summary>
public string StringValue { get; set; } public string StringValue { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Type /// String change event args type
/// </summary> /// </summary>
public ushort Type { get; set; } public ushort Type { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Index /// string change event args index
/// </summary> /// </summary>
public ushort Index { get; set; } public ushort Index { get; set; }
/// <summary> /// <summary>
@@ -168,4 +168,5 @@ namespace PepperDash.Core;
Type = type; Type = type;
Index = index; Index = index;
} }
} }
}

View File

@@ -4,13 +4,13 @@ using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
namespace PepperDash.Core.GenericRESTfulCommunications; namespace PepperDash.Core.GenericRESTfulCommunications
{
/// <summary> /// <summary>
/// Constants /// Constants
/// </summary> /// </summary>
public class GenericRESTfulConstants public class GenericRESTfulConstants
{ {
/// <summary> /// <summary>
/// Generic boolean change /// Generic boolean change
/// </summary> /// </summary>
@@ -35,4 +35,5 @@ public class GenericRESTfulConstants
/// Error string change /// Error string change
/// </summary> /// </summary>
public const ushort ErrorStringChange = 203; public const ushort ErrorStringChange = 203;
}
} }

View File

@@ -6,8 +6,8 @@ using Crestron.SimplSharp;
using Crestron.SimplSharp.Net.Http; using Crestron.SimplSharp.Net.Http;
using Crestron.SimplSharp.Net.Https; using Crestron.SimplSharp.Net.Https;
namespace PepperDash.Core.GenericRESTfulCommunications; namespace PepperDash.Core.GenericRESTfulCommunications
{
/// <summary> /// <summary>
/// Generic RESTful communication class /// Generic RESTful communication class
/// </summary> /// </summary>
@@ -42,7 +42,7 @@ namespace PepperDash.Core.GenericRESTfulCommunications;
/// <param name="requestType"></param> /// <param name="requestType"></param>
/// <param name="username"></param> /// <param name="username"></param>
/// <param name="password"></param> /// <param name="password"></param>
/// <param name="contentType"></param> /// <param name="contentType"></param>
public void SubmitRequest(string url, ushort port, ushort requestType, string contentType, string username, string password) public void SubmitRequest(string url, ushort port, ushort requestType, string contentType, string username, string password)
{ {
if (url.StartsWith("https:", StringComparison.OrdinalIgnoreCase)) if (url.StartsWith("https:", StringComparison.OrdinalIgnoreCase))
@@ -65,7 +65,7 @@ namespace PepperDash.Core.GenericRESTfulCommunications;
/// <param name="url"></param> /// <param name="url"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="requestType"></param> /// <param name="requestType"></param>
/// <param name="contentType"></param> /// <param name="contentType"></param>
/// <param name="username"></param> /// <param name="username"></param>
/// <param name="password"></param> /// <param name="password"></param>
private void SubmitRequestHttp(string url, ushort port, ushort requestType, string contentType, string username, string password) private void SubmitRequestHttp(string url, ushort port, ushort requestType, string contentType, string username, string password)
@@ -123,7 +123,7 @@ namespace PepperDash.Core.GenericRESTfulCommunications;
/// <param name="url"></param> /// <param name="url"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="requestType"></param> /// <param name="requestType"></param>
/// <param name="contentType"></param> /// <param name="contentType"></param>
/// <param name="username"></param> /// <param name="username"></param>
/// <param name="password"></param> /// <param name="password"></param>
private void SubmitRequestHttps(string url, ushort port, ushort requestType, string contentType, string username, string password) private void SubmitRequestHttps(string url, ushort port, ushort requestType, string contentType, string username, string password)
@@ -252,4 +252,5 @@ namespace PepperDash.Core.GenericRESTfulCommunications;
StringChange(this, args); StringChange(this, args);
} }
} }
} }
}

View File

@@ -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>
@@ -32,14 +32,14 @@ namespace PepperDash.Core.JsonStandardObjects;
/// </summary> /// </summary>
public DeviceConfig Device { get; set; } public DeviceConfig Device { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Type /// Device change event args type
/// </summary> /// </summary>
public ushort Type { get; set; } public ushort Type { get; set; }
/// <summary> /// <summary>
/// Gets or sets the Index /// Device change event args index
/// </summary> /// </summary>
public ushort Index { get; set; } public ushort Index { get; set; }
/// <summary> /// <summary>
@@ -73,4 +73,5 @@ namespace PepperDash.Core.JsonStandardObjects;
Type = type; Type = type;
Index = index; Index = index;
} }
} }
}

View File

@@ -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>
@@ -58,9 +58,6 @@ namespace PepperDash.Core.JsonStandardObjects;
/// </summary> /// </summary>
/// <param name="uniqueID"></param> /// <param name="uniqueID"></param>
/// <param name="deviceKey"></param> /// <param name="deviceKey"></param>
/// <summary>
/// Initialize method
/// </summary>
public void Initialize(string uniqueID, string deviceKey) public void Initialize(string uniqueID, string deviceKey)
{ {
// S+ set EvaluateFb low // S+ set EvaluateFb low
@@ -182,4 +179,5 @@ namespace PepperDash.Core.JsonStandardObjects;
} }
#endregion EventHandler Helpers #endregion EventHandler Helpers
} }
}

View File

@@ -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/#
@@ -47,60 +47,60 @@ namespace PepperDash.Core.JsonStandardObjects;
] ]
} }
*/ */
/// <summary> /// <summary>
/// Represents a ComParamsConfig /// Device communication parameter class
/// </summary> /// </summary>
public class ComParamsConfig public class ComParamsConfig
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int baudRate { get; set; } public int baudRate { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int dataBits { get; set; } public int dataBits { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int stopBits { get; set; } public int stopBits { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string parity { get; set; } public string parity { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string protocol { get; set; } public string protocol { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string hardwareHandshake { get; set; } public string hardwareHandshake { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string softwareHandshake { get; set; } public string softwareHandshake { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int pacing { get; set; } public int pacing { get; set; }
// convert properties for simpl // convert properties for simpl
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort simplBaudRate { get { return Convert.ToUInt16(baudRate); } } public ushort simplBaudRate { get { return Convert.ToUInt16(baudRate); } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort simplDataBits { get { return Convert.ToUInt16(dataBits); } } public ushort simplDataBits { get { return Convert.ToUInt16(dataBits); } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort simplStopBits { get { return Convert.ToUInt16(stopBits); } } public ushort simplStopBits { get { return Convert.ToUInt16(stopBits); } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort simplPacing { get { return Convert.ToUInt16(pacing); } } public ushort simplPacing { get { return Convert.ToUInt16(pacing); } }
/// <summary> /// <summary>
@@ -117,43 +117,43 @@ namespace PepperDash.Core.JsonStandardObjects;
/// </summary> /// </summary>
public class TcpSshPropertiesConfig public class TcpSshPropertiesConfig
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string address { get; set; } public string address { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int port { get; set; } public int port { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string username { get; set; } public string username { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string password { get; set; } public string password { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public bool autoReconnect { get; set; } public bool autoReconnect { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int autoReconnectIntervalMs { get; set; } public int autoReconnectIntervalMs { get; set; }
// convert properties for simpl // convert properties for simpl
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort simplPort { get { return Convert.ToUInt16(port); } } public ushort simplPort { get { return Convert.ToUInt16(port); } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort simplAutoReconnect { get { return (ushort)(autoReconnect ? 1 : 0); } } public ushort simplAutoReconnect { get { return (ushort)(autoReconnect ? 1 : 0); } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort simplAutoReconnectIntervalMs { get { return Convert.ToUInt16(autoReconnectIntervalMs); } } public ushort simplAutoReconnectIntervalMs { get { return Convert.ToUInt16(autoReconnectIntervalMs); } }
/// <summary> /// <summary>
@@ -170,31 +170,31 @@ namespace PepperDash.Core.JsonStandardObjects;
/// </summary> /// </summary>
public class ControlConfig public class ControlConfig
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string method { get; set; } public string method { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string controlPortDevKey { get; set; } public string controlPortDevKey { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int controlPortNumber { get; set; } public int controlPortNumber { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ComParamsConfig comParams { get; set; } public ComParamsConfig comParams { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public TcpSshPropertiesConfig tcpSshProperties { get; set; } public TcpSshPropertiesConfig tcpSshProperties { get; set; }
// convert properties for simpl // convert properties for simpl
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort simplControlPortNumber { get { return Convert.ToUInt16(controlPortNumber); } } public ushort simplControlPortNumber { get { return Convert.ToUInt16(controlPortNumber); } }
/// <summary> /// <summary>
@@ -207,32 +207,32 @@ namespace PepperDash.Core.JsonStandardObjects;
} }
} }
/// <summary> /// <summary>
/// Represents a PropertiesConfig /// Device properties class
/// </summary> /// </summary>
public class PropertiesConfig public class PropertiesConfig
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public int deviceId { get; set; } public int deviceId { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public bool enabled { get; set; } public bool enabled { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ControlConfig control { get; set; } public ControlConfig control { get; set; }
// convert properties for simpl // convert properties for simpl
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort simplDeviceId { get { return Convert.ToUInt16(deviceId); } } public ushort simplDeviceId { get { return Convert.ToUInt16(deviceId); } }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort simplEnabled { get { return (ushort)(enabled ? 1 : 0); } } public ushort simplEnabled { get { return (ushort)(enabled ? 1 : 0); } }
/// <summary> /// <summary>
@@ -249,8 +249,9 @@ namespace PepperDash.Core.JsonStandardObjects;
/// </summary> /// </summary>
public class RootObject public class RootObject
{ {
/// <summary> /// <summary>
/// The collection of devices /// The collection of devices
/// </summary> /// </summary>
public List<DeviceConfig> devices { get; set; } public List<DeviceConfig> devices { get; set; }
} }
}

View File

@@ -4,78 +4,78 @@ 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>
public class JsonToSimplConstants public class JsonToSimplConstants
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort BoolValueChange = 1; public const ushort BoolValueChange = 1;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort JsonIsValidBoolChange = 2; public const ushort JsonIsValidBoolChange = 2;
/// <summary> /// <summary>
/// Reports the if the device is 3-series compatible /// Reports the if the device is 3-series compatible
/// </summary> /// </summary>
public const ushort ProgramCompatibility3SeriesChange = 3; public const ushort ProgramCompatibility3SeriesChange = 3;
/// <summary> /// <summary>
/// Reports the if the device is 4-series compatible /// Reports the if the device is 4-series compatible
/// </summary> /// </summary>
public const ushort ProgramCompatibility4SeriesChange = 4; public const ushort ProgramCompatibility4SeriesChange = 4;
/// <summary> /// <summary>
/// Reports the device platform enum value /// Reports the device platform enum value
/// </summary> /// </summary>
public const ushort DevicePlatformValueChange = 5; public const ushort DevicePlatformValueChange = 5;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort UshortValueChange = 101; public const ushort UshortValueChange = 101;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort StringValueChange = 201; public const ushort StringValueChange = 201;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort FullPathToArrayChange = 202; public const ushort FullPathToArrayChange = 202;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort ActualFilePathChange = 203; public const ushort ActualFilePathChange = 203;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort FilenameResolvedChange = 204; public const ushort FilenameResolvedChange = 204;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort FilePathResolvedChange = 205; public const ushort FilePathResolvedChange = 205;
/// <summary> /// <summary>
/// Reports the root directory change /// Reports the root directory change
/// </summary> /// </summary>
public const ushort RootDirectoryChange = 206; public const ushort RootDirectoryChange = 206;
/// <summary> /// <summary>
/// Reports the room ID change /// Reports the room ID change
/// </summary> /// </summary>
public const ushort RoomIdChange = 207; public const ushort RoomIdChange = 207;
/// <summary> /// <summary>
/// Reports the room name change /// Reports the room name change
/// </summary> /// </summary>
public const ushort RoomNameChange = 208; public const ushort RoomNameChange = 208;
} }
/// <summary> /// <summary>
@@ -88,33 +88,33 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary> /// </summary>
public class SPlusValueWrapper public class SPlusValueWrapper
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public SPlusType ValueType { get; private set; } public SPlusType ValueType { get; private set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort Index { get; private set; } public ushort Index { get; private set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort BoolUShortValue { get; set; } public ushort BoolUShortValue { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string StringValue { get; set; } public string StringValue { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public SPlusValueWrapper() {} public SPlusValueWrapper() {}
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="type"></param> /// <param name="type"></param>
/// <param name="index"></param> /// <param name="index"></param>
public SPlusValueWrapper(SPlusType type, ushort index) public SPlusValueWrapper(SPlusType type, ushort index)
{ {
ValueType = type; ValueType = type;
@@ -122,21 +122,22 @@ namespace PepperDash.Core.JsonToSimpl;
} }
} }
/// <summary> /// <summary>
/// Enumeration of SPlusType values /// S+ types enum
/// </summary> /// </summary>
public enum SPlusType public enum SPlusType
{ {
/// <summary> /// <summary>
/// Digital /// Digital
/// </summary> /// </summary>
Digital, Digital,
/// <summary> /// <summary>
/// Analog /// Analog
/// </summary> /// </summary>
Analog, Analog,
/// <summary> /// <summary>
/// String /// String
/// </summary> /// </summary>
String String
} }
}

View File

@@ -7,11 +7,11 @@ 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>
public class J2SGlobal public class J2SGlobal
{ {
static List<JsonToSimplMaster> Masters = new List<JsonToSimplMaster>(); static List<JsonToSimplMaster> Masters = new List<JsonToSimplMaster>();
@@ -22,7 +22,7 @@ namespace PepperDash.Core.JsonToSimpl;
/// master, this will fail /// master, this will fail
/// </summary> /// </summary>
/// <param name="master">New master to add</param> /// <param name="master">New master to add</param>
/// ///
public static void AddMaster(JsonToSimplMaster master) public static void AddMaster(JsonToSimplMaster master)
{ {
if (master == null) if (master == null)
@@ -49,11 +49,12 @@ namespace PepperDash.Core.JsonToSimpl;
} }
} }
/// <summary> /// <summary>
/// GetMasterByFile method /// Gets a master by its key. Case-insensitive
/// </summary> /// </summary>
public static JsonToSimplMaster GetMasterByFile(string file) public static JsonToSimplMaster GetMasterByFile(string file)
{ {
return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase)); return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase));
} }
} }
}

View File

@@ -1,24 +1,22 @@
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 /// Used to interact with an array of values with the S+ modules
/// </summary> /// </summary>
public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string SearchPropertyName { get; set; } public string SearchPropertyName { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string SearchPropertyValue { get; set; } public string SearchPropertyValue { get; set; }
int ArrayIndex; int ArrayIndex;
@@ -78,9 +76,9 @@ namespace PepperDash.Core.JsonToSimpl;
PathSuffix == null ? "" : PathSuffix); PathSuffix == null ? "" : PathSuffix);
} }
/// <summary> /// <summary>
/// Process all values /// Process all values
/// </summary> /// </summary>
public override void ProcessAll() public override void ProcessAll()
{ {
if (FindInArray()) if (FindInArray())
@@ -130,7 +128,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)
@@ -160,4 +158,5 @@ namespace PepperDash.Core.JsonToSimpl;
return false; return false;
} }
} }
}

View File

@@ -1,43 +1,41 @@
extern alias NewtonsoftJson; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue; using Newtonsoft.Json.Linq;
namespace PepperDash.Core.JsonToSimpl; namespace PepperDash.Core.JsonToSimpl
{
/// <summary> /// <summary>
/// Base class for JSON objects /// Base class for JSON objects
/// </summary> /// </summary>
public abstract class JsonToSimplChildObjectBase : IKeyed public abstract class JsonToSimplChildObjectBase : IKeyed
{ {
/// <summary> /// <summary>
/// Notifies of bool change /// Notifies of bool change
/// </summary> /// </summary>
public event EventHandler<BoolChangeEventArgs> BoolChange; public event EventHandler<BoolChangeEventArgs> BoolChange;
/// <summary> /// <summary>
/// Notifies of ushort change /// Notifies of ushort change
/// </summary> /// </summary>
public event EventHandler<UshrtChangeEventArgs> UShortChange; public event EventHandler<UshrtChangeEventArgs> UShortChange;
/// <summary> /// <summary>
/// Notifies of string change /// Notifies of string change
/// </summary> /// </summary>
public event EventHandler<StringChangeEventArgs> StringChange; public event EventHandler<StringChangeEventArgs> StringChange;
/// <summary> /// <summary>
/// Delegate to get all values /// Delegate to get all values
/// </summary> /// </summary>
public SPlusValuesDelegate GetAllValuesDelegate { get; set; } public SPlusValuesDelegate GetAllValuesDelegate { get; set; }
/// <summary> /// <summary>
/// Gets or sets the SetAllPathsDelegate /// Use a callback to reduce task switch/threading
/// </summary> /// </summary>
public SPlusValuesDelegate SetAllPathsDelegate { get; set; } public SPlusValuesDelegate SetAllPathsDelegate { get; set; }
/// <summary> /// <summary>
/// Unique identifier for instance /// Unique identifier for instance
/// </summary> /// </summary>
public string Key { get; protected set; } public string Key { get; protected set; }
/// <summary> /// <summary>
@@ -51,33 +49,33 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary> /// </summary>
public string PathSuffix { get; protected set; } public string PathSuffix { get; protected set; }
/// <summary> /// <summary>
/// Indicates if the instance is linked to an object /// Indicates if the instance is linked to an object
/// </summary> /// </summary>
public bool LinkedToObject { get; protected set; } public bool LinkedToObject { get; protected set; }
/// <summary> /// <summary>
/// Reference to Master instance /// Reference to Master instance
/// </summary> /// </summary>
protected JsonToSimplMaster Master; protected JsonToSimplMaster Master;
/// <summary> /// <summary>
/// Paths to boolean values in JSON structure /// Paths to boolean values in JSON structure
/// </summary> /// </summary>
protected Dictionary<ushort, string> BoolPaths = new Dictionary<ushort, string>(); protected Dictionary<ushort, string> BoolPaths = new Dictionary<ushort, string>();
/// <summary> /// <summary>
/// Paths to numeric values in JSON structure /// Paths to numeric values in JSON structure
/// </summary> /// </summary>
protected Dictionary<ushort, string> UshortPaths = new Dictionary<ushort, string>(); protected Dictionary<ushort, string> UshortPaths = new Dictionary<ushort, string>();
/// <summary> /// <summary>
/// Paths to string values in JSON structure /// Paths to string values in JSON structure
/// </summary> /// </summary>
protected Dictionary<ushort, string> StringPaths = new Dictionary<ushort, string>(); protected Dictionary<ushort, string> StringPaths = new Dictionary<ushort, string>();
/// <summary> /// <summary>
/// Call this before doing anything else /// Call this before doing anything else
/// </summary> /// </summary>
/// <param name="masterUniqueId"></param> /// <param name="masterUniqueId"></param>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="pathPrefix"></param> /// <param name="pathPrefix"></param>
/// <param name="pathSuffix"></param> /// <param name="pathSuffix"></param>
@@ -94,10 +92,10 @@ namespace PepperDash.Core.JsonToSimpl;
Debug.Console(1, "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>
public void SetPathPrefix(string pathPrefix) public void SetPathPrefix(string pathPrefix)
{ {
PathPrefix = pathPrefix; PathPrefix = pathPrefix;
@@ -112,9 +110,9 @@ namespace PepperDash.Core.JsonToSimpl;
BoolPaths[index] = path; BoolPaths[index] = path;
} }
/// <summary> /// <summary>
/// SetUshortPath method /// Set the JPath for a ushort out index.
/// </summary> /// </summary>
public void SetUshortPath(ushort index, string path) public void SetUshortPath(ushort index, string path)
{ {
Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path); Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
@@ -122,9 +120,9 @@ namespace PepperDash.Core.JsonToSimpl;
UshortPaths[index] = path; UshortPaths[index] = path;
} }
/// <summary> /// <summary>
/// SetStringPath method /// Set the JPath for a string output index.
/// </summary> /// </summary>
public void SetStringPath(ushort index, string path) public void SetStringPath(ushort index, string path)
{ {
Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path); Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
@@ -132,10 +130,10 @@ namespace PepperDash.Core.JsonToSimpl;
StringPaths[index] = path; StringPaths[index] = path;
} }
/// <summary> /// <summary>
/// ProcessAll method /// Evalutates all outputs with defined paths. called by S+ when paths are ready to process
/// </summary> /// and by Master when file is read.
/// <inheritdoc /> /// </summary>
public virtual void ProcessAll() public virtual void ProcessAll()
{ {
if (!LinkedToObject) if (!LinkedToObject)
@@ -172,18 +170,18 @@ namespace PepperDash.Core.JsonToSimpl;
} }
// Processes the path to a ushort, converting to ushort if able, twos complement if necessary, firing off UshrtChange event // Processes the path to a ushort, converting to ushort if able, twos complement if necessary, firing off UshrtChange event
void ProcessUshortPath(ushort index) { void ProcessUshortPath(ushort index) {
string response; string response;
if (Process(UshortPaths[index], out response)) { if (Process(UshortPaths[index], out response)) {
ushort val; ushort val;
try { val = Convert.ToInt32(response) < 0 ? (ushort)(Convert.ToInt16(response) + 65536) : Convert.ToUInt16(response); } try { val = Convert.ToInt32(response) < 0 ? (ushort)(Convert.ToInt16(response) + 65536) : Convert.ToUInt16(response); }
catch { val = 0; } catch { val = 0; }
OnUShortChange(val, index, JsonToSimplConstants.UshortValueChange); OnUShortChange(val, index, JsonToSimplConstants.UshortValueChange);
}
else { }
// OnUShortChange(0, index, JsonToSimplConstants.UshortValueChange);
} }
else { }
// OnUShortChange(0, index, JsonToSimplConstants.UshortValueChange);
}
// Processes the path to a string property and fires of a StringChange event. // Processes the path to a string property and fires of a StringChange event.
void ProcessStringPath(ushort index) void ProcessStringPath(ushort index)
@@ -232,7 +230,7 @@ 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>();
Debug.Console(1, " ='{0}'", response); Debug.Console(1, " ='{0}'", response);
return true; return true;
} }
@@ -274,54 +272,54 @@ namespace PepperDash.Core.JsonToSimpl;
GetAllValuesDelegate(); GetAllValuesDelegate();
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="theValue"></param> /// <param name="theValue"></param>
public void USetBoolValue(ushort key, ushort theValue) public void USetBoolValue(ushort key, ushort theValue)
{ {
SetBoolValue(key, theValue == 1); SetBoolValue(key, theValue == 1);
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="theValue"></param> /// <param name="theValue"></param>
public void SetBoolValue(ushort key, bool theValue) public void SetBoolValue(ushort key, bool theValue)
{ {
if (BoolPaths.ContainsKey(key)) if (BoolPaths.ContainsKey(key))
SetValueOnMaster(BoolPaths[key], new JValue(theValue)); SetValueOnMaster(BoolPaths[key], new JValue(theValue));
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="theValue"></param> /// <param name="theValue"></param>
public void SetUShortValue(ushort key, ushort theValue) public void SetUShortValue(ushort key, ushort theValue)
{ {
if (UshortPaths.ContainsKey(key)) if (UshortPaths.ContainsKey(key))
SetValueOnMaster(UshortPaths[key], new JValue(theValue)); SetValueOnMaster(UshortPaths[key], new JValue(theValue));
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="theValue"></param> /// <param name="theValue"></param>
public void SetStringValue(ushort key, string theValue) public void SetStringValue(ushort key, string theValue)
{ {
if (StringPaths.ContainsKey(key)) if (StringPaths.ContainsKey(key))
SetValueOnMaster(StringPaths[key], new JValue(theValue)); SetValueOnMaster(StringPaths[key], new JValue(theValue));
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="keyPath"></param> /// <param name="keyPath"></param>
/// <param name="valueToSave"></param> /// <param name="valueToSave"></param>
public void SetValueOnMaster(string keyPath, JValue valueToSave) public void SetValueOnMaster(string keyPath, JValue valueToSave)
{ {
var path = GetFullPath(keyPath); var path = GetFullPath(keyPath);
@@ -351,12 +349,12 @@ namespace PepperDash.Core.JsonToSimpl;
// Helpers for events // Helpers for events
//****************************************************************************************** //******************************************************************************************
/// <summary> /// <summary>
/// Event helper /// Event helper
/// </summary> /// </summary>
/// <param name="state"></param> /// <param name="state"></param>
/// <param name="index"></param> /// <param name="index"></param>
/// <param name="type"></param> /// <param name="type"></param>
protected void OnBoolChange(bool state, ushort index, ushort type) protected void OnBoolChange(bool state, ushort index, ushort type)
{ {
var handler = BoolChange; var handler = BoolChange;
@@ -369,12 +367,12 @@ namespace PepperDash.Core.JsonToSimpl;
} }
//****************************************************************************************** //******************************************************************************************
/// <summary> /// <summary>
/// Event helper /// Event helper
/// </summary> /// </summary>
/// <param name="state"></param> /// <param name="state"></param>
/// <param name="index"></param> /// <param name="index"></param>
/// <param name="type"></param> /// <param name="type"></param>
protected void OnUShortChange(ushort state, ushort index, ushort type) protected void OnUShortChange(ushort state, ushort index, ushort type)
{ {
var handler = UShortChange; var handler = UShortChange;
@@ -386,12 +384,12 @@ namespace PepperDash.Core.JsonToSimpl;
} }
} }
/// <summary> /// <summary>
/// Event helper /// Event helper
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
/// <param name="index"></param> /// <param name="index"></param>
/// <param name="type"></param> /// <param name="type"></param>
protected void OnStringChange(string value, ushort index, ushort type) protected void OnStringChange(string value, ushort index, ushort type)
{ {
var handler = StringChange; var handler = StringChange;
@@ -402,4 +400,5 @@ namespace PepperDash.Core.JsonToSimpl;
StringChange(this, args); StringChange(this, args);
} }
} }
} }
}

View File

@@ -1,290 +1,287 @@
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 Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting; using Newtonsoft.Json.Linq;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
namespace PepperDash.Core.JsonToSimpl; namespace PepperDash.Core.JsonToSimpl
/// <summary>
/// Represents a JSON file that can be read and written to
/// </summary>
public class JsonToSimplFileMaster : JsonToSimplMaster
{ {
/// <summary> /// <summary>
/// Sets the filepath as well as registers this with the Global.Masters list /// Represents a JSON file that can be read and written to
/// </summary> /// </summary>
public string Filepath { get; private set; } public class JsonToSimplFileMaster : JsonToSimplMaster
/// <summary>
/// Filepath to the actual file that will be read (Portal or local)
/// </summary>
public string ActualFilePath { get; private set; }
/// <summary>
///
/// </summary>
public string Filename { get; private set; }
/// <summary>
///
/// </summary>
public string FilePathName { get; private set; }
/*****************************************************************************************/
/** Privates **/
// The JSON file in JObject form
// For gathering the incoming data
object StringBuilderLock = new object();
// To prevent multiple same-file access
static object FileLock = new object();
/*****************************************************************************************/
/// <summary>
/// SIMPL+ default constructor.
/// </summary>
public JsonToSimplFileMaster()
{ {
} /// <summary>
/// Sets the filepath as well as registers this with the Global.Masters list
/// </summary>
public string Filepath { get; private set; }
/// <summary> /// <summary>
/// Read, evaluate and udpate status /// Filepath to the actual file that will be read (Portal or local)
/// </summary> /// </summary>
public void EvaluateFile(string filepath) public string ActualFilePath { get; private set; }
{
try /// <summary>
///
/// </summary>
public string Filename { get; private set; }
/// <summary>
///
/// </summary>
public string FilePathName { get; private set; }
/*****************************************************************************************/
/** Privates **/
// The JSON file in JObject form
// For gathering the incoming data
object StringBuilderLock = new object();
// To prevent multiple same-file access
static object FileLock = new object();
/*****************************************************************************************/
/// <summary>
/// SIMPL+ default constructor.
/// </summary>
public JsonToSimplFileMaster()
{ {
OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange); }
var dirSeparator = Path.DirectorySeparatorChar; /// <summary>
var dirSeparatorAlt = Path.AltDirectorySeparatorChar; /// Read, evaluate and udpate status
/// </summary>
var series = CrestronEnvironment.ProgramCompatibility; public void EvaluateFile(string filepath)
{
var is3Series = (eCrestronSeries.Series3 == (series & eCrestronSeries.Series3)); try
OnBoolChange(is3Series, 0,
JsonToSimplConstants.ProgramCompatibility3SeriesChange);
var is4Series = (eCrestronSeries.Series4 == (series & eCrestronSeries.Series4));
OnBoolChange(is4Series, 0,
JsonToSimplConstants.ProgramCompatibility4SeriesChange);
var isServer = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server;
OnBoolChange(isServer, 0,
JsonToSimplConstants.DevicePlatformValueChange);
// get the roomID
var roomId = Crestron.SimplSharp.InitialParametersClass.RoomId;
if (!string.IsNullOrEmpty(roomId))
{ {
OnStringChange(roomId, 0, JsonToSimplConstants.RoomIdChange); OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange);
}
// get the roomName var dirSeparator = Path.DirectorySeparatorChar;
var roomName = Crestron.SimplSharp.InitialParametersClass.RoomName; var dirSeparatorAlt = Path.AltDirectorySeparatorChar;
if (!string.IsNullOrEmpty(roomName))
{
OnStringChange(roomName, 0, JsonToSimplConstants.RoomNameChange);
}
var rootDirectory = Directory.GetApplicationRootDirectory(); var series = CrestronEnvironment.ProgramCompatibility;
OnStringChange(rootDirectory, 0, JsonToSimplConstants.RootDirectoryChange);
var is3Series = (eCrestronSeries.Series3 == (series & eCrestronSeries.Series3));
var splusPath = string.Empty; OnBoolChange(is3Series, 0,
if (Regex.IsMatch(filepath, @"user", RegexOptions.IgnoreCase)) JsonToSimplConstants.ProgramCompatibility3SeriesChange);
{
if (is4Series) var is4Series = (eCrestronSeries.Series4 == (series & eCrestronSeries.Series4));
splusPath = Regex.Replace(filepath, "user", "user", RegexOptions.IgnoreCase); OnBoolChange(is4Series, 0,
else if (isServer) JsonToSimplConstants.ProgramCompatibility4SeriesChange);
splusPath = Regex.Replace(filepath, "user", "User", RegexOptions.IgnoreCase);
var isServer = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server;
OnBoolChange(isServer, 0,
JsonToSimplConstants.DevicePlatformValueChange);
// get the roomID
var roomId = Crestron.SimplSharp.InitialParametersClass.RoomId;
if (!string.IsNullOrEmpty(roomId))
{
OnStringChange(roomId, 0, JsonToSimplConstants.RoomIdChange);
}
// get the roomName
var roomName = Crestron.SimplSharp.InitialParametersClass.RoomName;
if (!string.IsNullOrEmpty(roomName))
{
OnStringChange(roomName, 0, JsonToSimplConstants.RoomNameChange);
}
var rootDirectory = Directory.GetApplicationRootDirectory();
OnStringChange(rootDirectory, 0, JsonToSimplConstants.RootDirectoryChange);
var splusPath = string.Empty;
if (Regex.IsMatch(filepath, @"user", RegexOptions.IgnoreCase))
{
if (is4Series)
splusPath = Regex.Replace(filepath, "user", "user", RegexOptions.IgnoreCase);
else if (isServer)
splusPath = Regex.Replace(filepath, "user", "User", RegexOptions.IgnoreCase);
else
splusPath = filepath;
}
filepath = splusPath.Replace(dirSeparatorAlt, dirSeparator);
Filepath = string.Format("{1}{0}{2}", dirSeparator, rootDirectory,
filepath.TrimStart(dirSeparator, dirSeparatorAlt));
OnStringChange(string.Format("Attempting to evaluate {0}", Filepath), 0, JsonToSimplConstants.StringValueChange);
if (string.IsNullOrEmpty(Filepath))
{
OnStringChange(string.Format("Cannot evaluate file. JSON file path not set"), 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set");
return;
}
// get file directory and name to search
var fileDirectory = Path.GetDirectoryName(Filepath);
var fileName = Path.GetFileName(Filepath);
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName);
if (Directory.Exists(fileDirectory))
{
// get the directory info
var directoryInfo = new DirectoryInfo(fileDirectory);
// get the file to be read
var actualFile = directoryInfo.GetFiles(fileName).FirstOrDefault();
if (actualFile == null)
{
var msg = string.Format("JSON file not found: {0}", Filepath);
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(msg);
ErrorLog.Error(msg);
return;
}
// \xSE\xR\PDT000-Template_Main_Config-Combined_DSP_v00.02.json
// \USER\PDT000-Template_Main_Config-Combined_DSP_v00.02.json
ActualFilePath = actualFile.FullName;
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "Actual JSON file is {0}", ActualFilePath);
Filename = actualFile.Name;
OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange);
OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "JSON Filename is {0}", Filename);
FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator);
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "JSON File Path is {0}", FilePathName);
var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
JsonObject = JObject.Parse(json);
foreach (var child in Children)
child.ProcessAll();
OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange);
}
else else
splusPath = filepath; {
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "'{0}' not found", fileDirectory);
}
}
catch (Exception e)
{
var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message);
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(msg);
ErrorLog.Error(msg);
var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(stackTrace);
ErrorLog.Error(stackTrace);
}
}
/// <summary>
/// Sets the debug level
/// </summary>
/// <param name="level"></param>
public void setDebugLevel(uint level)
{
Debug.SetDebugLevel(level);
}
/// <summary>
/// Saves the values to the file
/// </summary>
public override void Save()
{
// this code is duplicated in the other masters!!!!!!!!!!!!!
UnsavedValues = new Dictionary<string, JValue>();
// Make each child update their values into master object
foreach (var child in Children)
{
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
child.UpdateInputsForMaster();
} }
filepath = splusPath.Replace(dirSeparatorAlt, dirSeparator); if (UnsavedValues == null || UnsavedValues.Count == 0)
Filepath = string.Format("{1}{0}{2}", dirSeparator, rootDirectory,
filepath.TrimStart(dirSeparator, dirSeparatorAlt));
OnStringChange(string.Format("Attempting to evaluate {0}", Filepath), 0, JsonToSimplConstants.StringValueChange);
if (string.IsNullOrEmpty(Filepath))
{ {
OnStringChange(string.Format("Cannot evaluate file. JSON file path not set"), 0, JsonToSimplConstants.StringValueChange); Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set");
return; return;
} }
lock (FileLock)
// get file directory and name to search
var fileDirectory = Path.GetDirectoryName(Filepath);
var fileName = Path.GetFileName(Filepath);
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName);
if (Directory.Exists(fileDirectory))
{ {
// get the directory info Debug.Console(1, "Saving");
var directoryInfo = new DirectoryInfo(fileDirectory); foreach (var path in UnsavedValues.Keys)
// get the file to be read
var actualFile = directoryInfo.GetFiles(fileName).FirstOrDefault();
if (actualFile == null)
{ {
var msg = string.Format("JSON file not found: {0}", Filepath); var tokenToReplace = JsonObject.SelectToken(path);
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange); if (tokenToReplace != null)
CrestronConsole.PrintLine(msg); {// It's found
ErrorLog.Error(msg); tokenToReplace.Replace(UnsavedValues[path]);
return; Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path);
}
else // No token. Let's make one
{
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
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))
// \xSE\xR\PDT000-Template_Main_Config-Combined_DSP_v00.02.json
// \USER\PDT000-Template_Main_Config-Combined_DSP_v00.02.json
ActualFilePath = actualFile.FullName;
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "Actual JSON file is {0}", ActualFilePath);
Filename = actualFile.Name;
OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange);
OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "JSON Filename is {0}", Filename);
FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator);
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "JSON File Path is {0}", FilePathName);
var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
JsonObject = JObject.Parse(json);
foreach (var child in Children)
child.ProcessAll();
OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange);
}
else
{
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "'{0}' not found", fileDirectory);
}
}
catch (Exception e)
{
var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message);
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(msg);
ErrorLog.Error(msg);
var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(stackTrace);
ErrorLog.Error(stackTrace);
}
}
/// <summary>
/// Sets the debug level
/// </summary>
/// <param name="level"></param>
public void setDebugLevel(uint level)
{
Debug.SetDebugLevel(level);
}
/// <summary>
/// Saves the values to the file
/// </summary>
public override void Save()
{
// this code is duplicated in the other masters!!!!!!!!!!!!!
UnsavedValues = new Dictionary<string, JValue>();
// Make each child update their values into master object
foreach (var child in Children)
{
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
child.UpdateInputsForMaster();
}
if (UnsavedValues == null || UnsavedValues.Count == 0)
{
Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
return;
}
lock (FileLock)
{
Debug.Console(1, "Saving");
foreach (var path in UnsavedValues.Keys)
{
var tokenToReplace = JsonObject.SelectToken(path);
if (tokenToReplace != null)
{// It's found
tokenToReplace.Replace(UnsavedValues[path]);
Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path);
}
else // No token. Let's make one
{ {
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net try
Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path); {
sw.Write(JsonObject.ToString());
// JContainer jpart = JsonObject; sw.Flush();
// // walk down the path and find where it goes }
//#warning Does not handle arrays. catch (Exception e)
// foreach (var part in path.Split('.')) {
// { string err = string.Format("Error writing JSON file:\r{0}", e);
Debug.Console(0, err);
// var openPos = part.IndexOf('['); ErrorLog.Warn(err);
// if (openPos > -1) return;
// { }
// 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))
{
try
{
sw.Write(JsonObject.ToString());
sw.Flush();
}
catch (Exception e)
{
string err = string.Format("Error writing JSON file:\r{0}", e);
Debug.Console(0, err);
ErrorLog.Warn(err);
return;
} }
} }
} }

View File

@@ -1,17 +1,18 @@
namespace PepperDash.Core.JsonToSimpl; namespace PepperDash.Core.JsonToSimpl
{
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public class JsonToSimplFixedPathObject : JsonToSimplChildObjectBase public class JsonToSimplFixedPathObject : JsonToSimplChildObjectBase
{ {
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
public JsonToSimplFixedPathObject() public JsonToSimplFixedPathObject()
{ {
this.LinkedToObject = true; this.LinkedToObject = true;
} }
} }
}

View File

@@ -1,40 +1,37 @@
extern alias NewtonsoftJson; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp; using Crestron.SimplSharp;
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject; using Newtonsoft.Json.Linq;
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
namespace PepperDash.Core.JsonToSimpl; namespace PepperDash.Core.JsonToSimpl
/// <summary>
/// Generic Master
/// </summary>
public class JsonToSimplGenericMaster : JsonToSimplMaster
{ {
/// <summary>
/// Generic Master
/// </summary>
public class JsonToSimplGenericMaster : JsonToSimplMaster
{
/*****************************************************************************************/ /*****************************************************************************************/
/** Privates **/ /** Privates **/
// The JSON file in JObject form // The JSON file in JObject form
// For gathering the incoming data // For gathering the incoming data
object StringBuilderLock = new object(); object StringBuilderLock = new object();
// To prevent multiple same-file access // To prevent multiple same-file access
static object WriteLock = new object(); static object WriteLock = new object();
/// <summary> /// <summary>
/// Callback action for saving /// Callback action for saving
/// </summary> /// </summary>
public Action<string> SaveCallback { get; set; } public Action<string> SaveCallback { get; set; }
/*****************************************************************************************/ /*****************************************************************************************/
/// <summary> /// <summary>
/// SIMPL+ default constructor. /// SIMPL+ default constructor.
/// </summary> /// </summary>
public JsonToSimplGenericMaster() public JsonToSimplGenericMaster()
{ {
} }
/// <summary> /// <summary>
@@ -63,9 +60,6 @@ namespace PepperDash.Core.JsonToSimpl;
/// Loads JSON into JsonObject, but does not trigger evaluation by children /// Loads JSON into JsonObject, but does not trigger evaluation by children
/// </summary> /// </summary>
/// <param name="json"></param> /// <param name="json"></param>
/// <summary>
/// SetJsonWithoutEvaluating method
/// </summary>
public void SetJsonWithoutEvaluating(string json) public void SetJsonWithoutEvaluating(string json)
{ {
try try
@@ -78,14 +72,13 @@ namespace PepperDash.Core.JsonToSimpl;
} }
} }
/// <summary> /// <summary>
/// Save method ///
/// </summary> /// </summary>
/// <inheritdoc />
public override void Save() public override void Save()
{ {
// this code is duplicated in the other masters!!!!!!!!!!!!! // this code is duplicated in the other masters!!!!!!!!!!!!!
UnsavedValues = new Dictionary<string, JValue>(); UnsavedValues = new Dictionary<string, JValue>();
// 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)
{ {
@@ -121,4 +114,5 @@ namespace PepperDash.Core.JsonToSimpl;
else else
Debug.Console(0, this, "WARNING: No save callback defined."); Debug.Console(0, this, "WARNING: No save callback defined.");
} }
} }
}

View File

@@ -1,39 +1,34 @@
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;
namespace PepperDash.Core.JsonToSimpl; namespace PepperDash.Core.JsonToSimpl
{
/// <summary> /// <summary>
/// Abstract base class for JsonToSimpl interactions /// Abstract base class for JsonToSimpl interactions
/// </summary> /// </summary>
public abstract class JsonToSimplMaster : IKeyed public abstract class JsonToSimplMaster : IKeyed
{ {
/// <summary> /// <summary>
/// Notifies of bool change /// Notifies of bool change
/// </summary> /// </summary>
public event EventHandler<BoolChangeEventArgs> BoolChange; public event EventHandler<BoolChangeEventArgs> BoolChange;
/// <summary> /// <summary>
/// Notifies of ushort change /// Notifies of ushort change
/// </summary> /// </summary>
public event EventHandler<UshrtChangeEventArgs> UshrtChange; public event EventHandler<UshrtChangeEventArgs> UshrtChange;
/// <summary> /// <summary>
/// Notifies of string change /// Notifies of string change
/// </summary> /// </summary>
public event EventHandler<StringChangeEventArgs> StringChange; public event EventHandler<StringChangeEventArgs> StringChange;
/// <summary> /// <summary>
/// A collection of associated child modules /// A collection of associated child modules
/// </summary> /// </summary>
protected List<JsonToSimplChildObjectBase> Children = new List<JsonToSimplChildObjectBase>(); protected List<JsonToSimplChildObjectBase> Children = new List<JsonToSimplChildObjectBase>();
/*****************************************************************************************/ /*****************************************************************************************/
@@ -43,9 +38,9 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary> /// </summary>
public string Key { get { return UniqueID; } } public string Key { get { return UniqueID; } }
/// <summary> /// <summary>
/// A unique ID /// A unique ID
/// </summary> /// </summary>
public string UniqueID { get; protected set; } public string UniqueID { get; protected set; }
/// <summary> /// <summary>
@@ -58,9 +53,10 @@ namespace PepperDash.Core.JsonToSimpl;
} }
string _DebugName = ""; string _DebugName = "";
/// <summary> /// <summary>
/// Gets or sets the PathPrefix /// This will be prepended to all paths to allow path swapping or for more organized
/// </summary> /// sub-paths
/// </summary>
public string PathPrefix { get; set; } public string PathPrefix { get; set; }
/// <summary> /// <summary>
@@ -87,9 +83,9 @@ namespace PepperDash.Core.JsonToSimpl;
} }
} }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public JObject JsonObject { get; protected set; } public JObject JsonObject { get; protected set; }
/*****************************************************************************************/ /*****************************************************************************************/
@@ -124,9 +120,6 @@ namespace PepperDash.Core.JsonToSimpl;
/// Adds a child "module" to this master /// Adds a child "module" to this master
/// </summary> /// </summary>
/// <param name="child"></param> /// <param name="child"></param>
/// <summary>
/// AddChild method
/// </summary>
public void AddChild(JsonToSimplChildObjectBase child) public void AddChild(JsonToSimplChildObjectBase child)
{ {
if (!Children.Contains(child)) if (!Children.Contains(child))
@@ -135,9 +128,9 @@ namespace PepperDash.Core.JsonToSimpl;
} }
} }
/// <summary> /// <summary>
/// AddUnsavedValue method /// Called from the child to add changed or new values for saving
/// </summary> /// </summary>
public void AddUnsavedValue(string path, JValue value) public void AddUnsavedValue(string path, JValue value)
{ {
if (UnsavedValues.ContainsKey(path)) if (UnsavedValues.ContainsKey(path))
@@ -149,9 +142,9 @@ namespace PepperDash.Core.JsonToSimpl;
//Debug.Console(0, "Master[{0}] Unsaved size={1}", UniqueID, UnsavedValues.Count); //Debug.Console(0, "Master[{0}] Unsaved size={1}", UniqueID, UnsavedValues.Count);
} }
/// <summary> /// <summary>
/// Saves the file /// Saves the file
/// </summary> /// </summary>
public abstract void Save(); public abstract void Save();
@@ -160,14 +153,18 @@ namespace PepperDash.Core.JsonToSimpl;
/// </summary> /// </summary>
public static class JsonFixes public static class JsonFixes
{ {
/// <summary> /// <summary>
/// Deserializes a string into a JObject /// Deserializes a string into a JObject
/// </summary> /// </summary>
/// <param name="json"></param> /// <param name="json"></param>
/// <returns></returns> /// <returns></returns>
public static JObject ParseObject(string json) public static JObject ParseObject(string json)
{ {
using (var reader = new JsonTextReader(new System.IO.StringReader(json))) #if NET6_0
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
#else
using (var reader = new JsonTextReader(new Crestron.SimplSharp.CrestronIO.StringReader(json)))
#endif
{ {
var startDepth = reader.Depth; var startDepth = reader.Depth;
var obj = JObject.Load(reader); var obj = JObject.Load(reader);
@@ -177,15 +174,18 @@ namespace PepperDash.Core.JsonToSimpl;
} }
} }
/// <summary> /// <summary>
/// Deserializes a string into a JArray /// Deserializes a string into a JArray
/// </summary> /// </summary>
/// <param name="json"></param> /// <param name="json"></param>
/// <returns></returns> /// <returns></returns>
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);
@@ -228,12 +228,12 @@ namespace PepperDash.Core.JsonToSimpl;
} }
} }
/// <summary> /// <summary>
/// Helper event /// Helper event
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
/// <param name="index"></param> /// <param name="index"></param>
/// <param name="type"></param> /// <param name="type"></param>
protected void OnStringChange(string value, ushort index, ushort type) protected void OnStringChange(string value, ushort index, ushort type)
{ {
if (StringChange != null) if (StringChange != null)
@@ -244,3 +244,4 @@ namespace PepperDash.Core.JsonToSimpl;
} }
} }
} }
}

View File

@@ -1,30 +1,27 @@
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;
namespace PepperDash.Core.JsonToSimpl; namespace PepperDash.Core.JsonToSimpl
/// <summary>
/// Portal File Master
/// </summary>
public class JsonToSimplPortalFileMaster : JsonToSimplMaster
{ {
/// <summary>
/// Portal File Master
/// </summary>
public class JsonToSimplPortalFileMaster : JsonToSimplMaster
{
/// <summary> /// <summary>
/// Sets the filepath as well as registers this with the Global.Masters list /// Sets the filepath as well as registers this with the Global.Masters list
/// </summary> /// </summary>
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) /// File path of the actual file being read (Portal or local)
/// </summary> /// </summary>
public string ActualFilePath { get; private set; } public string ActualFilePath { get; private set; }
/*****************************************************************************************/ /*****************************************************************************************/
/** Privates **/ /** Privates **/
@@ -36,10 +33,10 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
/*****************************************************************************************/ /*****************************************************************************************/
/// <summary> /// <summary>
/// SIMPL+ default constructor. /// SIMPL+ default constructor.
/// </summary> /// </summary>
public JsonToSimplPortalFileMaster() public JsonToSimplPortalFileMaster()
{ {
} }
/// <summary> /// <summary>
@@ -67,7 +64,7 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
if (actualLocalFile != null) if (actualLocalFile != null)
{ {
ActualFilePath = actualLocalFile.FullName; ActualFilePath = actualLocalFile.FullName;
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
} }
// If the local file does not exist, then read the portal file xyz.json // If the local file does not exist, then read the portal file xyz.json
// and create the local. // and create the local.
@@ -81,7 +78,7 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
// got the portal file, hand off to the merge / save method // got the portal file, hand off to the merge / save method
PortalConfigReader.ReadAndMergeFileIfNecessary(actualPortalFile.FullName, newLocalPath); PortalConfigReader.ReadAndMergeFileIfNecessary(actualPortalFile.FullName, newLocalPath);
ActualFilePath = newLocalPath; ActualFilePath = newLocalPath;
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange); OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
} }
else else
{ {
@@ -131,9 +128,6 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
/// ///
/// </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);
@@ -194,3 +188,4 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
} }
} }
} }
}

View File

@@ -7,30 +7,31 @@ 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
public class CrestronEnricher : ILogEventEnricher
{ {
static readonly string _appName; public class CrestronEnricher : ILogEventEnricher
static CrestronEnricher()
{ {
switch (CrestronEnvironment.DevicePlatform) static readonly string _appName;
static CrestronEnricher()
{ {
case eDevicePlatform.Appliance: switch (CrestronEnvironment.DevicePlatform)
_appName = $"App {InitialParametersClass.ApplicationNumber}"; {
break; case eDevicePlatform.Appliance:
case eDevicePlatform.Server: _appName = $"App {InitialParametersClass.ApplicationNumber}";
_appName = $"{InitialParametersClass.RoomId}"; break;
break; case eDevicePlatform.Server:
_appName = $"{InitialParametersClass.RoomId}";
break;
}
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var property = propertyFactory.CreateProperty("App", _appName);
logEvent.AddOrUpdateProperty(property);
} }
} }
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var property = propertyFactory.CreateProperty("App", _appName);
logEvent.AddOrUpdateProperty(property);
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -9,45 +9,47 @@ using System.IO;
using System.Text; using System.Text;
namespace PepperDash.Core; namespace PepperDash.Core
public class DebugConsoleSink : ILogEventSink
{ {
private readonly ITextFormatter _textFormatter; public class DebugConsoleSink : ILogEventSink
public void Emit(LogEvent logEvent)
{ {
if (!Debug.IsRunningOnAppliance) return; private readonly ITextFormatter _textFormatter;
/*string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}"; public void Emit(LogEvent logEvent)
if(logEvent.Properties.TryGetValue("Key",out var value) && value is ScalarValue sv && sv.Value is string rawValue)
{ {
message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue,3}]: {logEvent.RenderMessage()}"; if (!Debug.IsRunningOnAppliance) return;
}*/
var buffer = new StringWriter(new StringBuilder(256)); /*string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}";
_textFormatter.Format(logEvent, buffer); if(logEvent.Properties.TryGetValue("Key",out var value) && value is ScalarValue sv && sv.Value is string rawValue)
{
message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue,3}]: {logEvent.RenderMessage()}";
}*/
var message = buffer.ToString(); var buffer = new StringWriter(new StringBuilder(256));
_textFormatter.Format(logEvent, buffer);
var message = buffer.ToString();
CrestronConsole.PrintLine(message);
}
public DebugConsoleSink(ITextFormatter formatProvider )
{
_textFormatter = formatProvider ?? new JsonFormatter();
}
CrestronConsole.PrintLine(message);
} }
public DebugConsoleSink(ITextFormatter formatProvider ) public static class DebugConsoleSinkExtensions
{ {
_textFormatter = formatProvider ?? new JsonFormatter(); public static LoggerConfiguration DebugConsoleSink(
this LoggerSinkConfiguration loggerConfiguration,
ITextFormatter formatProvider = null)
{
return loggerConfiguration.Sink(new DebugConsoleSink(formatProvider));
}
} }
} }
public static class DebugConsoleSinkExtensions
{
public static LoggerConfiguration DebugConsoleSink(
this LoggerSinkConfiguration loggerConfiguration,
ITextFormatter formatProvider = null)
{
return loggerConfiguration.Sink(new DebugConsoleSink(formatProvider));
}
}

View File

@@ -1,283 +1,281 @@
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 Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting; using Newtonsoft.Json;
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
namespace PepperDash.Core; namespace PepperDash.Core
/// <summary>
/// Represents a debugging context
/// </summary>
public class DebugContext
{ {
/// <summary> /// <summary>
/// Describes the folder location where a given program stores it's debug level memory. By default, the /// Represents a debugging context
/// file written will be named appNdebug where N is 1-10.
/// </summary> /// </summary>
public string Key { get; private set; } public class DebugContext
///// <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>
public static DebugContext GetDebugContext(string key)
{ {
var context = Contexts.FirstOrDefault(c => c.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); /// <summary>
if (context == null) /// 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>
public static DebugContext GetDebugContext(string key)
{ {
context = new DebugContext(key); var context = Contexts.FirstOrDefault(c => c.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
Contexts.Add(context); if (context == null)
}
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(); context = new DebugContext(key);
SaveTimer = null; Contexts.Add(context);
} }
Console(0, "Saving debug settings"); return context;
SaveMemory();
} }
}
/// <summary> /// <summary>
/// Callback for console command /// Do not use. For S+ access.
/// </summary> /// </summary>
/// <param name="levelString"></param> public DebugContext() { }
public void SetDebugFromConsole(string levelString)
{ DebugContext(string key)
try
{ {
if (string.IsNullOrEmpty(levelString.Trim())) Key = key;
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
{ {
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", SaveData.Level); // Add command to console
return; CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
"appdebug:P [0-2]: Sets the application's console debug message level",
ConsoleAccessLevelEnum.AccessOperator);
} }
SetDebugLevel(Convert.ToInt32(levelString)); CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
LoadMemory();
} }
catch
/// <summary>
/// Used to save memory when shutting down
/// </summary>
/// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{ {
CrestronConsole.PrintLine("Usage: appdebug:P [0-2]"); if (programEventType == eProgramStatusEventType.Stopping)
}
}
/// <summary>
/// Sets the debug level
/// </summary>
/// <param name="level"> Valid values 0 (no debug), 1 (critical), 2 (all messages)</param>
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>
/// Appends a device Key to the beginning of a message
/// </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>
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; if (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; SaveTimer.Stop();
Debug.Console(1, "Debug memory restored from file"); SaveTimer = null;
}
Console(0, "Saving debug settings");
SaveMemory();
}
}
/// <summary>
/// Callback for console command
/// </summary>
/// <param name="levelString"></param>
public void SetDebugFromConsole(string levelString)
{
try
{
if (string.IsNullOrEmpty(levelString.Trim()))
{
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", SaveData.Level);
return; return;
} }
else
SaveData = new DebugContextSaveData(); 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>
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>
/// Appends a device Key to the beginning of a message
/// </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>
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>
/// 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>
/// ///
/// </summary> /// </summary>
public int Level { get; set; } public class DebugContextSaveData
{
/// <summary>
///
/// </summary>
public int Level { get; set; }
}
} }

View File

@@ -3,26 +3,27 @@ using Crestron.SimplSharp.CrestronLogger;
using Serilog.Core; using Serilog.Core;
using Serilog.Events; using Serilog.Events;
namespace PepperDash.Core.Logging; namespace PepperDash.Core.Logging
public class DebugCrestronLoggerSink : ILogEventSink
{ {
public void Emit(LogEvent logEvent) public class DebugCrestronLoggerSink : ILogEventSink
{ {
if (!Debug.IsRunningOnAppliance) return; public void Emit(LogEvent logEvent)
string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}";
if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue)
{ {
message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue}]: {logEvent.RenderMessage()}"; if (!Debug.IsRunningOnAppliance) return;
string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}";
if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue)
{
message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}][{rawValue}]: {logEvent.RenderMessage()}";
}
CrestronLogger.WriteToLog(message, (uint)logEvent.Level);
} }
CrestronLogger.WriteToLog(message, (uint)logEvent.Level); public DebugCrestronLoggerSink()
} {
CrestronLogger.Initialize(1, LoggerModeEnum.RM);
public DebugCrestronLoggerSink() }
{
CrestronLogger.Initialize(1, LoggerModeEnum.RM);
} }
} }

View File

@@ -9,56 +9,57 @@ 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
public class DebugErrorLogSink : ILogEventSink
{ {
private ITextFormatter _formatter; public class DebugErrorLogSink : ILogEventSink
private Dictionary<LogEventLevel, Action<string>> _errorLogMap = new Dictionary<LogEventLevel, Action<string>>
{ {
{ LogEventLevel.Verbose, (msg) => ErrorLog.Notice(msg) }, private ITextFormatter _formatter;
{LogEventLevel.Debug, (msg) => ErrorLog.Notice(msg) },
{LogEventLevel.Information, (msg) => ErrorLog.Notice(msg) },
{LogEventLevel.Warning, (msg) => ErrorLog.Warn(msg) },
{LogEventLevel.Error, (msg) => ErrorLog.Error(msg) },
{LogEventLevel.Fatal, (msg) => ErrorLog.Error(msg) }
};
public void Emit(LogEvent logEvent)
{
string message;
if (_formatter == null) private Dictionary<LogEventLevel, Action<string>> _errorLogMap = new Dictionary<LogEventLevel, Action<string>>
{ {
var programId = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance { LogEventLevel.Verbose, (msg) => ErrorLog.Notice(msg) },
? $"App {InitialParametersClass.ApplicationNumber}" {LogEventLevel.Debug, (msg) => ErrorLog.Notice(msg) },
: $"Room {InitialParametersClass.RoomId}"; {LogEventLevel.Information, (msg) => ErrorLog.Notice(msg) },
{LogEventLevel.Warning, (msg) => ErrorLog.Warn(msg) },
{LogEventLevel.Error, (msg) => ErrorLog.Error(msg) },
{LogEventLevel.Fatal, (msg) => ErrorLog.Error(msg) }
};
public void Emit(LogEvent logEvent)
{
string message;
message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}]{logEvent.RenderMessage()}"; if (_formatter == null)
if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue)
{ {
message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}][{rawValue}]: {logEvent.RenderMessage()}"; var programId = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
? $"App {InitialParametersClass.ApplicationNumber}"
: $"Room {InitialParametersClass.RoomId}";
message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}]{logEvent.RenderMessage()}";
if (logEvent.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue)
{
message = $"[{logEvent.Timestamp}][{logEvent.Level}][{programId}][{rawValue}]: {logEvent.RenderMessage()}";
}
} else
{
var buffer = new StringWriter(new StringBuilder(256));
_formatter.Format(logEvent, buffer);
message = buffer.ToString();
} }
} else
{
var buffer = new StringWriter(new StringBuilder(256));
_formatter.Format(logEvent, buffer); if(!_errorLogMap.TryGetValue(logEvent.Level, out var handler))
{
return;
}
message = buffer.ToString(); handler(message);
} }
if(!_errorLogMap.TryGetValue(logEvent.Level, out var handler)) public DebugErrorLogSink(ITextFormatter formatter = null)
{ {
return; _formatter = formatter;
} }
handler(message);
}
public DebugErrorLogSink(ITextFormatter formatter = null)
{
_formatter = formatter;
} }
} }

View File

@@ -1,73 +1,74 @@
using System; using Serilog.Events;
using Serilog.Events; using System;
using Log = PepperDash.Core.Debug; using Log = PepperDash.Core.Debug;
namespace PepperDash.Core.Logging; namespace PepperDash.Core.Logging
public static class DebugExtensions
{ {
public static void LogException(this IKeyed device, Exception ex, string message, params object[] args) public static class DebugExtensions
{ {
Log.LogMessage(ex, message, device, args); public static void LogException(this IKeyed device, Exception ex, string message, params object[] args)
} {
Log.LogMessage(ex, message, device, args);
}
public static void LogVerbose(this IKeyed device, 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.LogMessage(LogEventLevel.Verbose, ex, message, device, args);
} }
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.LogMessage(LogEventLevel.Verbose, device, message, args);
} }
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.LogMessage(LogEventLevel.Debug, ex, message, device, args);
} }
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.LogMessage(LogEventLevel.Debug, device, message, args);
} }
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.LogMessage(LogEventLevel.Information, ex, message, device, args);
} }
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.LogMessage(LogEventLevel.Information, device, message, args);
} }
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.LogMessage(LogEventLevel.Warning, ex, message, device, args);
} }
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.LogMessage(LogEventLevel.Warning, device, message, args);
} }
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.LogMessage(LogEventLevel.Error, ex, message, device, args);
} }
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.LogMessage(LogEventLevel.Error, device, message, args);
} }
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.LogMessage(LogEventLevel.Fatal, ex, message, device, args);
} }
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.LogMessage(LogEventLevel.Fatal, device, message, args);
}
} }
} }

View File

@@ -1,116 +1,115 @@
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>
/// Class to persist current Debug settings across program restarts
/// </summary>
public class DebugContextCollection
{ {
/// <summary> /// <summary>
/// To prevent threading issues with the DeviceDebugSettings collection /// Class to persist current Debug settings across program restarts
/// </summary> /// </summary>
private readonly object _deviceDebugSettingsLock = new(); public class DebugContextCollection
{
/// <summary>
/// To prevent threading issues with the DeviceDebugSettings collection
/// </summary>
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>
/// Default constructor /// Default constructor
/// </summary> /// </summary>
public DebugContextCollection() public DebugContextCollection()
{ {
_deviceDebugSettingsLock = new CCriticalSection();
DeviceDebugSettings = new Dictionary<string, object>();
_items = new Dictionary<string, DebugContextItem>();
}
} /// <summary>
/// Sets the level of a given context item, and adds that item if it does not
/// exist
/// </summary>
/// <param name="contextKey"></param>
/// <param name="level"></param>
public void SetLevel(string contextKey, int level)
{
if (level < 0 || level > 2)
return;
GetOrCreateItem(contextKey).Level = level;
}
/// <summary> /// <summary>
/// Sets the level of a given context item, and adds that item if it does not /// Gets a level or creates it if not existing
/// exist /// </summary>
/// </summary> /// <param name="contextKey"></param>
/// <param name="contextKey"></param> /// <returns></returns>
/// <param name="level"></param> public DebugContextItem GetOrCreateItem(string contextKey)
/// <summary> {
/// SetLevel method if (!_items.ContainsKey(contextKey))
/// </summary> _items[contextKey] = new DebugContextItem { Level = 0 };
public void SetLevel(string contextKey, int level) return _items[contextKey];
{ }
if (level < 0 || level > 2)
return;
GetOrCreateItem(contextKey).Level = level;
}
/// <summary>
/// Gets a level or creates it if not existing
/// </summary>
/// <param name="contextKey"></param>
/// <returns></returns>
/// <summary>
/// GetOrCreateItem method
/// </summary>
public DebugContextItem GetOrCreateItem(string contextKey)
{
if (!_items.ContainsKey(contextKey))
_items[contextKey] = new DebugContextItem { Level = 0 };
return _items[contextKey];
}
/// <summary> /// <summary>
/// sets the settings for a device or creates a new entry /// sets the settings for a device or creates a new entry
/// </summary> /// </summary>
/// <param name="deviceKey"></param> /// <param name="deviceKey"></param>
/// <param name="settings"></param> /// <param name="settings"></param>
/// <returns></returns> /// <returns></returns>
public void SetDebugSettingsForKey(string deviceKey, object settings) public void SetDebugSettingsForKey(string deviceKey, object settings)
{
lock (_deviceDebugSettingsLock)
{ {
if (DeviceDebugSettings.ContainsKey(deviceKey)) try
{ {
DeviceDebugSettings[deviceKey] = settings; _deviceDebugSettingsLock.Enter();
if (DeviceDebugSettings.ContainsKey(deviceKey))
{
DeviceDebugSettings[deviceKey] = settings;
}
else
DeviceDebugSettings.Add(deviceKey, settings);
}
finally
{
_deviceDebugSettingsLock.Leave();
} }
else
DeviceDebugSettings.Add(deviceKey, settings);
} }
}
/// <summary> /// <summary>
/// Gets the device settings for a device by key or returns null /// Gets the device settings for a device by key or returns null
/// </summary> /// </summary>
/// <param name="deviceKey"></param> /// <param name="deviceKey"></param>
/// <returns></returns> /// <returns></returns>
public object GetDebugSettingsForKey(string deviceKey) public object GetDebugSettingsForKey(string deviceKey)
{ {
return DeviceDebugSettings[deviceKey]; return DeviceDebugSettings[deviceKey];
} }
} }
/// <summary> /// <summary>
/// Contains information about /// Contains information about
/// </summary> /// </summary>
public class DebugContextItem public class DebugContextItem
{ {
/// <summary> /// <summary>
/// The level of debug messages to print /// The level of debug messages to print
/// </summary> /// </summary>
[JsonProperty("level")] [JsonProperty("level")]
public int Level { get; set; } public int Level { get; set; }
/// <summary> /// <summary>
/// Property to tell the program not to intitialize when it boots, if desired /// Property to tell the program not to intitialize when it boots, if desired
/// </summary> /// </summary>
[JsonProperty("doNotLoadOnNextBoot")] [JsonProperty("doNotLoadOnNextBoot")]
public bool DoNotLoadOnNextBoot { get; set; } public bool DoNotLoadOnNextBoot { get; set; }
}
} }

View File

@@ -1,336 +1,271 @@
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>
/// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected
/// WebSocket clients.
/// </summary>
/// <remarks>This class implements the <see cref="ILogEventSink"/> interface and is designed to send
/// 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; public class DebugWebsocketSink : ILogEventSink
private readonly string _path = "/debug/join/";
private const string _certificateName = "selfCres";
private const string _certificatePassword = "cres12345";
/// <summary>
/// Gets the port number on which the HTTPS server is currently running.
/// </summary>
public int Port
{ get
{
if(_httpsServer == null) return 0;
return _httpsServer.Port;
}
}
/// <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
{ {
get private HttpServer _httpsServer;
{
if (_httpsServer == null) return ""; private string _path = "/debug/join/";
return $"wss://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{_httpsServer.Port}{_httpsServer.WebSocketServices[_path].Path}"; private const string _certificateName = "selfCres";
} private const string _certificatePassword = "cres12345";
}
/// <summary> public int Port
/// Gets a value indicating whether the HTTPS server is currently listening for incoming connections. { get
/// </summary> {
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
/// <inheritdoc/>
public string Key => "DebugWebsocketSink";
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)
{
_textFormatter = formatProvider ?? new JsonFormatter();
if (!File.Exists($"\\user\\{_certificateName}.pfx"))
CreateCert();
CrestronEnvironment.ProgramStatusEventHandler += type =>
{
if (type == eProgramStatusEventType.Stopping)
{
StopServer();
}
};
}
private static void CreateCert()
{
try
{
var utility = new BouncyCertificate();
var ipAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
var hostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
var domainName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 0);
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]);
//Crestron fails to let us do this...perhaps it should be done through their Dll's but haven't tested
var separator = Path.DirectorySeparatorChar;
utility.CertificatePassword = _certificatePassword;
utility.WriteCertificate(certificate, @$"{separator}user{separator}", _certificateName);
}
catch (Exception ex)
{
//Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace);
CrestronConsole.PrintLine("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace);
}
}
/// <summary>
/// Sends a log event to all connected WebSocket clients.
/// </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)
{
if (_httpsServer == null || !_httpsServer.IsListening) return;
var sw = new StringWriter();
_textFormatter.Format(logEvent, sw);
_httpsServer.WebSocketServices[_path].Sessions.Broadcast(sw.ToString());
}
/// <summary>
/// Starts the WebSocket server on the specified port and configures it with the appropriate certificate.
/// </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)
{
Debug.Console(0, "Starting Websocket Server on port: {0}", port);
Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
}
private void Start(int port, string certPath = "", string certPassword = "")
{
try
{
_httpsServer = new HttpServer(port, true);
if (!string.IsNullOrWhiteSpace(certPath))
{
Debug.Console(0, "Assigning SSL Configuration");
_httpsServer.SslConfiguration.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.Console(0, "HTTPS ClientCerticateValidation Callback triggered");
return true;
};
}
Debug.Console(0, "Adding Debug Client Service");
_httpsServer.AddWebSocketService<DebugClient>(_path);
Debug.Console(0, "Assigning Log Info");
_httpsServer.Log.Level = LogLevel.Trace;
_httpsServer.Log.Output = (d, s) =>
{
uint level;
switch(d.Level)
{
case WebSocketSharp.LogLevel.Fatal:
level = 3;
break;
case WebSocketSharp.LogLevel.Error:
level = 2;
break;
case WebSocketSharp.LogLevel.Warn:
level = 1;
break;
case WebSocketSharp.LogLevel.Info:
level = 0;
break;
case WebSocketSharp.LogLevel.Debug:
level = 4;
break;
case WebSocketSharp.LogLevel.Trace:
level = 5;
break;
default:
level = 4;
break;
}
Debug.Console(level, "{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s); if(_httpsServer == null) return 0;
return _httpsServer.Port;
}
}
public string Url
{
get
{
if (_httpsServer == null) return "";
return $"wss://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{_httpsServer.Port}{_httpsServer.WebSocketServices[_path].Path}";
}
}
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
private readonly ITextFormatter _textFormatter;
public DebugWebsocketSink(ITextFormatter formatProvider)
{
_textFormatter = formatProvider ?? new JsonFormatter();
if (!File.Exists($"\\user\\{_certificateName}.pfx"))
CreateCert(null);
CrestronEnvironment.ProgramStatusEventHandler += type =>
{
if (type == eProgramStatusEventType.Stopping)
{
StopServer();
}
}; };
Debug.Console(0, "Starting");
_httpsServer.Start();
Debug.Console(0, "Ready");
} }
catch (Exception ex)
private void CreateCert(string[] args)
{ {
Debug.Console(0, "WebSocket Failed to start {0}", ex.Message); try
}
}
/// <summary>
/// Stops the WebSocket server if it is currently running.
/// </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()
{
Debug.Console(0, "Stopping Websocket Server");
_httpsServer?.Stop();
_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
{
/// <summary>
/// Configures a logger to write log events to a debug WebSocket sink.
/// </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(
this LoggerSinkConfiguration loggerConfiguration,
ITextFormatter formatProvider = null)
{
return loggerConfiguration.Sink(new DebugWebsocketSink(formatProvider));
}
}
/// <summary>
/// Represents a WebSocket client for debugging purposes, providing connection lifecycle management and message
/// handling functionality.
/// </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
{
private DateTime _connectionTime;
/// <summary>
/// Gets the duration of time the WebSocket connection has been active.
/// </summary>
public TimeSpan ConnectedDuration
{
get
{
if (Context.WebSocket.IsAlive)
{ {
return DateTime.Now - _connectionTime; //Debug.Console(0,"CreateCert Creating Utility");
CrestronConsole.PrintLine("CreateCert Creating Utility");
//var utility = new CertificateUtility();
var utility = new BouncyCertificate();
//Debug.Console(0, "CreateCert Calling CreateCert");
CrestronConsole.PrintLine("CreateCert Calling CreateCert");
//utility.CreateCert();
var ipAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
var hostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
var domainName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 0);
//Debug.Console(0, "DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress);
CrestronConsole.PrintLine(string.Format("DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress));
var certificate = utility.CreateSelfSignedCertificate(string.Format("CN={0}.{1}", hostName, domainName), new[] { string.Format("{0}.{1}", hostName, domainName), ipAddress }, new[] { KeyPurposeID.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
//Debug.Print($"CreateCert Storing Certificate To My.LocalMachine");
//utility.AddCertToStore(certificate, StoreName.My, StoreLocation.LocalMachine);
//Debug.Console(0, "CreateCert Saving Cert to \\user\\");
CrestronConsole.PrintLine("CreateCert Saving Cert to \\user\\");
utility.CertificatePassword = _certificatePassword;
utility.WriteCertificate(certificate, @"\user\", _certificateName);
//Debug.Console(0, "CreateCert Ending CreateCert");
CrestronConsole.PrintLine("CreateCert Ending CreateCert");
} }
else catch (Exception ex)
{ {
return new TimeSpan(0); //Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace);
CrestronConsole.PrintLine(string.Format("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace));
} }
} }
public void Emit(LogEvent logEvent)
{
if (_httpsServer == null || !_httpsServer.IsListening) return;
var sw = new StringWriter();
_textFormatter.Format(logEvent, sw);
_httpsServer.WebSocketServices.Broadcast(sw.ToString());
}
public void StartServerAndSetPort(int port)
{
Debug.Console(0, "Starting Websocket Server on port: {0}", port);
Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
}
private void Start(int port, string certPath = "", string certPassword = "")
{
try
{
_httpsServer = new HttpServer(port, true);
if (!string.IsNullOrWhiteSpace(certPath))
{
Debug.Console(0, "Assigning SSL Configuration");
_httpsServer.SslConfiguration = new ServerSslConfiguration(new X509Certificate2(certPath, certPassword))
{
ClientCertificateRequired = false,
CheckCertificateRevocation = false,
EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
//this is just to test, you might want to actually validate
ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
Debug.Console(0, "HTTPS ClientCerticateValidation Callback triggered");
return true;
}
};
}
Debug.Console(0, "Adding Debug Client Service");
_httpsServer.AddWebSocketService<DebugClient>(_path);
Debug.Console(0, "Assigning Log Info");
_httpsServer.Log.Level = LogLevel.Trace;
_httpsServer.Log.Output = (d, s) =>
{
uint level;
switch(d.Level)
{
case WebSocketSharp.LogLevel.Fatal:
level = 3;
break;
case WebSocketSharp.LogLevel.Error:
level = 2;
break;
case WebSocketSharp.LogLevel.Warn:
level = 1;
break;
case WebSocketSharp.LogLevel.Info:
level = 0;
break;
case WebSocketSharp.LogLevel.Debug:
level = 4;
break;
case WebSocketSharp.LogLevel.Trace:
level = 5;
break;
default:
level = 4;
break;
}
Debug.Console(level, "{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
};
Debug.Console(0, "Starting");
_httpsServer.Start();
Debug.Console(0, "Ready");
}
catch (Exception ex)
{
Debug.Console(0, "WebSocket Failed to start {0}", ex.Message);
}
}
public void StopServer()
{
Debug.Console(0, "Stopping Websocket Server");
_httpsServer?.Stop();
_httpsServer = null;
}
} }
/// <summary> public static class DebugWebsocketSinkExtensions
/// Initializes a new instance of the <see cref="DebugClient"/> class.
/// </summary>
/// <remarks>This constructor creates a new <see cref="DebugClient"/> instance and logs its
/// creation using the <see cref="Debug.Console(int, string)"/> method with a debug level of 0.</remarks>
public DebugClient()
{ {
Debug.Console(0, "DebugClient Created"); public static LoggerConfiguration DebugWebsocketSink(
this LoggerSinkConfiguration loggerConfiguration,
ITextFormatter formatProvider = null)
{
return loggerConfiguration.Sink(new DebugWebsocketSink(formatProvider));
}
} }
/// <inheritdoc/> public class DebugClient : WebSocketBehavior
protected override void OnOpen()
{ {
base.OnOpen(); private DateTime _connectionTime;
var url = Context.WebSocket.Url; public TimeSpan ConnectedDuration
Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url); {
get
{
if (Context.WebSocket.IsAlive)
{
return DateTime.Now - _connectionTime;
}
else
{
return new TimeSpan(0);
}
}
}
_connectionTime = DateTime.Now; public DebugClient()
} {
Debug.Console(0, "DebugClient Created");
}
/// <inheritdoc/> protected override void OnOpen()
protected override void OnMessage(MessageEventArgs e) {
{ base.OnOpen();
base.OnMessage(e);
Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data); var url = Context.WebSocket.Url;
} Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url);
/// <inheritdoc/> _connectionTime = DateTime.Now;
protected override void OnClose(CloseEventArgs e) }
{
base.OnClose(e);
Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason); protected override void OnMessage(MessageEventArgs e)
{
base.OnMessage(e);
} Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data);
}
/// <inheritdoc/> protected override void OnClose(CloseEventArgs e)
protected override void OnError(WebSocketSharp.ErrorEventArgs e) {
{ base.OnClose(e);
base.OnError(e);
Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message); Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
}
protected override void OnError(WebSocketSharp.ErrorEventArgs e)
{
base.OnError(e);
Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
}
} }
} }

View File

@@ -4,17 +4,19 @@ using System.Linq;
using System.Text; using System.Text;
using Crestron.SimplSharp; using Crestron.SimplSharp;
namespace PepperDash.Core; namespace PepperDash.Core
{
/// <summary>
/// Not in use
/// </summary>
public static class NetworkComm
{
/// <summary> /// <summary>
/// Not in use /// Not in use
/// </summary> /// </summary>
public static class NetworkComm
{
/// <summary>
/// Not in use
/// </summary>
static NetworkComm() static NetworkComm()
{ {
} }
} }
}

View File

@@ -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>
@@ -22,4 +22,5 @@ namespace PepperDash.Core.PasswordManagement;
{ {
} }
} }
}

View File

@@ -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>
@@ -53,4 +53,5 @@ namespace PepperDash.Core.PasswordManagement;
/// Generic string value change constant /// Generic string value change constant
/// </summary> /// </summary>
public const ushort StringValueChange = 201; public const ushort StringValueChange = 201;
} }
}

View File

@@ -1,10 +1,10 @@
using System; using System;
namespace PepperDash.Core.PasswordManagement; namespace PepperDash.Core.PasswordManagement
{
/// <summary> /// <summary>
/// A class to allow user interaction with the PasswordManager /// A class to allow user interaction with the PasswordManager
/// </summary> /// </summary>
public class PasswordClient public class PasswordClient
{ {
/// <summary> /// <summary>
@@ -59,9 +59,6 @@ namespace PepperDash.Core.PasswordManagement;
/// Retrieve password by index /// Retrieve password by index
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <summary>
/// GetPasswordByIndex method
/// </summary>
public void GetPasswordByIndex(ushort key) public void GetPasswordByIndex(ushort key)
{ {
OnUshrtChange((ushort)PasswordManager.Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange); OnUshrtChange((ushort)PasswordManager.Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
@@ -84,9 +81,6 @@ namespace PepperDash.Core.PasswordManagement;
/// Password validation method /// Password validation method
/// </summary> /// </summary>
/// <param name="password"></param> /// <param name="password"></param>
/// <summary>
/// ValidatePassword method
/// </summary>
public void ValidatePassword(string password) public void ValidatePassword(string password)
{ {
if (string.IsNullOrEmpty(password)) if (string.IsNullOrEmpty(password))
@@ -105,9 +99,6 @@ namespace PepperDash.Core.PasswordManagement;
/// password against the selected password when the length of the 2 are equal /// password against the selected password when the length of the 2 are equal
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
/// <summary>
/// BuildPassword method
/// </summary>
public void BuildPassword(string data) public void BuildPassword(string data)
{ {
PasswordToValidate = String.Concat(PasswordToValidate, data); PasswordToValidate = String.Concat(PasswordToValidate, data);
@@ -117,9 +108,9 @@ namespace PepperDash.Core.PasswordManagement;
ValidatePassword(PasswordToValidate); ValidatePassword(PasswordToValidate);
} }
/// <summary> /// <summary>
/// ClearPassword method /// Clears the user entered password and resets the LEDs
/// </summary> /// </summary>
public void ClearPassword() public void ClearPassword()
{ {
PasswordToValidate = ""; PasswordToValidate = "";
@@ -192,4 +183,5 @@ namespace PepperDash.Core.PasswordManagement;
GetPasswordByIndex(args.Index); GetPasswordByIndex(args.Index);
} }
} }
} }
}

View File

@@ -2,11 +2,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using Crestron.SimplSharp; using Crestron.SimplSharp;
namespace PepperDash.Core.PasswordManagement; namespace PepperDash.Core.PasswordManagement
{
/// <summary> /// <summary>
/// Allows passwords to be stored and managed /// Allows passwords to be stored and managed
/// </summary> /// </summary>
public class PasswordManager public class PasswordManager
{ {
/// <summary> /// <summary>
@@ -71,9 +71,6 @@ namespace PepperDash.Core.PasswordManagement;
/// </summary> /// </summary>
/// <param name="key"></param> /// <param name="key"></param>
/// <param name="password"></param> /// <param name="password"></param>
/// <summary>
/// UpdatePassword method
/// </summary>
public void UpdatePassword(ushort key, string password) public void UpdatePassword(ushort key, string password)
{ {
// validate the parameters // validate the parameters
@@ -155,9 +152,6 @@ namespace PepperDash.Core.PasswordManagement;
/// Method to change the default timer value, (default 5000ms/5s) /// Method to change the default timer value, (default 5000ms/5s)
/// </summary> /// </summary>
/// <param name="time"></param> /// <param name="time"></param>
/// <summary>
/// PasswordTimerMs method
/// </summary>
public void PasswordTimerMs(ushort time) public void PasswordTimerMs(ushort time)
{ {
PasswordTimerElapsedMs = Convert.ToInt64(time); PasswordTimerElapsedMs = Convert.ToInt64(time);
@@ -196,7 +190,7 @@ namespace PepperDash.Core.PasswordManagement;
/// <summary> /// <summary>
/// Protected ushort change event handler /// Protected ushort change event handler
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
/// <param name="index"></param> /// <param name="index"></param>
/// <param name="type"></param> /// <param name="type"></param>
protected void OnUshrtChange(ushort value, ushort index, ushort type) protected void OnUshrtChange(ushort value, ushort index, ushort type)
@@ -243,4 +237,5 @@ namespace PepperDash.Core.PasswordManagement;
PasswordChange(this, args); PasswordChange(this, args);
} }
} }
} }
}

View File

@@ -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>
@@ -35,20 +35,25 @@
<EmbeddedResource Remove="Properties\**" /> <EmbeddedResource Remove="Properties\**" />
<None Remove="lib\**" /> <None Remove="lib\**" />
<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" />

View File

@@ -4,87 +4,87 @@ 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>
public class SystemInfoConstants public class SystemInfoConstants
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort BoolValueChange = 1; public const ushort BoolValueChange = 1;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort CompleteBoolChange = 2; public const ushort CompleteBoolChange = 2;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort BusyBoolChange = 3; public const ushort BusyBoolChange = 3;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort UshortValueChange = 101; public const ushort UshortValueChange = 101;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort StringValueChange = 201; public const ushort StringValueChange = 201;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort ConsoleResponseChange = 202; public const ushort ConsoleResponseChange = 202;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort ProcessorUptimeChange = 203; public const ushort ProcessorUptimeChange = 203;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort ProgramUptimeChange = 204; public const ushort ProgramUptimeChange = 204;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort ObjectChange = 301; public const ushort ObjectChange = 301;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort ProcessorConfigChange = 302; public const ushort ProcessorConfigChange = 302;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort EthernetConfigChange = 303; public const ushort EthernetConfigChange = 303;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort ControlSubnetConfigChange = 304; public const ushort ControlSubnetConfigChange = 304;
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public const ushort ProgramConfigChange = 305; public const ushort ProgramConfigChange = 305;
} }
/// <summary> /// <summary>
/// Represents a ProcessorChangeEventArgs /// Processor Change Event Args Class
/// </summary> /// </summary>
public class ProcessorChangeEventArgs : EventArgs public class ProcessorChangeEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ProcessorInfo Processor { get; set; } public ProcessorInfo Processor { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort Type { get; set; } public ushort Type { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort Index { get; set; } public ushort Index { get; set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
@@ -114,23 +114,23 @@ namespace PepperDash.Core.SystemInfo;
} }
} }
/// <summary> /// <summary>
/// Represents a EthernetChangeEventArgs /// Ethernet Change Event Args Class
/// </summary> /// </summary>
public class EthernetChangeEventArgs : EventArgs public class EthernetChangeEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public EthernetInfo Adapter { get; set; } public EthernetInfo Adapter { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort Type { get; set; } public ushort Type { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort Index { get; set; } public ushort Index { get; set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
@@ -143,7 +143,7 @@ namespace PepperDash.Core.SystemInfo;
/// <summary> /// <summary>
/// Constructor overload /// Constructor overload
/// </summary> /// </summary>
/// <param name="ethernet"></param> /// <param name="ethernet"></param>
/// <param name="type"></param> /// <param name="type"></param>
public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type) public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type)
{ {
@@ -154,9 +154,9 @@ namespace PepperDash.Core.SystemInfo;
/// <summary> /// <summary>
/// Constructor overload /// Constructor overload
/// </summary> /// </summary>
/// <param name="ethernet"></param> /// <param name="ethernet"></param>
/// <param name="type"></param> /// <param name="type"></param>
/// <param name="index"></param> /// <param name="index"></param>
public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type, ushort index) public EthernetChangeEventArgs(EthernetInfo ethernet, ushort type, ushort index)
{ {
Adapter = ethernet; Adapter = ethernet;
@@ -165,23 +165,23 @@ namespace PepperDash.Core.SystemInfo;
} }
} }
/// <summary> /// <summary>
/// Represents a ControlSubnetChangeEventArgs /// Control Subnet Chage Event Args Class
/// </summary> /// </summary>
public class ControlSubnetChangeEventArgs : EventArgs public class ControlSubnetChangeEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ControlSubnetInfo Adapter { get; set; } public ControlSubnetInfo Adapter { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort Type { get; set; } public ushort Type { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort Index { get; set; } public ushort Index { get; set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
@@ -211,23 +211,23 @@ namespace PepperDash.Core.SystemInfo;
} }
} }
/// <summary> /// <summary>
/// Represents a ProgramChangeEventArgs /// Program Change Event Args Class
/// </summary> /// </summary>
public class ProgramChangeEventArgs : EventArgs public class ProgramChangeEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ProgramInfo Program { get; set; } public ProgramInfo Program { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort Type { get; set; } public ushort Type { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort Index { get; set; } public ushort Index { get; set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
@@ -240,7 +240,7 @@ namespace PepperDash.Core.SystemInfo;
/// <summary> /// <summary>
/// Constructor overload /// Constructor overload
/// </summary> /// </summary>
/// <param name="program"></param> /// <param name="program"></param>
/// <param name="type"></param> /// <param name="type"></param>
public ProgramChangeEventArgs(ProgramInfo program, ushort type) public ProgramChangeEventArgs(ProgramInfo program, ushort type)
{ {
@@ -251,13 +251,14 @@ namespace PepperDash.Core.SystemInfo;
/// <summary> /// <summary>
/// Constructor overload /// Constructor overload
/// </summary> /// </summary>
/// <param name="program"></param> /// <param name="program"></param>
/// <param name="type"></param> /// <param name="type"></param>
/// <param name="index"></param> /// <param name="index"></param>
public ProgramChangeEventArgs(ProgramInfo program, ushort type, ushort index) public ProgramChangeEventArgs(ProgramInfo program, ushort type, ushort index)
{ {
Program = program; Program = program;
Type = type; Type = type;
Index = index; Index = index;
} }
} }
}

View File

@@ -4,52 +4,52 @@ 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>
public class ProcessorInfo public class ProcessorInfo
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Model { get; set; } public string Model { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string SerialNumber { get; set; } public string SerialNumber { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Firmware { get; set; } public string Firmware { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string FirmwareDate { get; set; } public string FirmwareDate { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string OsVersion { get; set; } public string OsVersion { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string RuntimeEnvironment { get; set; } public string RuntimeEnvironment { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string DevicePlatform { get; set; } public string DevicePlatform { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string ModuleDirectory { get; set; } public string ModuleDirectory { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string LocalTimeZone { get; set; } public string LocalTimeZone { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string ProgramIdTag { get; set; } public string ProgramIdTag { get; set; }
/// <summary> /// <summary>
@@ -66,45 +66,45 @@ namespace PepperDash.Core.SystemInfo;
/// </summary> /// </summary>
public class EthernetInfo public class EthernetInfo
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort DhcpIsOn { get; set; } public ushort DhcpIsOn { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Hostname { get; set; } public string Hostname { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string MacAddress { get; set; } public string MacAddress { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string IpAddress { get; set; } public string IpAddress { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Subnet { get; set; } public string Subnet { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Gateway { get; set; } public string Gateway { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Dns1 { get; set; } public string Dns1 { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Dns2 { get; set; } public string Dns2 { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Dns3 { get; set; } public string Dns3 { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Domain { get; set; } public string Domain { get; set; }
/// <summary> /// <summary>
@@ -121,29 +121,29 @@ namespace PepperDash.Core.SystemInfo;
/// </summary> /// </summary>
public class ControlSubnetInfo public class ControlSubnetInfo
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort Enabled { get; set; } public ushort Enabled { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public ushort IsInAutomaticMode { get; set; } public ushort IsInAutomaticMode { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string MacAddress { get; set; } public string MacAddress { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string IpAddress { get; set; } public string IpAddress { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Subnet { get; set; } public string Subnet { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string RouterPrefix { get; set; } public string RouterPrefix { get; set; }
/// <summary> /// <summary>
@@ -160,37 +160,37 @@ namespace PepperDash.Core.SystemInfo;
/// </summary> /// </summary>
public class ProgramInfo public class ProgramInfo
{ {
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Header { get; set; } public string Header { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string System { get; set; } public string System { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string ProgramIdTag { get; set; } public string ProgramIdTag { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string CompileTime { get; set; } public string CompileTime { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Database { get; set; } public string Database { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Environment { get; set; } public string Environment { get; set; }
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public string Programmer { get; set; } public string Programmer { get; set; }
/// <summary> /// <summary>
@@ -200,4 +200,5 @@ namespace PepperDash.Core.SystemInfo;
{ {
} }
} }
}

View File

@@ -4,37 +4,37 @@ 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>
public class SystemInfoToSimpl public class SystemInfoToSimpl
{ {
/// <summary> /// <summary>
/// Notifies of bool change /// Notifies of bool change
/// </summary> /// </summary>
public event EventHandler<BoolChangeEventArgs> BoolChange; public event EventHandler<BoolChangeEventArgs> BoolChange;
/// <summary> /// <summary>
/// Notifies of string change /// Notifies of string change
/// </summary> /// </summary>
public event EventHandler<StringChangeEventArgs> StringChange; public event EventHandler<StringChangeEventArgs> StringChange;
/// <summary> /// <summary>
/// Notifies of processor change /// Notifies of processor change
/// </summary> /// </summary>
public event EventHandler<ProcessorChangeEventArgs> ProcessorChange; public event EventHandler<ProcessorChangeEventArgs> ProcessorChange;
/// <summary> /// <summary>
/// Notifies of ethernet change /// Notifies of ethernet change
/// </summary> /// </summary>
public event EventHandler<EthernetChangeEventArgs> EthernetChange; public event EventHandler<EthernetChangeEventArgs> EthernetChange;
/// <summary> /// <summary>
/// Notifies of control subnet change /// Notifies of control subnet change
/// </summary> /// </summary>
public event EventHandler<ControlSubnetChangeEventArgs> ControlSubnetChange; public event EventHandler<ControlSubnetChangeEventArgs> ControlSubnetChange;
/// <summary> /// <summary>
/// Notifies of program change /// Notifies of program change
/// </summary> /// </summary>
public event EventHandler<ProgramChangeEventArgs> ProgramChange; public event EventHandler<ProgramChangeEventArgs> ProgramChange;
/// <summary> /// <summary>
@@ -100,9 +100,9 @@ namespace PepperDash.Core.SystemInfo;
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
} }
/// <summary> /// <summary>
/// GetEthernetInfo method /// Gets the current ethernet info
/// </summary> /// </summary>
public void GetEthernetInfo() public void GetEthernetInfo()
{ {
OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange); OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange);
@@ -161,9 +161,9 @@ namespace PepperDash.Core.SystemInfo;
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
} }
/// <summary> /// <summary>
/// GetControlSubnetInfo method /// Gets the current control subnet info
/// </summary> /// </summary>
public void GetControlSubnetInfo() public void GetControlSubnetInfo()
{ {
OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange); OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange);
@@ -206,9 +206,6 @@ namespace PepperDash.Core.SystemInfo;
/// Gets the program info by index /// Gets the program info by index
/// </summary> /// </summary>
/// <param name="index"></param> /// <param name="index"></param>
/// <summary>
/// GetProgramInfoByIndex method
/// </summary>
public void GetProgramInfoByIndex(ushort index) public void GetProgramInfoByIndex(ushort index)
{ {
if (index < 1 || index > 10) if (index < 1 || index > 10)
@@ -266,9 +263,9 @@ namespace PepperDash.Core.SystemInfo;
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange); OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
} }
/// <summary> /// <summary>
/// RefreshProcessorUptime method /// Gets the processor uptime and passes it to S+
/// </summary> /// </summary>
public void RefreshProcessorUptime() public void RefreshProcessorUptime()
{ {
try try
@@ -290,9 +287,6 @@ namespace PepperDash.Core.SystemInfo;
/// Gets the program uptime, by index, and passes it to S+ /// Gets the program uptime, by index, and passes it to S+
/// </summary> /// </summary>
/// <param name="index"></param> /// <param name="index"></param>
/// <summary>
/// RefreshProgramUptimeByIndex method
/// </summary>
public void RefreshProgramUptimeByIndex(int index) public void RefreshProgramUptimeByIndex(int index)
{ {
try try
@@ -314,9 +308,6 @@ namespace PepperDash.Core.SystemInfo;
/// Sends command to console, passes response back using string change event /// Sends command to console, passes response back using string change event
/// </summary> /// </summary>
/// <param name="cmd"></param> /// <param name="cmd"></param>
/// <summary>
/// SendConsoleCommand method
/// </summary>
public void SendConsoleCommand(string cmd) public void SendConsoleCommand(string cmd)
{ {
if (string.IsNullOrEmpty(cmd)) if (string.IsNullOrEmpty(cmd))
@@ -336,10 +327,10 @@ namespace PepperDash.Core.SystemInfo;
/// <summary> /// <summary>
/// private method to parse console messages /// private method to parse console messages
/// </summary> /// </summary>
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="line"></param> /// <param name="line"></param>
/// <param name="dataStart"></param> /// <param name="dataStart"></param>
/// <param name="dataEnd"></param> /// <param name="dataEnd"></param>
/// <returns></returns> /// <returns></returns>
private string ParseConsoleResponse(string data, string line, string dataStart, string dataEnd) private string ParseConsoleResponse(string data, string line, string dataStart, string dataEnd)
{ {
@@ -467,4 +458,5 @@ namespace PepperDash.Core.SystemInfo;
ProgramChange(this, args); ProgramChange(this, args);
} }
} }
} }
}

View File

@@ -20,336 +20,337 @@ 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>
/// Taken From https://github.com/rlipscombe/bouncy-castle-csharp/
/// </summary>
internal class BouncyCertificate
{ {
public string CertificatePassword { get; set; } = "password";
public X509Certificate2 LoadCertificate(string issuerFileName, string password)
{
// We need to pass 'Exportable', otherwise we can't get the private key.
var issuerCertificate = new X509Certificate2(issuerFileName, password, X509KeyStorageFlags.Exportable);
return issuerCertificate;
}
public X509Certificate2 IssueCertificate(string subjectName, X509Certificate2 issuerCertificate, string[] subjectAlternativeNames, KeyPurposeID[] usages)
{
// It's self-signed, so these are the same.
var issuerName = issuerCertificate.Subject;
var random = GetSecureRandom();
var subjectKeyPair = GenerateKeyPair(random, 2048);
var issuerKeyPair = DotNetUtilities.GetKeyPair(issuerCertificate.PrivateKey);
var serialNumber = GenerateSerialNumber(random);
var issuerSerialNumber = new BigInteger(issuerCertificate.GetSerialNumber());
const bool isCertificateAuthority = false;
var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
subjectAlternativeNames, issuerName, issuerKeyPair,
issuerSerialNumber, isCertificateAuthority,
usages);
return ConvertCertificate(certificate, subjectKeyPair, random);
}
public X509Certificate2 CreateCertificateAuthorityCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
{
// It's self-signed, so these are the same.
var issuerName = subjectName;
var random = GetSecureRandom();
var subjectKeyPair = GenerateKeyPair(random, 2048);
// It's self-signed, so these are the same.
var issuerKeyPair = subjectKeyPair;
var serialNumber = GenerateSerialNumber(random);
var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number.
const bool isCertificateAuthority = true;
var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
subjectAlternativeNames, issuerName, issuerKeyPair,
issuerSerialNumber, isCertificateAuthority,
usages);
return ConvertCertificate(certificate, subjectKeyPair, random);
}
public X509Certificate2 CreateSelfSignedCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
{
// It's self-signed, so these are the same.
var issuerName = subjectName;
var random = GetSecureRandom();
var subjectKeyPair = GenerateKeyPair(random, 2048);
// It's self-signed, so these are the same.
var issuerKeyPair = subjectKeyPair;
var serialNumber = GenerateSerialNumber(random);
var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number.
const bool isCertificateAuthority = false;
var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
subjectAlternativeNames, issuerName, issuerKeyPair,
issuerSerialNumber, isCertificateAuthority,
usages);
return ConvertCertificate(certificate, subjectKeyPair, random);
}
private SecureRandom GetSecureRandom()
{
// Since we're on Windows, we'll use the CryptoAPI one (on the assumption
// that it might have access to better sources of entropy than the built-in
// Bouncy Castle ones):
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
return random;
}
private X509Certificate GenerateCertificate(SecureRandom random,
string subjectName,
AsymmetricCipherKeyPair subjectKeyPair,
BigInteger subjectSerialNumber,
string[] subjectAlternativeNames,
string issuerName,
AsymmetricCipherKeyPair issuerKeyPair,
BigInteger issuerSerialNumber,
bool isCertificateAuthority,
KeyPurposeID[] usages)
{
var certificateGenerator = new X509V3CertificateGenerator();
certificateGenerator.SetSerialNumber(subjectSerialNumber);
var issuerDN = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDN);
// Note: The subject can be omitted if you specify a subject alternative name (SAN).
var subjectDN = new X509Name(subjectName);
certificateGenerator.SetSubjectDN(subjectDN);
// Our certificate needs valid from/to values.
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(2);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
// The subject's public key goes in the certificate.
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
AddAuthorityKeyIdentifier(certificateGenerator, issuerDN, issuerKeyPair, issuerSerialNumber);
AddSubjectKeyIdentifier(certificateGenerator, subjectKeyPair);
//AddBasicConstraints(certificateGenerator, isCertificateAuthority);
if (usages != null && usages.Any())
AddExtendedKeyUsage(certificateGenerator, usages);
if (subjectAlternativeNames != null && subjectAlternativeNames.Any())
AddSubjectAlternativeNames(certificateGenerator, subjectAlternativeNames);
// Set the signature algorithm. This is used to generate the thumbprint which is then signed
// with the issuer's private key. We'll use SHA-256, which is (currently) considered fairly strong.
const string signatureAlgorithm = "SHA256WithRSA";
// The certificate is signed with the issuer's private key.
ISignatureFactory signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private, random);
var certificate = certificateGenerator.Generate(signatureFactory);
return certificate;
}
/// <summary> /// <summary>
/// The certificate needs a serial number. This is used for revocation, /// Taken From https://github.com/rlipscombe/bouncy-castle-csharp/
/// and usually should be an incrementing index (which makes it easier to revoke a range of certificates).
/// Since we don't have anywhere to store the incrementing index, we can just use a random number.
/// </summary> /// </summary>
/// <param name="random"></param> internal class BouncyCertificate
/// <returns></returns>
private BigInteger GenerateSerialNumber(SecureRandom random)
{ {
var serialNumber = public string CertificatePassword { get; set; } = "password";
BigIntegers.CreateRandomInRange( public X509Certificate2 LoadCertificate(string issuerFileName, string password)
BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
return serialNumber;
}
/// <summary>
/// Generate a key pair.
/// </summary>
/// <param name="random">The random number generator.</param>
/// <param name="strength">The key length in bits. For RSA, 2048 bits should be considered the minimum acceptable these days.</param>
/// <returns></returns>
private AsymmetricCipherKeyPair GenerateKeyPair(SecureRandom random, int strength)
{
var keyGenerationParameters = new KeyGenerationParameters(random, strength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
return subjectKeyPair;
}
/// <summary>
/// Add the Authority Key Identifier. According to http://www.alvestrand.no/objectid/2.5.29.35.html, this
/// identifies the public key to be used to verify the signature on this certificate.
/// In a certificate chain, this corresponds to the "Subject Key Identifier" on the *issuer* certificate.
/// The Bouncy Castle documentation, at http://www.bouncycastle.org/wiki/display/JA1/X.509+Public+Key+Certificate+and+Certification+Request+Generation,
/// shows how to create this from the issuing certificate. Since we're creating a self-signed certificate, we have to do this slightly differently.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="issuerDN"></param>
/// <param name="issuerKeyPair"></param>
/// <param name="issuerSerialNumber"></param>
private void AddAuthorityKeyIdentifier(X509V3CertificateGenerator certificateGenerator,
X509Name issuerDN,
AsymmetricCipherKeyPair issuerKeyPair,
BigInteger issuerSerialNumber)
{
var authorityKeyIdentifierExtension =
new AuthorityKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(issuerKeyPair.Public),
new GeneralNames(new GeneralName(issuerDN)),
issuerSerialNumber);
certificateGenerator.AddExtension(
X509Extensions.AuthorityKeyIdentifier.Id, false, authorityKeyIdentifierExtension);
}
/// <summary>
/// Add the "Subject Alternative Names" extension. Note that you have to repeat
/// the value from the "Subject Name" property.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="subjectAlternativeNames"></param>
private void AddSubjectAlternativeNames(X509V3CertificateGenerator certificateGenerator,
IEnumerable<string> subjectAlternativeNames)
{
var subjectAlternativeNamesExtension =
new DerSequence(
subjectAlternativeNames.Select(name => new GeneralName(GeneralName.DnsName, name))
.ToArray<Asn1Encodable>());
certificateGenerator.AddExtension(
X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
}
/// <summary>
/// Add the "Extended Key Usage" extension, specifying (for example) "server authentication".
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="usages"></param>
private void AddExtendedKeyUsage(X509V3CertificateGenerator certificateGenerator, KeyPurposeID[] usages)
{
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(usages));
}
/// <summary>
/// Add the "Basic Constraints" extension.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="isCertificateAuthority"></param>
private void AddBasicConstraints(X509V3CertificateGenerator certificateGenerator,
bool isCertificateAuthority)
{
certificateGenerator.AddExtension(
X509Extensions.BasicConstraints.Id, true, new BasicConstraints(isCertificateAuthority));
}
/// <summary>
/// Add the Subject Key Identifier.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="subjectKeyPair"></param>
private void AddSubjectKeyIdentifier(X509V3CertificateGenerator certificateGenerator,
AsymmetricCipherKeyPair subjectKeyPair)
{
var subjectKeyIdentifierExtension =
new SubjectKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(subjectKeyPair.Public));
certificateGenerator.AddExtension(
X509Extensions.SubjectKeyIdentifier.Id, false, subjectKeyIdentifierExtension);
}
private X509Certificate2 ConvertCertificate(X509Certificate certificate,
AsymmetricCipherKeyPair subjectKeyPair,
SecureRandom random)
{
// Now to convert the Bouncy Castle certificate to a .NET certificate.
// See http://web.archive.org/web/20100504192226/http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx
// ...but, basically, we create a PKCS12 store (a .PFX file) in memory, and add the public and private key to that.
var store = new Pkcs12StoreBuilder().Build();
// What Bouncy Castle calls "alias" is the same as what Windows terms the "friendly name".
string friendlyName = certificate.SubjectDN.ToString();
// Add the certificate.
var certificateEntry = new X509CertificateEntry(certificate);
store.SetCertificateEntry(friendlyName, certificateEntry);
// Add the private key.
store.SetKeyEntry(friendlyName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });
// Convert it to an X509Certificate2 object by saving/loading it from a MemoryStream.
// It needs a password. Since we'll remove this later, it doesn't particularly matter what we use.
var stream = new MemoryStream();
store.Save(stream, CertificatePassword.ToCharArray(), random);
var convertedCertificate =
new X509Certificate2(stream.ToArray(),
CertificatePassword,
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
return convertedCertificate;
}
public void WriteCertificate(X509Certificate2 certificate, string outputDirectory, string certName)
{
// This password is the one attached to the PFX file. Use 'null' for no password.
// Create PFX (PKCS #12) with private key
try
{ {
var pfx = certificate.Export(X509ContentType.Pfx, CertificatePassword); // We need to pass 'Exportable', otherwise we can't get the private key.
File.WriteAllBytes(string.Format("{0}.pfx", Path.Combine(outputDirectory, certName)), pfx); var issuerCertificate = new X509Certificate2(issuerFileName, password, X509KeyStorageFlags.Exportable);
return issuerCertificate;
} }
catch (Exception ex)
public X509Certificate2 IssueCertificate(string subjectName, X509Certificate2 issuerCertificate, string[] subjectAlternativeNames, KeyPurposeID[] usages)
{ {
CrestronConsole.PrintLine(string.Format("Failed to write x509 cert pfx\r\n{0}", ex.Message)); // It's self-signed, so these are the same.
var issuerName = issuerCertificate.Subject;
var random = GetSecureRandom();
var subjectKeyPair = GenerateKeyPair(random, 2048);
var issuerKeyPair = DotNetUtilities.GetKeyPair(issuerCertificate.PrivateKey);
var serialNumber = GenerateSerialNumber(random);
var issuerSerialNumber = new BigInteger(issuerCertificate.GetSerialNumber());
const bool isCertificateAuthority = false;
var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
subjectAlternativeNames, issuerName, issuerKeyPair,
issuerSerialNumber, isCertificateAuthority,
usages);
return ConvertCertificate(certificate, subjectKeyPair, random);
} }
// Create Base 64 encoded CER (public key only)
using (var writer = new StreamWriter($"{Path.Combine(outputDirectory, certName)}.cer", false)) public X509Certificate2 CreateCertificateAuthorityCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
{ {
// It's self-signed, so these are the same.
var issuerName = subjectName;
var random = GetSecureRandom();
var subjectKeyPair = GenerateKeyPair(random, 2048);
// It's self-signed, so these are the same.
var issuerKeyPair = subjectKeyPair;
var serialNumber = GenerateSerialNumber(random);
var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number.
const bool isCertificateAuthority = true;
var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
subjectAlternativeNames, issuerName, issuerKeyPair,
issuerSerialNumber, isCertificateAuthority,
usages);
return ConvertCertificate(certificate, subjectKeyPair, random);
}
public X509Certificate2 CreateSelfSignedCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
{
// It's self-signed, so these are the same.
var issuerName = subjectName;
var random = GetSecureRandom();
var subjectKeyPair = GenerateKeyPair(random, 2048);
// It's self-signed, so these are the same.
var issuerKeyPair = subjectKeyPair;
var serialNumber = GenerateSerialNumber(random);
var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number.
const bool isCertificateAuthority = false;
var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
subjectAlternativeNames, issuerName, issuerKeyPair,
issuerSerialNumber, isCertificateAuthority,
usages);
return ConvertCertificate(certificate, subjectKeyPair, random);
}
private SecureRandom GetSecureRandom()
{
// Since we're on Windows, we'll use the CryptoAPI one (on the assumption
// that it might have access to better sources of entropy than the built-in
// Bouncy Castle ones):
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
return random;
}
private X509Certificate GenerateCertificate(SecureRandom random,
string subjectName,
AsymmetricCipherKeyPair subjectKeyPair,
BigInteger subjectSerialNumber,
string[] subjectAlternativeNames,
string issuerName,
AsymmetricCipherKeyPair issuerKeyPair,
BigInteger issuerSerialNumber,
bool isCertificateAuthority,
KeyPurposeID[] usages)
{
var certificateGenerator = new X509V3CertificateGenerator();
certificateGenerator.SetSerialNumber(subjectSerialNumber);
var issuerDN = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDN);
// Note: The subject can be omitted if you specify a subject alternative name (SAN).
var subjectDN = new X509Name(subjectName);
certificateGenerator.SetSubjectDN(subjectDN);
// Our certificate needs valid from/to values.
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(2);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
// The subject's public key goes in the certificate.
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
AddAuthorityKeyIdentifier(certificateGenerator, issuerDN, issuerKeyPair, issuerSerialNumber);
AddSubjectKeyIdentifier(certificateGenerator, subjectKeyPair);
//AddBasicConstraints(certificateGenerator, isCertificateAuthority);
if (usages != null && usages.Any())
AddExtendedKeyUsage(certificateGenerator, usages);
if (subjectAlternativeNames != null && subjectAlternativeNames.Any())
AddSubjectAlternativeNames(certificateGenerator, subjectAlternativeNames);
// Set the signature algorithm. This is used to generate the thumbprint which is then signed
// with the issuer's private key. We'll use SHA-256, which is (currently) considered fairly strong.
const string signatureAlgorithm = "SHA256WithRSA";
// The certificate is signed with the issuer's private key.
ISignatureFactory signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private, random);
var certificate = certificateGenerator.Generate(signatureFactory);
return certificate;
}
/// <summary>
/// The certificate needs a serial number. This is used for revocation,
/// and usually should be an incrementing index (which makes it easier to revoke a range of certificates).
/// Since we don't have anywhere to store the incrementing index, we can just use a random number.
/// </summary>
/// <param name="random"></param>
/// <returns></returns>
private BigInteger GenerateSerialNumber(SecureRandom random)
{
var serialNumber =
BigIntegers.CreateRandomInRange(
BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
return serialNumber;
}
/// <summary>
/// Generate a key pair.
/// </summary>
/// <param name="random">The random number generator.</param>
/// <param name="strength">The key length in bits. For RSA, 2048 bits should be considered the minimum acceptable these days.</param>
/// <returns></returns>
private AsymmetricCipherKeyPair GenerateKeyPair(SecureRandom random, int strength)
{
var keyGenerationParameters = new KeyGenerationParameters(random, strength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
return subjectKeyPair;
}
/// <summary>
/// Add the Authority Key Identifier. According to http://www.alvestrand.no/objectid/2.5.29.35.html, this
/// identifies the public key to be used to verify the signature on this certificate.
/// In a certificate chain, this corresponds to the "Subject Key Identifier" on the *issuer* certificate.
/// The Bouncy Castle documentation, at http://www.bouncycastle.org/wiki/display/JA1/X.509+Public+Key+Certificate+and+Certification+Request+Generation,
/// shows how to create this from the issuing certificate. Since we're creating a self-signed certificate, we have to do this slightly differently.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="issuerDN"></param>
/// <param name="issuerKeyPair"></param>
/// <param name="issuerSerialNumber"></param>
private void AddAuthorityKeyIdentifier(X509V3CertificateGenerator certificateGenerator,
X509Name issuerDN,
AsymmetricCipherKeyPair issuerKeyPair,
BigInteger issuerSerialNumber)
{
var authorityKeyIdentifierExtension =
new AuthorityKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(issuerKeyPair.Public),
new GeneralNames(new GeneralName(issuerDN)),
issuerSerialNumber);
certificateGenerator.AddExtension(
X509Extensions.AuthorityKeyIdentifier.Id, false, authorityKeyIdentifierExtension);
}
/// <summary>
/// Add the "Subject Alternative Names" extension. Note that you have to repeat
/// the value from the "Subject Name" property.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="subjectAlternativeNames"></param>
private void AddSubjectAlternativeNames(X509V3CertificateGenerator certificateGenerator,
IEnumerable<string> subjectAlternativeNames)
{
var subjectAlternativeNamesExtension =
new DerSequence(
subjectAlternativeNames.Select(name => new GeneralName(GeneralName.DnsName, name))
.ToArray<Asn1Encodable>());
certificateGenerator.AddExtension(
X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
}
/// <summary>
/// Add the "Extended Key Usage" extension, specifying (for example) "server authentication".
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="usages"></param>
private void AddExtendedKeyUsage(X509V3CertificateGenerator certificateGenerator, KeyPurposeID[] usages)
{
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(usages));
}
/// <summary>
/// Add the "Basic Constraints" extension.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="isCertificateAuthority"></param>
private void AddBasicConstraints(X509V3CertificateGenerator certificateGenerator,
bool isCertificateAuthority)
{
certificateGenerator.AddExtension(
X509Extensions.BasicConstraints.Id, true, new BasicConstraints(isCertificateAuthority));
}
/// <summary>
/// Add the Subject Key Identifier.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="subjectKeyPair"></param>
private void AddSubjectKeyIdentifier(X509V3CertificateGenerator certificateGenerator,
AsymmetricCipherKeyPair subjectKeyPair)
{
var subjectKeyIdentifierExtension =
new SubjectKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(subjectKeyPair.Public));
certificateGenerator.AddExtension(
X509Extensions.SubjectKeyIdentifier.Id, false, subjectKeyIdentifierExtension);
}
private X509Certificate2 ConvertCertificate(X509Certificate certificate,
AsymmetricCipherKeyPair subjectKeyPair,
SecureRandom random)
{
// Now to convert the Bouncy Castle certificate to a .NET certificate.
// See http://web.archive.org/web/20100504192226/http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx
// ...but, basically, we create a PKCS12 store (a .PFX file) in memory, and add the public and private key to that.
var store = new Pkcs12StoreBuilder().Build();
// What Bouncy Castle calls "alias" is the same as what Windows terms the "friendly name".
string friendlyName = certificate.SubjectDN.ToString();
// Add the certificate.
var certificateEntry = new X509CertificateEntry(certificate);
store.SetCertificateEntry(friendlyName, certificateEntry);
// Add the private key.
store.SetKeyEntry(friendlyName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });
// Convert it to an X509Certificate2 object by saving/loading it from a MemoryStream.
// It needs a password. Since we'll remove this later, it doesn't particularly matter what we use.
var stream = new MemoryStream();
store.Save(stream, CertificatePassword.ToCharArray(), random);
var convertedCertificate =
new X509Certificate2(stream.ToArray(),
CertificatePassword,
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
return convertedCertificate;
}
public void WriteCertificate(X509Certificate2 certificate, string outputDirectory, string certName)
{
// This password is the one attached to the PFX file. Use 'null' for no password.
// Create PFX (PKCS #12) with private key
try try
{ {
var contents = string.Format("-----BEGIN CERTIFICATE-----\r\n{0}\r\n-----END CERTIFICATE-----", Convert.ToBase64String(certificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)); var pfx = certificate.Export(X509ContentType.Pfx, CertificatePassword);
writer.Write(contents); File.WriteAllBytes(string.Format("{0}.pfx", Path.Combine(outputDirectory, certName)), pfx);
} }
catch (Exception ex) catch (Exception ex)
{ {
CrestronConsole.PrintLine(string.Format("Failed to write x509 cert cer\r\n{0}", ex.Message)); CrestronConsole.PrintLine(string.Format("Failed to write x509 cert pfx\r\n{0}", ex.Message));
}
// Create Base 64 encoded CER (public key only)
using (var writer = new StreamWriter($"{Path.Combine(outputDirectory, certName)}.cer", false))
{
try
{
var contents = string.Format("-----BEGIN CERTIFICATE-----\r\n{0}\r\n-----END CERTIFICATE-----", Convert.ToBase64String(certificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));
writer.Write(contents);
}
catch (Exception ex)
{
CrestronConsole.PrintLine(string.Format("Failed to write x509 cert cer\r\n{0}", ex.Message));
}
} }
} }
} public bool AddCertToStore(X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl)
public bool AddCertToStore(X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl)
{
bool bRet = false;
try
{ {
var store = new System.Security.Cryptography.X509Certificates.X509Store(st, sl); bool bRet = false;
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadWrite);
store.Add(cert);
store.Close(); try
bRet = true; {
} var store = new System.Security.Cryptography.X509Certificates.X509Store(st, sl);
catch (Exception ex) store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadWrite);
{ store.Add(cert);
CrestronConsole.PrintLine(string.Format("AddCertToStore Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace));
}
return bRet; store.Close();
bRet = true;
}
catch (Exception ex)
{
CrestronConsole.PrintLine(string.Format("AddCertToStore Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace));
}
return bRet;
}
} }
} }

View File

@@ -1,7 +1,7 @@
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 /// Web API default request handler
/// </summary> /// </summary>
@@ -13,4 +13,5 @@ namespace PepperDash.Core.Web.RequestHandlers;
public DefaultRequestHandler() public DefaultRequestHandler()
: base(true) : base(true)
{ } { }
} }
}

View File

@@ -3,160 +3,161 @@ 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
{ {
private readonly Dictionary<string, Func<HttpCwsContext, Task>> _handlers; public abstract class WebApiBaseRequestAsyncHandler:IHttpCwsHandler
protected readonly bool EnableCors;
/// <summary>
/// Constructor
/// </summary>
protected WebApiBaseRequestAsyncHandler(bool enableCors)
{ {
EnableCors = enableCors; private readonly Dictionary<string, Func<HttpCwsContext, Task>> _handlers;
protected readonly bool EnableCors;
_handlers = new Dictionary<string, Func<HttpCwsContext, Task>> /// <summary>
/// Constructor
/// </summary>
protected WebApiBaseRequestAsyncHandler(bool enableCors)
{ {
{"CONNECT", HandleConnect}, EnableCors = enableCors;
{"DELETE", HandleDelete},
{"GET", HandleGet},
{"HEAD", HandleHead},
{"OPTIONS", HandleOptions},
{"PATCH", HandlePatch},
{"POST", HandlePost},
{"PUT", HandlePut},
{"TRACE", HandleTrace}
};
}
/// <summary> _handlers = new Dictionary<string, Func<HttpCwsContext, Task>>
/// Constructor {
/// </summary> {"CONNECT", HandleConnect},
protected WebApiBaseRequestAsyncHandler() {"DELETE", HandleDelete},
: this(false) {"GET", HandleGet},
{ {"HEAD", HandleHead},
} {"OPTIONS", HandleOptions},
{"PATCH", HandlePatch},
/// <summary> {"POST", HandlePost},
/// Handles CONNECT method requests {"PUT", HandlePut},
/// </summary> {"TRACE", HandleTrace}
/// <param name="context"></param> };
protected virtual async Task HandleConnect(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles DELETE method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleDelete(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles GET method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleGet(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles HEAD method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleHead(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles OPTIONS method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleOptions(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles PATCH method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandlePatch(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles POST method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandlePost(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles PUT method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandlePut(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles TRACE method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleTrace(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Process request
/// </summary>
/// <param name="context"></param>
public void ProcessRequest(HttpCwsContext context)
{
if (!_handlers.TryGetValue(context.Request.HttpMethod, out Func<HttpCwsContext, Task> handler))
{
return;
} }
if (EnableCors) /// <summary>
/// Constructor
/// </summary>
protected WebApiBaseRequestAsyncHandler()
: this(false)
{ {
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
context.Response.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
} }
var handlerTask = handler(context); /// <summary>
/// Handles CONNECT method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleConnect(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
handlerTask.GetAwaiter().GetResult(); /// <summary>
/// Handles DELETE method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleDelete(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles GET method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleGet(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles HEAD method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleHead(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles OPTIONS method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleOptions(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles PATCH method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandlePatch(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles POST method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandlePost(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles PUT method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandlePut(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Handles TRACE method requests
/// </summary>
/// <param name="context"></param>
protected virtual async Task HandleTrace(HttpCwsContext context)
{
context.Response.StatusCode = 501;
context.Response.StatusDescription = "Not Implemented";
context.Response.End();
}
/// <summary>
/// Process request
/// </summary>
/// <param name="context"></param>
public void ProcessRequest(HttpCwsContext context)
{
if (!_handlers.TryGetValue(context.Request.HttpMethod, out Func<HttpCwsContext, Task> handler))
{
return;
}
if (EnableCors)
{
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
context.Response.Headers.Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
}
var handlerTask = handler(context);
handlerTask.GetAwaiter().GetResult();
}
} }
} }

View File

@@ -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>
@@ -144,9 +144,6 @@ namespace PepperDash.Core.Web.RequestHandlers;
/// 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)
{ {
Action<HttpCwsContext> handler; Action<HttpCwsContext> handler;
@@ -164,4 +161,5 @@ namespace PepperDash.Core.Web.RequestHandlers;
handler(context); handler(context);
} }
} }
}

View File

@@ -1,252 +1,284 @@
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>
/// Web API server
/// </summary>
public class WebApiServer : IKeyName
{ {
private const string SplusKey = "Uninitialized Web API Server";
private const string DefaultName = "Web API Server";
private const string DefaultBasePath = "/api";
private readonly object _serverLock = new();
private HttpCwsServer _server;
/// <summary> /// <summary>
/// Gets or sets the Key /// Web API server
/// </summary> /// </summary>
public string Key { get; private set; } public class WebApiServer : IKeyName
/// <summary>
/// Gets or sets the Name
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets or sets the BasePath
/// </summary>
public string BasePath { get; private set; }
/// <summary>
/// Gets or sets the IsRegistered
/// </summary>
public bool IsRegistered { get; private set; }
/// <summary>
/// Constructor for S+. Make sure to set necessary properties using init method
/// </summary>
public WebApiServer()
: this(SplusKey, DefaultName, null)
{ {
} private const string SplusKey = "Uninitialized Web API Server";
private const string DefaultName = "Web API Server";
private const string DefaultBasePath = "/api";
/// <summary> private const uint DebugTrace = 0;
/// Constructor private const uint DebugInfo = 1;
/// </summary> private const uint DebugVerbose = 2;
/// <param name="key"></param>
/// <param name="basePath"></param>
public WebApiServer(string key, string basePath)
: this(key, DefaultName, basePath)
{
}
/// <summary> private readonly CCriticalSection _serverLock = new CCriticalSection();
/// Constructor private HttpCwsServer _server;
/// </summary>
/// <param name="key"></param>
/// <param name="name"></param>
/// <param name="basePath"></param>
public WebApiServer(string key, string name, string basePath)
{
Key = key;
Name = string.IsNullOrEmpty(name) ? DefaultName : name;
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
if (_server == null) _server = new HttpCwsServer(BasePath); /// <summary>
/// Web API server key
/// </summary>
public string Key { get; private set; }
_server.setProcessName(Key); /// <summary>
_server.HttpRequestHandler = new DefaultRequestHandler(); /// Web API server name
/// </summary>
public string Name { get; private set; }
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; /// <summary>
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler; /// CWS base path, will default to "/api" if not set via initialize method
} /// </summary>
public string BasePath { get; private set; }
/// <summary> /// <summary>
/// Program status event handler /// Indicates CWS is registered with base path
/// </summary> /// </summary>
/// <param name="programEventType"></param> public bool IsRegistered { get; private set; }
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType != eProgramStatusEventType.Stopping) return;
this.LogInformation("Program stopping. stopping server"); /// <summary>
/// Http request handler
/// </summary>
//public IHttpCwsHandler HttpRequestHandler
//{
// get { return _server.HttpRequestHandler; }
// set
// {
// if (_server == null) return;
// _server.HttpRequestHandler = value;
// }
//}
Stop(); /// <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>
/// Ethernet event handler /// Constructor for S+. Make sure to set necessary properties using init method
/// </summary> /// </summary>
/// <param name="ethernetEventArgs"></param> public WebApiServer()
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs) : this(SplusKey, DefaultName, null)
{
// Re-enable the server if the link comes back up and the status should be connected
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered)
{ {
this.LogInformation("Ethernet link up. Server is already registered.");
return;
} }
this.LogInformation("Ethernet link up. Starting server"); /// <summary>
/// Constructor
Start(); /// </summary>
} /// <param name="key"></param>
/// <param name="basePath"></param>
/// <summary> public WebApiServer(string key, string basePath)
/// Initialize method : this(key, DefaultName, basePath)
/// </summary>
public void Initialize(string key, string basePath)
{
Key = key;
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
}
/// <summary>
/// Adds a route to CWS
/// </summary>
public void AddRoute(HttpCwsRoute route)
{
if (route == null)
{ {
this.LogWarning("Failed to add route, route parameter is null");
return;
} }
_server.Routes.Add(route); /// <summary>
/// Constructor
} /// </summary>
/// <param name="key"></param>
/// <summary> /// <param name="name"></param>
/// Removes a route from CWS /// <param name="basePath"></param>
/// </summary> public WebApiServer(string key, string name, string basePath)
/// <param name="route"></param>
/// <summary>
/// RemoveRoute method
/// </summary>
public void RemoveRoute(HttpCwsRoute route)
{
if (route == null)
{ {
this.LogWarning("Failed to remove route, route parameter is null"); Key = key;
return; Name = string.IsNullOrEmpty(name) ? DefaultName : name;
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
if (_server == null) _server = new HttpCwsServer(BasePath);
_server.setProcessName(Key);
_server.HttpRequestHandler = new DefaultRequestHandler();
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
} }
_server.Routes.Remove(route); /// <summary>
} /// Program status event handler
/// </summary>
/// <param name="programEventType"></param>
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
{
if (programEventType != eProgramStatusEventType.Stopping) return;
/// <summary> Debug.Console(DebugInfo, this, "Program stopping. stopping server");
/// GetRouteCollection method
/// </summary>
public HttpCwsRouteCollection GetRouteCollection()
{
return _server.Routes;
}
/// <summary> Stop();
/// Starts CWS instance }
/// </summary>
public void Start() /// <summary>
{ /// Ethernet event handler
lock (_serverLock) /// </summary>
/// <param name="ethernetEventArgs"></param>
void CrestronEnvironment_EthernetEventHandler(EthernetEventArgs ethernetEventArgs)
{
// Re-enable the server if the link comes back up and the status should be connected
if (ethernetEventArgs.EthernetEventType == eEthernetEventType.LinkUp && IsRegistered)
{
Debug.Console(DebugInfo, this, "Ethernet link up. Server is alreedy registered.");
return;
}
Debug.Console(DebugInfo, this, "Ethernet link up. Starting server");
Start();
}
/// <summary>
/// Initializes CWS class
/// </summary>
public void Initialize(string key, string basePath)
{
Key = key;
BasePath = string.IsNullOrEmpty(basePath) ? DefaultBasePath : basePath;
}
/// <summary>
/// Adds a route to CWS
/// </summary>
public void AddRoute(HttpCwsRoute route)
{
if (route == null)
{
Debug.Console(DebugInfo, this, "Failed to add route, route parameter is null");
return;
}
_server.Routes.Add(route);
}
/// <summary>
/// Removes a route from CWS
/// </summary>
/// <param name="route"></param>
public void RemoveRoute(HttpCwsRoute route)
{
if (route == null)
{
Debug.Console(DebugInfo, this, "Failed to remote route, orute parameter is null");
return;
}
_server.Routes.Remove(route);
}
/// <summary>
/// Returns a list of the current routes
/// </summary>
public HttpCwsRouteCollection GetRouteCollection()
{
return _server.Routes;
}
/// <summary>
/// Starts CWS instance
/// </summary>
public void Start()
{ {
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);
} }
} // end lock finally
} {
_serverLock.Leave();
}
}
/// <summary> /// <summary>
/// Stop method /// Stop CWS instance
/// </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>
/// Received request handler
/// </summary>
/// <remarks>
/// This is here for development and testing
/// </remarks>
/// <param name="sender"></param>
/// <param name="args"></param>
public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args)
{
try
{
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
this.LogVerbose("RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
} }
catch (Exception ex)
/// <summary>
/// Received request handler
/// </summary>
/// <remarks>
/// This is here for development and testing
/// </remarks>
/// <param name="sender"></param>
/// <param name="args"></param>
public void ReceivedRequestEventHandler(object sender, HttpCwsRequestEventArgs args)
{ {
this.LogException(ex, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message); try
this.LogVerbose("ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace); {
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
Debug.Console(DebugVerbose, this, "RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
}
catch (Exception ex)
{
Debug.Console(DebugInfo, this, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
if (ex.InnerException != null)
Debug.Console(DebugVerbose, this, "ReceivedRequestEventHandler Exception InnerException: {0}", ex.InnerException);
}
} }
} }
} }

View File

@@ -1,45 +1,45 @@
using System; using System;
namespace PepperDash.Core.WebApi.Presets; namespace PepperDash.Core.WebApi.Presets
{
/// <summary> /// <summary>
/// Represents a preset /// Represents a preset
/// </summary> /// </summary>
public class Preset public class Preset
{ {
/// <summary> /// <summary>
/// ID of preset /// ID of preset
/// </summary> /// </summary>
public int Id { get; set; } public int Id { get; set; }
/// <summary> /// <summary>
/// User ID /// User ID
/// </summary> /// </summary>
public int UserId { get; set; } public int UserId { get; set; }
/// <summary> /// <summary>
/// Room Type ID /// Room Type ID
/// </summary> /// </summary>
public int RoomTypeId { get; set; } public int RoomTypeId { get; set; }
/// <summary> /// <summary>
/// Preset Name /// Preset Name
/// </summary> /// </summary>
public string PresetName { get; set; } public string PresetName { get; set; }
/// <summary> /// <summary>
/// Preset Number /// Preset Number
/// </summary> /// </summary>
public int PresetNumber { get; set; } public int PresetNumber { get; set; }
/// <summary> /// <summary>
/// Preset Data /// Preset Data
/// </summary> /// </summary>
public string Data { get; set; } public string Data { get; set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
public Preset() public Preset()
{ {
PresetName = ""; PresetName = "";
@@ -48,39 +48,40 @@ namespace PepperDash.Core.WebApi.Presets;
} }
} }
/// <summary> /// <summary>
/// Represents a PresetReceivedEventArgs ///
/// </summary> /// </summary>
public class PresetReceivedEventArgs : EventArgs public class PresetReceivedEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// True when the preset is found /// True when the preset is found
/// </summary> /// </summary>
public bool LookupSuccess { get; private set; } public bool LookupSuccess { get; private set; }
/// <summary> /// <summary>
/// S+ helper /// S+ helper
/// </summary> /// </summary>
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } } public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
/// <summary> /// <summary>
/// The preset /// The preset
/// </summary> /// </summary>
public Preset Preset { get; private set; } public Preset Preset { get; private set; }
/// <summary> /// <summary>
/// For Simpl+ /// For Simpl+
/// </summary> /// </summary>
public PresetReceivedEventArgs() { } public PresetReceivedEventArgs() { }
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
/// <param name="preset"></param> /// <param name="preset"></param>
/// <param name="success"></param> /// <param name="success"></param>
public PresetReceivedEventArgs(Preset preset, bool success) public PresetReceivedEventArgs(Preset preset, bool success)
{ {
LookupSuccess = success; LookupSuccess = success;
Preset = preset; Preset = preset;
} }
} }
}

Some files were not shown because too many files have changed in this diff Show More