mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-03-31 18:15:08 +00:00
Compare commits
20 Commits
dev/v3
...
v3.0.0-net
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90b6f258f0 | ||
|
|
e1e32cea6f | ||
|
|
e31df338d6 | ||
|
|
d14058fc32 | ||
|
|
04d6508c80 | ||
|
|
1cbc8194ec | ||
|
|
6d2cd75cbe | ||
|
|
8b873b7248 | ||
|
|
58a2a5c008 | ||
|
|
cc7e2ab675 | ||
|
|
dc900f3f31 | ||
|
|
562f0ba793 | ||
|
|
9c3c924a29 | ||
|
|
9b5af60a46 | ||
|
|
a99b0a1fac | ||
|
|
7591913a9c | ||
|
|
66a6612b65 | ||
|
|
688cf34153 | ||
|
|
0c59237232 | ||
|
|
88eec9a3f1 |
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"csharpier": {
|
||||
"version": "1.2.4",
|
||||
"commands": [
|
||||
"csharpier"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
56
.github/workflows/ci.yml
vendored
Normal file
56
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: CI Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop, net8-updates ]
|
||||
pull_request:
|
||||
branches: [ main, develop, net8-updates ]
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage
|
||||
|
||||
- name: Generate coverage report
|
||||
uses: danielpalme/ReportGenerator-GitHub-Action@5.2.0
|
||||
with:
|
||||
reports: coverage/**/coverage.cobertura.xml
|
||||
targetdir: coverage-report
|
||||
reporttypes: Html;Cobertura;MarkdownSummary
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage-report/Cobertura.xml
|
||||
flags: unittests
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Write coverage summary
|
||||
run: cat coverage-report/Summary.md >> $GITHUB_STEP_SUMMARY
|
||||
if: always()
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-results
|
||||
path: |
|
||||
coverage/
|
||||
coverage-report/
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -393,7 +393,4 @@ essentials-framework/Essentials Interfaces/PepperDash_Essentials_Interfaces/Pepp
|
||||
/._PepperDash.Essentials.sln
|
||||
.vscode/settings.json
|
||||
_site/
|
||||
api/
|
||||
*.DS_Store
|
||||
/._PepperDash.Essentials.4Series.sln
|
||||
dotnet
|
||||
api/
|
||||
9
.vscode/extensions.json
vendored
9
.vscode/extensions.json
vendored
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-dotnettools.vscode-dotnet-runtime",
|
||||
"ms-dotnettools.csharp",
|
||||
"ms-dotnettools.csdevkit",
|
||||
"vivaxy.vscode-conventional-commits",
|
||||
"mhutchie.git-graph"
|
||||
]
|
||||
}
|
||||
@@ -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)*
|
||||
|
||||
42
PepperDash.Essentials.Tests.sln
Normal file
42
PepperDash.Essentials.Tests.sln
Normal file
@@ -0,0 +1,42 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A8B8F24D-3181-45BF-9ED3-F734E04F0BC8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Core", "src\PepperDash.Core\PepperDash.Core.csproj", "{1E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Core", "src\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj", "{2E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{B8B8F24D-3181-45BF-9ED3-F734E04F0BC9}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Core.Tests", "tests\PepperDash.Essentials.Core.Tests\PepperDash.Essentials.Core.Tests.csproj", "{3E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{1E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7A} = {A8B8F24D-3181-45BF-9ED3-F734E04F0BC8}
|
||||
{2E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7B} = {A8B8F24D-3181-45BF-9ED3-F734E04F0BC8}
|
||||
{3E5D8C7C-A4D0-4D0E-A6B0-9E3F3D9C8B7C} = {B8B8F24D-3181-45BF-9ED3-F734E04F0BC9}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
166
TESTING_STRATEGY.md
Normal file
166
TESTING_STRATEGY.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# PepperDash Essentials Unit Testing Strategy
|
||||
|
||||
## Problem Statement
|
||||
The PepperDash Essentials framework is tightly coupled to Crestron hardware libraries that only run on Crestron devices, making it impossible to run unit tests on development machines or in CI/CD pipelines.
|
||||
|
||||
## Solution: Abstraction Layer Pattern
|
||||
|
||||
### 1. Core Abstractions Created
|
||||
We've implemented abstraction interfaces that decouple business logic from Crestron hardware:
|
||||
|
||||
- **`ICrestronControlSystem`** - Abstracts the control system hardware
|
||||
- **`IRelayPort`** - Abstracts relay functionality
|
||||
- **`IDigitalInput`** - Abstracts digital inputs with event handling
|
||||
- **`IVersiPort`** - Abstracts VersiPort I/O
|
||||
|
||||
### 2. Adapter Pattern Implementation
|
||||
Created adapter classes that wrap Crestron objects in production:
|
||||
|
||||
```csharp
|
||||
// Production code uses adapters
|
||||
var controlSystem = new CrestronControlSystemAdapter(Global.ControlSystem);
|
||||
var processor = new CrestronProcessorTestable("key", controlSystem);
|
||||
|
||||
// Test code uses mocks
|
||||
var mockControlSystem = new Mock<ICrestronControlSystem>();
|
||||
var processor = new CrestronProcessorTestable("key", mockControlSystem.Object);
|
||||
```
|
||||
|
||||
### 3. Testable Classes
|
||||
Refactored classes to accept abstractions via dependency injection:
|
||||
|
||||
- **`CrestronProcessorTestable`** - Accepts `ICrestronControlSystem`
|
||||
- **`GenericRelayDeviceTestable`** - Accepts `IRelayPort`
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Identify Dependencies
|
||||
```bash
|
||||
# Find Crestron dependencies
|
||||
grep -r "using Crestron" --include="*.cs"
|
||||
```
|
||||
|
||||
### Step 2: Create Abstractions
|
||||
Define interfaces that mirror the Crestron API surface you need:
|
||||
```csharp
|
||||
public interface IRelayPort
|
||||
{
|
||||
void Open();
|
||||
void Close();
|
||||
void Pulse(int delayMs);
|
||||
bool State { get; }
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Implement Adapters
|
||||
Wrap Crestron objects with adapters:
|
||||
```csharp
|
||||
public class RelayPortAdapter : IRelayPort
|
||||
{
|
||||
private readonly Relay _relay;
|
||||
public void Open() => _relay.Open();
|
||||
// ... other methods
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Refactor Classes
|
||||
Accept abstractions in constructors:
|
||||
```csharp
|
||||
public class CrestronProcessorTestable
|
||||
{
|
||||
public CrestronProcessorTestable(string key, ICrestronControlSystem processor)
|
||||
{
|
||||
// Use abstraction instead of concrete type
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Write Tests
|
||||
Use mocking frameworks to test business logic:
|
||||
```csharp
|
||||
[Fact]
|
||||
public void OpenRelay_CallsRelayPortOpen()
|
||||
{
|
||||
var mockRelay = new Mock<IRelayPort>();
|
||||
var device = new GenericRelayDeviceTestable("test", mockRelay.Object);
|
||||
|
||||
device.OpenRelay();
|
||||
|
||||
mockRelay.Verify(r => r.Open(), Times.Once);
|
||||
}
|
||||
```
|
||||
|
||||
## Test Project Structure
|
||||
```
|
||||
tests/
|
||||
├── PepperDash.Essentials.Core.Tests/
|
||||
│ ├── Abstractions/ # Tests for abstraction adapters
|
||||
│ ├── Devices/ # Device-specific tests
|
||||
│ └── *.csproj # Test project file
|
||||
└── README.md # Testing documentation
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Workflow
|
||||
The `.github/workflows/ci.yml` file runs tests automatically on:
|
||||
- Push to main/develop branches
|
||||
- Pull requests
|
||||
- Generates code coverage reports
|
||||
|
||||
### Running Tests Locally
|
||||
```bash
|
||||
# Run all tests
|
||||
dotnet test
|
||||
|
||||
# Run with coverage
|
||||
dotnet test --collect:"XPlat Code Coverage"
|
||||
|
||||
# Run specific tests
|
||||
dotnet test --filter "FullyQualifiedName~CrestronProcessor"
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Unit Testing Without Hardware** - Tests run on any machine
|
||||
2. **CI/CD Integration** - Automated testing in pipelines
|
||||
3. **Better Design** - Encourages SOLID principles
|
||||
4. **Faster Development** - No need for hardware to test logic
|
||||
5. **Higher Code Quality** - Catch bugs before deployment
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### For Existing Code
|
||||
1. Identify classes with Crestron dependencies
|
||||
2. Create abstraction interfaces
|
||||
3. Implement adapters
|
||||
4. Create testable versions accepting abstractions
|
||||
5. Write unit tests
|
||||
|
||||
### For New Code
|
||||
1. Always code against abstractions, not Crestron types
|
||||
2. Use dependency injection
|
||||
3. Write tests first (TDD approach)
|
||||
|
||||
## Current Test Coverage
|
||||
- ✅ CrestronProcessor relay management
|
||||
- ✅ GenericRelayDevice operations
|
||||
- ✅ Digital input event handling
|
||||
- ✅ VersiPort analog/digital operations
|
||||
|
||||
## Next Steps
|
||||
1. Expand abstractions for more Crestron components
|
||||
2. Increase test coverage across all modules
|
||||
3. Add integration tests with mock hardware
|
||||
4. Document testing best practices
|
||||
5. Create code generation tools for adapters
|
||||
|
||||
## Tools Used
|
||||
- **xUnit** - Test framework
|
||||
- **Moq** - Mocking library
|
||||
- **FluentAssertions** - Readable assertions
|
||||
- **Coverlet** - Code coverage
|
||||
- **GitHub Actions** - CI/CD
|
||||
|
||||
## Summary
|
||||
By introducing an abstraction layer between the business logic and Crestron hardware dependencies, we've successfully enabled unit testing for the PepperDash Essentials framework. This approach allows development and testing without physical hardware while maintaining full compatibility with Crestron systems in production.
|
||||
@@ -52,7 +52,6 @@
|
||||
"_appLogoPath": "docs/images/favicon-32x32.png",
|
||||
"_appFaviconPath": "docs/images/favicon.ico",
|
||||
"_disableToc": false,
|
||||
"_enableNewTab": true,
|
||||
"pdf": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ Types of things in `DeviceManager`:
|
||||
|
||||
A Device doesn't always represent a physical piece of hardware, but rather a logical construct that "does something" and is used by one or more other devices in the running program. For example, we create a room device, and its corresponding Fusion device, and that room has a Cisco codec device, with an attached SSh client device. All of these lie in a flat collection in the `DeviceManager`.
|
||||
|
||||
> 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.
|
||||
|
||||
@@ -38,4 +38,4 @@ This flat structure ensures that every device in a system exists in one place an
|
||||
|
||||

|
||||
|
||||
Next: [Configurable lifecycle](~/docs/technical-docs/Arch-lifecycle.md)
|
||||
Next: [Configurable lifecycle](~/docs/Arch-lifecycle.md)
|
||||
@@ -105,7 +105,7 @@ Each of the three activation phases operates in a try/catch block for each devic
|
||||
|
||||
In any real-world system, devices and business logic need to talk to each other, otherwise, what's the point of all this coding? When creating your classes and configuration, it is best practice to _try_ not to "plug" one device into another during construction or activation. For example your touchpanel controller class has a `Display1` property that holds the display-1 object. Rather, it may be better to refer to the device as it is stored in the `DeviceManager` when it's needed using the static `DeviceManager.GetDeviceForKey(key)` method to get a reference to the device, which can be cast using various interfaces/class types, and then interacted with. This prevents objects from being referenced in places where the developer may later forget to dereference them, causing memory leak. This will become more important as Essentials becomes more able to be reconfigured at runtime.
|
||||
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Next: [More architecture](~/docs/technical-docs/Arch-topics.md)
|
||||
Next: [More architecture](~/docs/Arch-topics.md)
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
The diagram below describes how Essentials gets a program up and running.
|
||||
|
||||
(The various activation phases are covered in more detail on the [next page](~/docs/technical-docs/Arch-activate.md))
|
||||
(The various activation phases are covered in more detail on the [next page](~/docs/Arch-activate.md))
|
||||
|
||||

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

|
||||
|
||||
Next: [Architecture](~/docs/technical-docs/Arch-1.md)
|
||||
Next: [Architecture](~/docs/Arch-1.md)
|
||||
@@ -6,10 +6,10 @@ One of the most powerful features of Essentials is the ability to bridge SIMPL t
|
||||
|
||||
Follow the links below for examples of bridging to hardware and network resources.
|
||||
|
||||
**[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)**
|
||||
@@ -4,44 +4,23 @@
|
||||
[YouTube Video - Getting Started with PepperDash Essentials](https://youtu.be/FxEZtbpCwiQ)
|
||||
***
|
||||
|
||||
## Get a CPZ
|
||||
## Download or clone
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* [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)
|
||||
You may clone Essentials at <https://github.com/PepperDash/Essentials.git>
|
||||
|
||||
## 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
|
||||
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.
|
||||
This section assumes knowledge of loading programs to and working with the file system on a Crestron processor.
|
||||
|
||||
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```
|
||||
* 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)
|
||||
Next: [Standalone use](~/docs/Standalone-Use.md)
|
||||
|
||||
@@ -39,7 +39,7 @@ Thanks!
|
||||
|
||||
## Collaboration
|
||||
|
||||
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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?
|
||||
|
||||
|
||||
@@ -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. 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Next: [Essentials architecture](~/docs/technical-docs/Arch-summary.md)
|
||||
Next: [Essentials architecture](~/docs/Arch-summary.md)
|
||||
|
||||
@@ -286,7 +286,7 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
|
||||
|
||||
3. A bridge need not only bridge between applications on the same processor. A bridge may bridge to an application on a completely separate processor; simply define the ip address in the Bridge control properties accordingly.
|
||||
|
||||
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.
|
||||
|
||||
@@ -302,7 +302,7 @@ Example device config: <https://github.com/PepperDash/Essentials/blob/main/Peppe
|
||||
|
||||
## Join Map Documentation
|
||||
|
||||
[Join Map Documentation](~/docs/usage/JoinMaps.md)
|
||||
[Join Map Documentation](~/docs/JoinMaps.md)
|
||||
|
||||
## 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.
|
||||
|
||||
Next: [Essentials architecture](~/docs/technical-docs/Arch-summary.md)
|
||||
Next: [Essentials architecture](~/docs/Arch-summary.md)
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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#.
|
||||
|
||||
@@ -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. 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Next: [Essentials architecture](~/docs/technical-docs/Arch-summary.md)
|
||||
Next: [Essentials architecture](~/docs/Arch-summary.md)
|
||||
|
||||
@@ -8,7 +8,7 @@ By defining devices and a room in a JSON configuration file, Essentials can cont
|
||||
|
||||
### Devices
|
||||
|
||||
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
|
||||
|
||||
@@ -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]]
|
||||
|
||||
Next: [Simpl Windows bridging](~/docs/usage/SIMPL-Bridging-Updated.md)
|
||||
Next: [Simpl Windows bridging](~/docs/SIMPL-Bridging-Updated.md)
|
||||
@@ -1,148 +0,0 @@
|
||||
# How to Add Documentation to Essentials
|
||||
|
||||
This guide explains how to add new documentation articles to the Essentials docFx site.
|
||||
|
||||
## Overview
|
||||
|
||||
The Essentials documentation uses [docFx](https://dotnet.github.io/docfx/) to generate a static documentation website. Documentation files are organized in a hierarchical structure with a table of contents (TOC) file that defines the site navigation. Documentation should be organized and written to fit into the [Diátaxis](https://diataxis.fr/start-here/) conceptual framework.
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
Documentation files are located in `/docs/docs/` and organized into the following subdirectories:
|
||||
|
||||
- **how-to/** - Step-by-step guides and tutorials
|
||||
- **usage/** - Usage documentation for SIMPL bridging, standalone use, and hardware integration
|
||||
- **technical-docs/** - Technical documentation including architecture, plugins, and API references
|
||||
- **images/** - Image assets used in documentation
|
||||
|
||||
## Adding a New Document
|
||||
|
||||
### Step 1: Create Your Markdown File
|
||||
|
||||
1. Determine which category your document belongs to (how-to, usage, or technical-docs)
|
||||
2. Create a new `.md` file in the appropriate subdirectory
|
||||
3. Use a descriptive filename with hyphens (e.g., `my-new-feature.md`)
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# For a how-to guide
|
||||
touch /docs/docs/how-to/configure-audio-settings.md
|
||||
|
||||
# For usage documentation
|
||||
touch /docs/docs/usage/video-switcher-control.md
|
||||
|
||||
# For technical documentation
|
||||
touch /docs/docs/technical-docs/custom-device-plugin.md
|
||||
```
|
||||
|
||||
### Step 2: Write Your Content
|
||||
|
||||
Start your markdown file with a level 1 heading (`#`) that serves as the page title:
|
||||
|
||||
```markdown
|
||||
# Your Document Title
|
||||
|
||||
Brief introduction to the topic.
|
||||
|
||||
## Section Heading
|
||||
|
||||
Content goes here...
|
||||
|
||||
### Subsection
|
||||
|
||||
More detailed content...
|
||||
```
|
||||
|
||||
**Markdown Features:**
|
||||
- Use standard markdown syntax
|
||||
- Include code blocks with language specifiers (```csharp, ```json, etc.)
|
||||
- Add images: ``
|
||||
- Link to other docs: `[Link text](../usage/related-doc.md)`
|
||||
|
||||
### Step 3: Add to Table of Contents
|
||||
|
||||
Edit `/docs/docs/toc.yml` to add your new document to the navigation:
|
||||
|
||||
```yaml
|
||||
- name: How-to's
|
||||
items:
|
||||
- href: how-to/how-to-add-docs.md
|
||||
- href: how-to/your-new-doc.md # Add your document here
|
||||
```
|
||||
|
||||
**TOC Structure:**
|
||||
- `name:` - Display name in the navigation menu
|
||||
- `href:` - Relative path to the markdown file
|
||||
- `items:` - Nested items for hierarchical navigation
|
||||
|
||||
**Example with nested items:**
|
||||
```yaml
|
||||
- name: Usage
|
||||
items:
|
||||
- name: SIMPL Bridging
|
||||
href: usage/SIMPL-Bridging-Updated.md
|
||||
items:
|
||||
- name: Your Sub-Topic
|
||||
href: usage/your-sub-topic.md
|
||||
```
|
||||
|
||||
### Step 4: Test Locally
|
||||
|
||||
Build and preview the docFx site locally to verify your changes:
|
||||
|
||||
```bash
|
||||
# Navigate to the docs directory
|
||||
cd docs
|
||||
|
||||
# Build the documentation
|
||||
docfx build
|
||||
|
||||
# Serve the site locally
|
||||
docfx serve _site
|
||||
```
|
||||
|
||||
Then open your browser to `http://localhost:8080` to view the site.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### File Naming
|
||||
- Use lowercase with hyphens: `my-document-name.md`
|
||||
- Be descriptive but concise
|
||||
- Avoid special characters
|
||||
|
||||
### Content Guidelines
|
||||
- Start with a clear introduction
|
||||
- Use hierarchical headings (H1 → H2 → H3)
|
||||
- Include code examples where appropriate
|
||||
- Add images to illustrate complex concepts
|
||||
- Link to related documentation
|
||||
|
||||
### TOC Organization
|
||||
- Group related documents under the same parent
|
||||
- Order items logically (basic → advanced)
|
||||
- Keep the TOC hierarchy shallow (2-3 levels max)
|
||||
- Use clear, descriptive names for navigation items
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Document Not Appearing
|
||||
- Verify the file path in `toc.yml` is correct and uses forward slashes
|
||||
- Ensure the markdown file exists in the specified location
|
||||
- Check for YAML syntax errors in `toc.yml`
|
||||
|
||||
### Images Not Loading
|
||||
- Verify image path is relative to the markdown file location
|
||||
- Use `../images/` for files in the images directory
|
||||
- Ensure image files are committed to the repository
|
||||
|
||||
### Broken Links
|
||||
- Use relative paths for internal links
|
||||
- Test all links after building the site
|
||||
- Use `.md` extension when linking to other documentation files
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [docFx Documentation](https://dotnet.github.io/docfx/)
|
||||
- [Markdown Guide](https://www.markdownguide.org/)
|
||||
- [YAML Syntax](https://yaml.org/spec/1.2/spec.html)
|
||||
- [Diátaxis](https://diataxis.fr/start-here/)
|
||||
@@ -1,52 +1,48 @@
|
||||
- name: Get Started With Essentials
|
||||
- href: ../index.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
|
||||
- href: Get-started.md
|
||||
- name: Usage
|
||||
items:
|
||||
- href: usage/Standalone-Use.md
|
||||
- href: usage/SIMPL-Bridging-Updated.md
|
||||
- href: Standalone-Use.md
|
||||
- href: SIMPL-Bridging-Updated.md
|
||||
items:
|
||||
- name: Join Maps
|
||||
href: usage/JoinMaps.md
|
||||
href: JoinMaps.md
|
||||
- name: Bridging to Hardware Resources
|
||||
href: usage/Bridging-To-Hardware-Resources.md
|
||||
href: Bridging-To-Hardware-Resources.md
|
||||
items:
|
||||
- name: GenericComm Bridging
|
||||
href: usage/GenericComm.md
|
||||
href: GenericComm.md
|
||||
- name: RelayOutput Bridging
|
||||
href: usage/RelayOutput.md
|
||||
href: RelayOutput.md
|
||||
- name: Digital Input Bridging
|
||||
href: usage/DigitalInput.md
|
||||
href: DigitalInput.md
|
||||
- name: IR Driver Bridging
|
||||
href: usage/IR-Driver-Bridging.md
|
||||
href: IR-Driver-Bridging.md
|
||||
- name: Technical documentation
|
||||
items:
|
||||
- href: technical-docs/Arch-summary.md
|
||||
- href: Arch-summary.md
|
||||
- name: Devices and DeviceManager
|
||||
href: technical-docs/Arch-1.md
|
||||
href: Arch-1.md
|
||||
- name: Configurable lifecycle
|
||||
href: technical-docs/Arch-lifecycle.md
|
||||
href: Arch-lifecycle.md
|
||||
- name: Activation phases
|
||||
href: technical-docs/Arch-activate.md
|
||||
href: Arch-activate.md
|
||||
- name: More
|
||||
href: technical-docs/Arch-topics.md
|
||||
href: Arch-topics.md
|
||||
- name: Plugins
|
||||
href: technical-docs/Plugins.md
|
||||
href: Plugins.md
|
||||
- name: Communication Basics
|
||||
href: technical-docs/Communication-Basics.md
|
||||
href: Communication-Basics.md
|
||||
- name: Debugging
|
||||
href: technical-docs/Debugging.md
|
||||
href: Debugging.md
|
||||
- name: Feedback Classes
|
||||
href: technical-docs/Feedback-Classes.md
|
||||
href: Feedback-Classes.md
|
||||
- name: Connection Based Routing
|
||||
href: technical-docs/Connection-Based-Routing.md
|
||||
href: Connection-Based-Routing.md
|
||||
- name: Configuration Structure
|
||||
href: technical-docs/ConfigurationStructure.md
|
||||
href: ConfigurationStructure.md
|
||||
- name: Supported Devices
|
||||
href: technical-docs/Supported-Devices.md
|
||||
href: Supported-Devices.md
|
||||
- name: Glossary of Terms
|
||||
href: technical-docs/Glossary-of-Terms.md
|
||||
href: Glossary-of-Terms.md
|
||||
@@ -8,12 +8,12 @@ Essentials is a collection of C# libraries that can be used in many ways. It is
|
||||
|
||||
## Get started
|
||||
|
||||
- [Download an Essentials build or clone the repo](~/docs/Get-started.md)
|
||||
- [Get started](~/docs/Get-started.md)
|
||||
- [Download essentials build or clone repo](~/docs/Get-started.md)
|
||||
- [How to get started](~/docs/Get-started.md)
|
||||
- [YouTube Video Series Playlist](https://youtube.com/playlist?list=PLKOoNNwgPFZdV5wDEBDZxTHu1KROspaBu)
|
||||
- [Discord Server](https://discord.gg/6Vh3ssDdPs)
|
||||
|
||||
Or use the links to the 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
|
||||
- More flexibility with less code
|
||||
- Configurable using simple JSON files
|
||||
- Is awesome
|
||||
|
||||
---
|
||||
|
||||
## Comment
|
||||
|
||||
The Essentials wiki is clearly in-progress right now. Take a look at the links to the right. We are actively working on this documentation, so please be patient with us. If you have any comments on or suggestions for the documentation, please file an issue here, with as much detail as you can provide: <https://github.com/PepperDash/Essentials/issues>
|
||||
|
||||
Thanks!
|
||||
|
||||
---
|
||||
|
||||
## Collaboration
|
||||
|
||||
Essentials is an open-source project and we encourage collaboration on this community project. For features that may not be useful to the greater community, or for just-plain learning, we want to remind developers to try writing plugins for Essentials. More information can be found here: [Plugins](~/docs/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
|
||||
|
||||
@@ -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`
|
||||
- When working on a new feature or bugfix, branch from the `development` branch. When working on a hotfix, branch from `main`.
|
||||
3. Make commits as necessary (often is better). And use concise, descriptive language, leveraging issue notation and/or [Closing Keywords](https://help.github.com/articles/closing-issues-using-keywords) to ensure any issues addressed by your work are referenced accordingly.
|
||||
4. When the scope of the work for your branch is complete, make sure to 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.
|
||||
6. Your Pull Request will be reviewed by our team and evaluated for inclusion into the main repository.
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<Project>
|
||||
<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>
|
||||
<PackagePath>build;</PackagePath>
|
||||
</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>
|
||||
<PackagePath>build;</PackagePath>
|
||||
</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>
|
||||
<PackagePath>build;</PackagePath>
|
||||
</None>
|
||||
@@ -23,32 +23,23 @@
|
||||
<FileName>$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz</FileName>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="DeleteCLZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'Library' And $(TargetDir) != ''">
|
||||
<ItemGroup>
|
||||
<OldCLZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).clz" />
|
||||
</ItemGroup>
|
||||
<Delete Files="@(OldCLZFiles)" Condition="@(OldCLZFiles) != ''">
|
||||
<Target Name="DeleteCLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Library' And $(TargetDir) != '' And Exists($(FileName))">
|
||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).clz">
|
||||
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
|
||||
</Delete>
|
||||
<Message Text="Deleted old CLZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" />
|
||||
<Message Text="Deleted files: '@(DeletedList)'" />
|
||||
</Target>
|
||||
<Target Name="DeleteCPZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'Program' And $(TargetDir) != ''">
|
||||
<ItemGroup>
|
||||
<OldCPZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).cpz" />
|
||||
</ItemGroup>
|
||||
<Delete Files="@(OldCPZFiles)" Condition="@(OldCPZFiles) != ''">
|
||||
<Target Name="DeleteCPZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'Program' And $(TargetDir) != '' And Exists($(FileName))">
|
||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cpz">
|
||||
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
|
||||
</Delete>
|
||||
<Message Text="Deleted old CPZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" />
|
||||
<Message Text="Deleted files: '@(DeletedList)'" />
|
||||
</Target>
|
||||
<Target Name="DeleteCPLZ" BeforeTargets="CoreBuild" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''">
|
||||
<ItemGroup>
|
||||
<OldCPLZFiles Include="$(TargetDir)$(TargetName).*.$(TargetFramework).cplz" />
|
||||
</ItemGroup>
|
||||
<Delete Files="@(OldCPLZFiles)" Condition="@(OldCPLZFiles) != ''">
|
||||
<Target Name="DeleteCPLZ" BeforeTargets="PreBuildEvent" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != '' And Exists($(FileName))">
|
||||
<Delete Files="$(TargetDir)$(TargetName).$(Version).$(TargetFramework).cplz">
|
||||
<Output TaskParameter="DeletedFiles" ItemName="DeletedList"/>
|
||||
</Delete>
|
||||
<Message Text="Deleted old CPLZ files: '@(DeletedList)'" Condition="@(DeletedList) != ''" />
|
||||
<Message Text="Deleted files: '@(DeletedList)'" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CreateCPLZ" AfterTargets="Build" Condition="$(ProjectType) == 'ProgramLibrary' And $(TargetDir) != ''" DependsOnTargets="DeleteCPLZ">
|
||||
|
||||
@@ -84,9 +84,10 @@ namespace PepperDash.Core;
|
||||
port.TextReceived += Port_TextReceivedStringDelimiter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop method
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Disconnects this gather from the Port's TextReceived event. This will not fire LineReceived
|
||||
/// after the this call.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
Port.TextReceived -= Port_TextReceived;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Core;
|
||||
|
||||
namespace PepperDash.Core;
|
||||
@@ -20,7 +20,7 @@ public class CommunicationStreamDebugging
|
||||
/// <summary>
|
||||
/// Timer to disable automatically if not manually disabled
|
||||
/// </summary>
|
||||
private Timer DebugExpiryPeriod;
|
||||
private CTimer DebugExpiryPeriod;
|
||||
|
||||
/// <summary>
|
||||
/// The current debug setting
|
||||
@@ -93,9 +93,7 @@ public class CommunicationStreamDebugging
|
||||
|
||||
StopDebugTimer();
|
||||
|
||||
DebugExpiryPeriod = new Timer(_DebugTimeoutInMs) { AutoReset = false };
|
||||
DebugExpiryPeriod.Elapsed += (s, e) => DisableDebugging();
|
||||
DebugExpiryPeriod.Start();
|
||||
DebugExpiryPeriod = new CTimer((o) => DisableDebugging(), _DebugTimeoutInMs);
|
||||
|
||||
if ((setting & eStreamDebuggingSetting.Rx) == eStreamDebuggingSetting.Rx)
|
||||
RxStreamDebuggingIsEnabled = true;
|
||||
@@ -157,3 +155,22 @@ public enum eStreamDebuggingSetting
|
||||
Both = Rx | Tx
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The available settings for stream debugging response types
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum eStreamDebuggingDataTypeSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug data in byte format
|
||||
/// </summary>
|
||||
Bytes = 0,
|
||||
/// <summary>
|
||||
/// Debug data in text format
|
||||
/// </summary>
|
||||
Text = 1,
|
||||
/// <summary>
|
||||
/// Debug data in both byte and text formats
|
||||
/// </summary>
|
||||
Both = Bytes | Text,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using Crestron.SimplSharp;
|
||||
using JsonConverter = NewtonsoftJson::Newtonsoft.Json.JsonConverterAttribute;
|
||||
using JsonIgnore = NewtonsoftJson::Newtonsoft.Json.JsonIgnoreAttribute;
|
||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||
using NullValueHandling = NewtonsoftJson::Newtonsoft.Json.NullValueHandling;
|
||||
using StringEnumConverter = NewtonsoftJson::Newtonsoft.Json.Converters.StringEnumConverter;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace PepperDash.Core;
|
||||
|
||||
|
||||
@@ -27,25 +27,25 @@ public delegate void GenericSocketStatusChangeEventDelegate(ISocketStatus client
|
||||
/// <summary>
|
||||
/// EventArgs class for socket status changes
|
||||
/// </summary>
|
||||
public class GenericSocketStatusChageEventArgs : EventArgs
|
||||
{
|
||||
public class GenericSocketStatusChageEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Client
|
||||
///
|
||||
/// </summary>
|
||||
public ISocketStatus Client { get; private set; }
|
||||
public ISocketStatus Client { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
public GenericSocketStatusChageEventArgs(ISocketStatus client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericSocketStatusChageEventArgs() { }
|
||||
public GenericSocketStatusChageEventArgs(ISocketStatus client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericSocketStatusChageEventArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,22 +60,22 @@ public delegate void GenericTcpServerStateChangedEventDelegate(ServerState state
|
||||
public class GenericTcpServerStateChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the State
|
||||
///
|
||||
/// </summary>
|
||||
public ServerState State { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
public GenericTcpServerStateChangedEventArgs(ServerState state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerStateChangedEventArgs() { }
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerStateChangedEventArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -91,22 +91,20 @@ public delegate void GenericTcpServerSocketStatusChangeEventDelegate(object sock
|
||||
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>
|
||||
@@ -117,7 +115,7 @@ public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="socket"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
@@ -128,10 +126,10 @@ public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
||||
ReceivedFromClientIndex = clientIndex;
|
||||
ClientStatus = clientStatus;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerSocketStatusChangeEventArgs() { }
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerSocketStatusChangeEventArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,28 +138,28 @@ public class GenericTcpServerSocketStatusChangeEventArgs : EventArgs
|
||||
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;
|
||||
}
|
||||
}
|
||||
public ushort ReceivedFromClientIndexShort
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ushort)ReceivedFromClientIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Text
|
||||
///
|
||||
/// </summary>
|
||||
public string Text { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs(string text)
|
||||
@@ -170,7 +168,7 @@ public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="clientIndex"></param>
|
||||
@@ -179,10 +177,10 @@ public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
||||
Text = text;
|
||||
ReceivedFromClientIndex = clientIndex;
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs() { }
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerCommMethodReceiveTextArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -191,23 +189,22 @@ public class GenericTcpServerCommMethodReceiveTextArgs : EventArgs
|
||||
public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets IsReady
|
||||
///
|
||||
/// </summary>
|
||||
public bool IsReady;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="isReady"></param>
|
||||
public GenericTcpServerClientReadyForcommunicationsEventArgs(bool isReady)
|
||||
{
|
||||
IsReady = isReady;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericTcpServerClientReadyForcommunicationsEventArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -216,12 +213,11 @@ public class GenericTcpServerClientReadyForcommunicationsEventArgs : EventArgs
|
||||
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;
|
||||
|
||||
@@ -231,7 +227,7 @@ public class GenericUdpConnectedEventArgs : EventArgs
|
||||
public GenericUdpConnectedEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="uconnected"></param>
|
||||
public GenericUdpConnectedEventArgs(ushort uconnected)
|
||||
@@ -240,7 +236,7 @@ public class GenericUdpConnectedEventArgs : EventArgs
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="connected"></param>
|
||||
public GenericUdpConnectedEventArgs(bool connected)
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using PepperDash.Core.Logging;
|
||||
@@ -182,7 +181,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
|
||||
// private Timer for auto reconnect
|
||||
private Timer RetryTimer;
|
||||
private CTimer RetryTimer;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -265,12 +264,12 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
/// </summary>
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
|
||||
|
||||
Timer HeartbeatSendTimer;
|
||||
Timer HeartbeatAckTimer;
|
||||
CTimer HeartbeatSendTimer;
|
||||
CTimer HeartbeatAckTimer;
|
||||
|
||||
// Used to force disconnection on a dead connect attempt
|
||||
Timer ConnectFailTimer;
|
||||
Timer WaitForSharedKey;
|
||||
CTimer ConnectFailTimer;
|
||||
CTimer WaitForSharedKey;
|
||||
private int ConnectionCount;
|
||||
|
||||
bool ProgramIsStopping;
|
||||
@@ -278,7 +277,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
/// <summary>
|
||||
/// Queue lock
|
||||
/// </summary>
|
||||
private readonly object _dequeueLock = new();
|
||||
CCriticalSection DequeueLock = new CCriticalSection();
|
||||
|
||||
/// <summary>
|
||||
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
||||
@@ -358,7 +357,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
this.LogWarning( "Could not initialize client with key: {0}", Key);
|
||||
Debug.Console(0, this, "Could not initialize client with key: {0}", Key);
|
||||
return;
|
||||
}
|
||||
try
|
||||
@@ -398,7 +397,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("Exception initializing client with key: {0}\rException: {1}", Key, ex);
|
||||
Debug.Console(0, this, "Exception initializing client with key: {0}\rException: {1}", Key, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +410,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
||||
{
|
||||
this.LogInformation("Program stopping. Closing _client connection");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing _client connection");
|
||||
ProgramIsStopping = true;
|
||||
Disconnect();
|
||||
}
|
||||
@@ -438,17 +437,17 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
public void Connect()
|
||||
{
|
||||
ConnectionCount++;
|
||||
this.LogVerbose("Attempting connect Count:{0}", ConnectionCount);
|
||||
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
|
||||
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
this.LogInformation("Already connected. Ignoring.");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
||||
return;
|
||||
}
|
||||
if (IsTryingToConnect)
|
||||
{
|
||||
this.LogInformation("Already trying to connect. Ignoring.");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
||||
return;
|
||||
}
|
||||
try
|
||||
@@ -461,17 +460,17 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
if (string.IsNullOrEmpty(Hostname))
|
||||
{
|
||||
this.LogWarning("DynamicTcpClient: No address set");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
||||
return;
|
||||
}
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
this.LogWarning("DynamicTcpClient: Invalid port");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||
{
|
||||
this.LogWarning("DynamicTcpClient: No Shared Key set");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -492,10 +491,9 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
|
||||
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||
|
||||
ConnectFailTimer = new Timer(30000) { AutoReset = false };
|
||||
ConnectFailTimer.Elapsed += (s, e) =>
|
||||
ConnectFailTimer = new CTimer(o =>
|
||||
{
|
||||
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||
if (IsTryingToConnect)
|
||||
{
|
||||
IsTryingToConnect = false;
|
||||
@@ -507,13 +505,12 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
//SecureClient.DisconnectFromServer();
|
||||
//CheckClosedAndTryReconnect();
|
||||
}
|
||||
};
|
||||
ConnectFailTimer.Start();
|
||||
}, 30000);
|
||||
|
||||
this.LogVerbose("Making Connection Count:{0}", ConnectionCount);
|
||||
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
|
||||
_client.ConnectToServerAsync(o =>
|
||||
{
|
||||
this.LogVerbose("ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||
|
||||
if (ConnectFailTimer != null)
|
||||
{
|
||||
@@ -523,22 +520,22 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
|
||||
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
this.LogVerbose("_client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||
Debug.Console(2, this, "_client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||
o.ReceiveDataAsync(Receive);
|
||||
|
||||
if (SharedKeyRequired)
|
||||
{
|
||||
WaitingForSharedKeyResponse = true;
|
||||
WaitForSharedKey = new Timer(15000) { AutoReset = false };
|
||||
WaitForSharedKey.Elapsed += (s, e) =>
|
||||
WaitForSharedKey = new CTimer(timer =>
|
||||
{
|
||||
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
||||
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
||||
o.DisconnectFromServer();
|
||||
//CheckClosedAndTryReconnect();
|
||||
//OnClientReadyForcommunications(false); // Should send false event
|
||||
};
|
||||
WaitForSharedKey.Start();
|
||||
}, 15000);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -552,14 +549,14 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogWarning("Connect attempt failed {0}", o.ClientStatus);
|
||||
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
||||
CheckClosedAndTryReconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("_client connection exception: {0}", ex.Message);
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "_client connection exception: {0}", ex.Message);
|
||||
IsTryingToConnect = false;
|
||||
CheckClosedAndTryReconnect();
|
||||
}
|
||||
@@ -595,14 +592,14 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
{
|
||||
if (_client == null) return;
|
||||
|
||||
this.LogInformation("Disconnecting client");
|
||||
Debug.Console(1, this, "Disconnecting client");
|
||||
if (IsConnected)
|
||||
_client.DisconnectFromServer();
|
||||
|
||||
// close up client. ALWAYS use this when disconnecting.
|
||||
IsTryingToConnect = false;
|
||||
|
||||
this.LogVerbose("Disconnecting _client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||
Debug.Console(2, this, "Disconnecting _client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||
_client.SocketStatusChange -= Client_SocketStatusChange;
|
||||
_client.Dispose();
|
||||
_client = null;
|
||||
@@ -623,14 +620,14 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
{
|
||||
if (_client != null)
|
||||
{
|
||||
this.LogVerbose("Cleaning up remotely closed/failed connection.");
|
||||
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
|
||||
Disconnect();
|
||||
}
|
||||
if (!DisconnectCalledByUser && AutoReconnect)
|
||||
{
|
||||
var halfInterval = AutoReconnectIntervalMs / 2;
|
||||
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
||||
this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime);
|
||||
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
|
||||
if (RetryTimer != null)
|
||||
{
|
||||
RetryTimer.Stop();
|
||||
@@ -638,9 +635,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
if (AutoReconnectTriggered != null)
|
||||
AutoReconnectTriggered(this, new EventArgs());
|
||||
RetryTimer = new Timer(rndTime) { AutoReset = false };
|
||||
RetryTimer.Elapsed += (s, e) => Connect();
|
||||
RetryTimer.Start();
|
||||
RetryTimer = new CTimer(o => Connect(), rndTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,13 +654,13 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
{
|
||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
this.LogVerbose("_client Received:\r--------\r{0}\r--------", str);
|
||||
Debug.Console(2, this, "_client Received:\r--------\r{0}\r--------", str);
|
||||
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
||||
{
|
||||
|
||||
if (SharedKeyRequired && str == "SharedKey:")
|
||||
{
|
||||
this.LogVerbose("Server asking for shared key, sending");
|
||||
Debug.Console(2, this, "Server asking for shared key, sending");
|
||||
SendText(SharedKey + "\n");
|
||||
}
|
||||
else if (SharedKeyRequired && str == "Shared Key Match")
|
||||
@@ -673,7 +668,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
StopWaitForSharedKeyTimer();
|
||||
|
||||
|
||||
this.LogVerbose("Shared key confirmed. Ready for communication");
|
||||
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
|
||||
OnClientReadyForcommunications(true); // Successful key exchange
|
||||
}
|
||||
else
|
||||
@@ -693,7 +688,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("Error receiving data: {1}. Error: {0}", str, ex.Message);
|
||||
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
||||
}
|
||||
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
client.ReceiveDataAsync(Receive);
|
||||
@@ -701,18 +696,19 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
||||
if (handler != null)
|
||||
{
|
||||
if (System.Threading.Monitor.TryEnter(_dequeueLock))
|
||||
System.Threading.Tasks.Task.Run(() => DequeueEvent());
|
||||
var gotLock = DequeueLock.TryEnter();
|
||||
if (gotLock)
|
||||
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
|
||||
}
|
||||
}
|
||||
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
||||
}
|
||||
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
||||
{
|
||||
client.DisconnectFromServer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
|
||||
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
||||
/// It will dequeue items as they are enqueued automatically.
|
||||
/// </summary>
|
||||
void DequeueEvent()
|
||||
@@ -732,10 +728,13 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogError(e, "DequeueEvent error: {0}", e.Message);
|
||||
this.LogException(e, "DequeueEvent error");
|
||||
}
|
||||
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
|
||||
if (DequeueLock != null)
|
||||
{
|
||||
DequeueLock.Leave();
|
||||
}
|
||||
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
|
||||
System.Threading.Monitor.Exit(_dequeueLock);
|
||||
}
|
||||
|
||||
void HeartbeatStart()
|
||||
@@ -746,15 +745,11 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
if (HeartbeatSendTimer == null)
|
||||
{
|
||||
|
||||
HeartbeatSendTimer = new Timer(HeartbeatInterval) { AutoReset = true };
|
||||
HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null);
|
||||
HeartbeatSendTimer.Start();
|
||||
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
||||
}
|
||||
if (HeartbeatAckTimer == null)
|
||||
{
|
||||
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
|
||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
||||
HeartbeatAckTimer.Start();
|
||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -764,13 +759,13 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
|
||||
if (HeartbeatSendTimer != null)
|
||||
{
|
||||
this.LogVerbose("Stoping Heartbeat Send");
|
||||
Debug.Console(2, this, "Stoping Heartbeat Send");
|
||||
HeartbeatSendTimer.Stop();
|
||||
HeartbeatSendTimer = null;
|
||||
}
|
||||
if (HeartbeatAckTimer != null)
|
||||
{
|
||||
this.LogVerbose("Stoping Heartbeat Ack");
|
||||
Debug.Console(2, this, "Stoping Heartbeat Ack");
|
||||
HeartbeatAckTimer.Stop();
|
||||
HeartbeatAckTimer = null;
|
||||
}
|
||||
@@ -779,7 +774,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
void SendHeartbeat(object notused)
|
||||
{
|
||||
this.SendText(HeartbeatString);
|
||||
this.LogVerbose("Sending Heartbeat");
|
||||
Debug.Console(2, this, "Sending Heartbeat");
|
||||
|
||||
}
|
||||
|
||||
@@ -798,17 +793,13 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
{
|
||||
if (HeartbeatAckTimer != null)
|
||||
{
|
||||
HeartbeatAckTimer.Stop();
|
||||
HeartbeatAckTimer.Interval = HeartbeatInterval * 2;
|
||||
HeartbeatAckTimer.Start();
|
||||
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
|
||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
||||
HeartbeatAckTimer.Start();
|
||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||
}
|
||||
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||
return remainingText;
|
||||
}
|
||||
}
|
||||
@@ -816,7 +807,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, "Error checking heartbeat: {0}", ex.Message);
|
||||
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||
}
|
||||
return received;
|
||||
}
|
||||
@@ -830,7 +821,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||
SendText("Heartbeat not received by server, closing connection");
|
||||
CheckClosedAndTryReconnect();
|
||||
}
|
||||
@@ -838,7 +829,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, "Heartbeat timeout Error on _client: {0}, {1}", Key, ex.Message);
|
||||
ErrorLog.Error("Heartbeat timeout Error on _client: {0}, {1}", Key, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,14 +862,14 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
||||
if (n <= 0)
|
||||
{
|
||||
this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
||||
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -897,7 +888,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, "Error sending bytes. Error: {0}", ex.Message);
|
||||
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -916,7 +907,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
try
|
||||
{
|
||||
this.LogVerbose("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||
|
||||
OnConnectionChange();
|
||||
// The client could be null or disposed by this time...
|
||||
@@ -929,7 +920,7 @@ public class GenericSecureTcpIpClient : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError(ex, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using PepperDash.Core.Logging;
|
||||
@@ -83,7 +80,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
public string Hostname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The port number on which the server is listening.
|
||||
/// Port on server
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
@@ -116,7 +113,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SharedKey is sent for verification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
||||
/// SharedKey is sent for varification to the server. Shared key can be any text (255 char limit in SIMPL+ Module), but must match the Shared Key on the Server module
|
||||
/// </summary>
|
||||
public string SharedKey { get; set; }
|
||||
|
||||
@@ -224,7 +221,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
/// <summary>
|
||||
/// private Timer for auto reconnect
|
||||
/// </summary>
|
||||
System.Timers.Timer RetryTimer;
|
||||
CTimer RetryTimer;
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -256,13 +253,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
/// </summary>
|
||||
public ushort HeartbeatRequiredIntervalInSeconds { set { HeartbeatInterval = (value * 1000); } }
|
||||
|
||||
System.Timers.Timer HeartbeatSendTimer;
|
||||
System.Timers.Timer HeartbeatAckTimer;
|
||||
CTimer HeartbeatSendTimer;
|
||||
CTimer HeartbeatAckTimer;
|
||||
/// <summary>
|
||||
/// Used to force disconnection on a dead connect attempt
|
||||
/// </summary>
|
||||
System.Timers.Timer ConnectFailTimer;
|
||||
System.Timers.Timer WaitForSharedKey;
|
||||
CTimer ConnectFailTimer;
|
||||
CTimer WaitForSharedKey;
|
||||
private int ConnectionCount;
|
||||
/// <summary>
|
||||
/// Internal secure client
|
||||
@@ -274,7 +271,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
/// <summary>
|
||||
/// Queue lock
|
||||
/// </summary>
|
||||
private readonly object _dequeueLock = new();
|
||||
CCriticalSection DequeueLock = new CCriticalSection();
|
||||
|
||||
/// <summary>
|
||||
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
||||
@@ -339,9 +336,8 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the client's key property, which is used to identify this client instance.
|
||||
/// Just to help S+ set the key
|
||||
/// </summary>
|
||||
/// <param name="key">The unique key that identifies this client instance.</param>
|
||||
public void Initialize(string key)
|
||||
{
|
||||
Key = key;
|
||||
@@ -350,7 +346,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
/// <summary>
|
||||
/// Initialize called by the constructor that accepts a client config object. Can be called later to reset properties of client.
|
||||
/// </summary>
|
||||
/// <param name="clientConfigObject">The configuration object containing the client's settings.</param>
|
||||
/// <param name="clientConfigObject"></param>
|
||||
public void Initialize(TcpClientConfigObject clientConfigObject)
|
||||
{
|
||||
try
|
||||
@@ -365,7 +361,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
SharedKey = clientConfigObject.SharedKey;
|
||||
SharedKeyRequired = clientConfigObject.SharedKeyRequired;
|
||||
HeartbeatEnabled = clientConfigObject.HeartbeatRequired;
|
||||
HeartbeatRequiredIntervalInSeconds = clientConfigObject.HeartbeatRequiredIntervalInSeconds > 0 ?
|
||||
HeartbeatRequiredIntervalInSeconds = clientConfigObject.HeartbeatRequiredIntervalInSeconds > 0 ?
|
||||
clientConfigObject.HeartbeatRequiredIntervalInSeconds : (ushort)15;
|
||||
HeartbeatString = string.IsNullOrEmpty(clientConfigObject.HeartbeatStringToMatch) ? "heartbeat" : clientConfigObject.HeartbeatStringToMatch;
|
||||
Port = TcpSshProperties.Port;
|
||||
@@ -391,7 +387,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
||||
{
|
||||
this.LogInformation("Program stopping. Closing Client connection");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection");
|
||||
ProgramIsStopping = true;
|
||||
Disconnect();
|
||||
}
|
||||
@@ -404,17 +400,17 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
public void Connect()
|
||||
{
|
||||
ConnectionCount++;
|
||||
this.LogVerbose("Attempting connect Count:{0}", ConnectionCount);
|
||||
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
|
||||
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
this.LogInformation("Already connected. Ignoring.");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
||||
return;
|
||||
}
|
||||
if (IsTryingToConnect)
|
||||
{
|
||||
this.LogInformation("Already trying to connect. Ignoring.");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
||||
return;
|
||||
}
|
||||
try
|
||||
@@ -427,17 +423,17 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
if (string.IsNullOrEmpty(Hostname))
|
||||
{
|
||||
this.LogWarning("DynamicTcpClient: No address set");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
||||
return;
|
||||
}
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
this.LogWarning("DynamicTcpClient: Invalid port");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||
{
|
||||
this.LogWarning("DynamicTcpClient: No Shared Key set");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -458,10 +454,9 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
|
||||
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||
|
||||
ConnectFailTimer = new System.Timers.Timer(30000) { AutoReset = false };
|
||||
ConnectFailTimer.Elapsed += (s, e) =>
|
||||
ConnectFailTimer = new CTimer(o =>
|
||||
{
|
||||
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||
if (IsTryingToConnect)
|
||||
{
|
||||
IsTryingToConnect = false;
|
||||
@@ -473,13 +468,12 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
//SecureClient.DisconnectFromServer();
|
||||
//CheckClosedAndTryReconnect();
|
||||
}
|
||||
};
|
||||
ConnectFailTimer.Start();
|
||||
}, 30000);
|
||||
|
||||
this.LogVerbose("Making Connection Count:{0}", ConnectionCount);
|
||||
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
|
||||
Client.ConnectToServerAsync(o =>
|
||||
{
|
||||
this.LogVerbose("ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||
|
||||
if (ConnectFailTimer != null)
|
||||
{
|
||||
@@ -489,22 +483,22 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
|
||||
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
this.LogVerbose("Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||
Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||
o.ReceiveDataAsync(Receive);
|
||||
|
||||
if (SharedKeyRequired)
|
||||
{
|
||||
WaitingForSharedKeyResponse = true;
|
||||
WaitForSharedKey = new System.Timers.Timer(15000) { AutoReset = false };
|
||||
WaitForSharedKey.Elapsed += (s, e) =>
|
||||
WaitForSharedKey = new CTimer(timer =>
|
||||
{
|
||||
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
||||
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
||||
o.DisconnectFromServer();
|
||||
//CheckClosedAndTryReconnect();
|
||||
//OnClientReadyForcommunications(false); // Should send false event
|
||||
};
|
||||
WaitForSharedKey.Start();
|
||||
}, 15000);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -518,14 +512,14 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogWarning("Connect attempt failed {0}", o.ClientStatus);
|
||||
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
||||
CheckClosedAndTryReconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("Client connection exception: {0}", ex.Message);
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message);
|
||||
IsTryingToConnect = false;
|
||||
CheckClosedAndTryReconnect();
|
||||
}
|
||||
@@ -562,7 +556,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
if (Client != null)
|
||||
{
|
||||
//SecureClient.DisconnectFromServer();
|
||||
this.LogVerbose("Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||
Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||
Client.SocketStatusChange -= Client_SocketStatusChange;
|
||||
Client.Dispose();
|
||||
Client = null;
|
||||
@@ -584,24 +578,22 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
{
|
||||
if (Client != null)
|
||||
{
|
||||
this.LogVerbose("Cleaning up remotely closed/failed connection.");
|
||||
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
|
||||
Cleanup();
|
||||
}
|
||||
if (!DisconnectCalledByUser && AutoReconnect)
|
||||
{
|
||||
var halfInterval = AutoReconnectIntervalMs / 2;
|
||||
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
||||
this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime);
|
||||
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
|
||||
if (RetryTimer != null)
|
||||
{
|
||||
RetryTimer.Stop();
|
||||
RetryTimer = null;
|
||||
}
|
||||
if (AutoReconnectTriggered != null)
|
||||
if(AutoReconnectTriggered != null)
|
||||
AutoReconnectTriggered(this, new EventArgs());
|
||||
RetryTimer = new System.Timers.Timer(rndTime) { AutoReset = false };
|
||||
RetryTimer.Elapsed += (s, e) => Connect();
|
||||
RetryTimer.Start();
|
||||
RetryTimer = new CTimer(o => Connect(), rndTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,13 +612,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
{
|
||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
this.LogVerbose("Client Received:\r--------\r{0}\r--------", str);
|
||||
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
|
||||
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
||||
{
|
||||
|
||||
if (SharedKeyRequired && str == "SharedKey:")
|
||||
{
|
||||
this.LogVerbose("Server asking for shared key, sending");
|
||||
Debug.Console(2, this, "Server asking for shared key, sending");
|
||||
SendText(SharedKey + "\n");
|
||||
}
|
||||
else if (SharedKeyRequired && str == "Shared Key Match")
|
||||
@@ -634,7 +626,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
StopWaitForSharedKeyTimer();
|
||||
|
||||
|
||||
this.LogVerbose("Shared key confirmed. Ready for communication");
|
||||
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
|
||||
OnClientReadyForcommunications(true); // Successful key exchange
|
||||
}
|
||||
else
|
||||
@@ -654,7 +646,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("Error receiving data: {1}. Error: {0}", ex.Message, str);
|
||||
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
||||
}
|
||||
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
client.ReceiveDataAsync(Receive);
|
||||
@@ -662,8 +654,9 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
||||
if (handler != null)
|
||||
{
|
||||
if (Monitor.TryEnter(_dequeueLock))
|
||||
Task.Run(() => DequeueEvent());
|
||||
var gotLock = DequeueLock.TryEnter();
|
||||
if (gotLock)
|
||||
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
|
||||
}
|
||||
}
|
||||
else //JAG added this as I believe the error return is 0 bytes like the server. See help when hover on ReceiveAsync
|
||||
@@ -673,7 +666,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
|
||||
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
||||
/// It will dequeue items as they are enqueued automatically.
|
||||
/// </summary>
|
||||
void DequeueEvent()
|
||||
@@ -693,29 +686,28 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogError("DequeueEvent error: {0}", e.Message, e);
|
||||
this.LogException(e, "DequeueEvent error");
|
||||
}
|
||||
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
|
||||
if (DequeueLock != null)
|
||||
{
|
||||
DequeueLock.Leave();
|
||||
}
|
||||
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
|
||||
Monitor.Exit(_dequeueLock);
|
||||
}
|
||||
|
||||
void HeartbeatStart()
|
||||
{
|
||||
if (HeartbeatEnabled)
|
||||
{
|
||||
this.LogVerbose("Starting Heartbeat");
|
||||
Debug.Console(2, this, "Starting Heartbeat");
|
||||
if (HeartbeatSendTimer == null)
|
||||
{
|
||||
|
||||
HeartbeatSendTimer = new System.Timers.Timer(HeartbeatInterval) { AutoReset = true };
|
||||
HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null);
|
||||
HeartbeatSendTimer.Start();
|
||||
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
||||
}
|
||||
if (HeartbeatAckTimer == null)
|
||||
{
|
||||
HeartbeatAckTimer = new System.Timers.Timer(HeartbeatInterval * 2) { AutoReset = true };
|
||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
||||
HeartbeatAckTimer.Start();
|
||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -725,13 +717,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
|
||||
if (HeartbeatSendTimer != null)
|
||||
{
|
||||
this.LogVerbose("Stopping Heartbeat Send");
|
||||
Debug.Console(2, this, "Stoping Heartbeat Send");
|
||||
HeartbeatSendTimer.Stop();
|
||||
HeartbeatSendTimer = null;
|
||||
}
|
||||
if (HeartbeatAckTimer != null)
|
||||
{
|
||||
this.LogVerbose("Stopping Heartbeat Ack");
|
||||
Debug.Console(2, this, "Stoping Heartbeat Ack");
|
||||
HeartbeatAckTimer.Stop();
|
||||
HeartbeatAckTimer = null;
|
||||
}
|
||||
@@ -740,7 +732,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
void SendHeartbeat(object notused)
|
||||
{
|
||||
this.SendText(HeartbeatString);
|
||||
this.LogVerbose("Sending Heartbeat");
|
||||
Debug.Console(2, this, "Sending Heartbeat");
|
||||
|
||||
}
|
||||
|
||||
@@ -759,17 +751,13 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
{
|
||||
if (HeartbeatAckTimer != null)
|
||||
{
|
||||
HeartbeatAckTimer.Stop();
|
||||
HeartbeatAckTimer.Interval = HeartbeatInterval * 2;
|
||||
HeartbeatAckTimer.Start();
|
||||
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
HeartbeatAckTimer = new System.Timers.Timer(HeartbeatInterval * 2) { AutoReset = true };
|
||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
||||
HeartbeatAckTimer.Start();
|
||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||
}
|
||||
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||
return remainingText;
|
||||
}
|
||||
}
|
||||
@@ -777,7 +765,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("Error checking heartbeat: {0}", ex.Message, ex);
|
||||
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||
}
|
||||
return received;
|
||||
}
|
||||
@@ -791,7 +779,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||
SendText("Heartbeat not received by server, closing connection");
|
||||
CheckClosedAndTryReconnect();
|
||||
}
|
||||
@@ -799,7 +787,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
|
||||
ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -832,14 +820,14 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
||||
if (n <= 0)
|
||||
{
|
||||
this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("Error sending text: {1}. Error: {0}", text, ex.Message);
|
||||
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -858,7 +846,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("Error sending bytes. Error: {0}", ex.Message, ex);
|
||||
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -877,7 +865,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
try
|
||||
{
|
||||
this.LogDebug("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||
|
||||
OnConnectionChange();
|
||||
// The client could be null or disposed by this time...
|
||||
@@ -890,7 +878,7 @@ public class GenericSecureTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogError("Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
using System;
|
||||
/*PepperDash Technology Corp.
|
||||
JAG
|
||||
Copyright: 2017
|
||||
------------------------------------
|
||||
***Notice of Ownership and Copyright***
|
||||
The material in which this notice appears is the property of PepperDash Technology Corporation,
|
||||
which claims copyright under the laws of the United States of America in the entire body of material
|
||||
and in all parts thereof, regardless of the use to which it is being put. Any use, in whole or in part,
|
||||
of this material by another party without the express written permission of PepperDash Technology Corporation is prohibited.
|
||||
PepperDash Technology Corporation reserves all rights under applicable laws.
|
||||
------------------------------------ */
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using PepperDash.Core.Logging;
|
||||
@@ -59,17 +69,12 @@ public class GenericSecureTcpIpServer : Device
|
||||
/// <summary>
|
||||
/// Server listen lock
|
||||
/// </summary>
|
||||
private readonly object _serverLock = new();
|
||||
CCriticalSection ServerCCSection = new CCriticalSection();
|
||||
|
||||
/// <summary>
|
||||
/// Queue lock
|
||||
/// </summary>
|
||||
private readonly object _dequeueLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast lock
|
||||
/// </summary>
|
||||
private readonly object _broadcastLock = new();
|
||||
CCriticalSection DequeueLock = new CCriticalSection();
|
||||
|
||||
/// <summary>
|
||||
/// Receive Queue size. Defaults to 20. Will set to 20 if QueueSize property is less than 20. Use constructor or set queue size property before
|
||||
@@ -91,7 +96,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
/// <summary>
|
||||
/// Timer to operate the bandaid monitor client in a loop.
|
||||
/// </summary>
|
||||
Timer MonitorClientTimer;
|
||||
CTimer MonitorClientTimer;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -258,7 +263,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
public string HeartbeatStringToMatch { get; set; }
|
||||
|
||||
//private timers for Heartbeats per client
|
||||
Dictionary<uint, Timer> HeartbeatTimerDictionary = new Dictionary<uint, Timer>();
|
||||
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||
|
||||
//flags to show the secure server is waiting for client at index to send the shared key
|
||||
List<uint> WaitingForSharedKey = new List<uint>();
|
||||
@@ -394,19 +399,18 @@ public class GenericSecureTcpIpServer : Device
|
||||
/// </summary>
|
||||
public void Listen()
|
||||
{
|
||||
lock (_serverLock)
|
||||
{
|
||||
ServerCCSection.Enter();
|
||||
try
|
||||
{
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
this.LogError("Server '{0}': Invalid port", Key);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key);
|
||||
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||
{
|
||||
this.LogError("Server '{0}': No Shared Key set", Key);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key);
|
||||
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
||||
return;
|
||||
}
|
||||
@@ -430,21 +434,22 @@ public class GenericSecureTcpIpServer : Device
|
||||
SocketErrorCodes status = SecureServer.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
||||
{
|
||||
this.LogError("Error starting WaitForConnectionAsync {0}", status);
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Error starting WaitForConnectionAsync {0}", status);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerStopped = false;
|
||||
}
|
||||
OnServerStateChange(SecureServer.State);
|
||||
this.LogInformation("Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Secure Server Status: {0}, Socket Status: {1}", SecureServer.State, SecureServer.ServerSocketStatus);
|
||||
ServerCCSection.Leave();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
||||
ServerCCSection.Leave();
|
||||
ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
||||
}
|
||||
} // end lock
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -454,18 +459,18 @@ public class GenericSecureTcpIpServer : Device
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogVerbose("Stopping Listener");
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
|
||||
if (SecureServer != null)
|
||||
{
|
||||
SecureServer.Stop();
|
||||
this.LogVerbose("Server State: {0}", SecureServer.State);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", SecureServer.State);
|
||||
OnServerStateChange(SecureServer.State);
|
||||
}
|
||||
ServerStopped = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error stopping server. Error: {0}", ex.Message);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,11 +483,11 @@ public class GenericSecureTcpIpServer : Device
|
||||
try
|
||||
{
|
||||
SecureServer.Disconnect(client);
|
||||
this.LogVerbose("Disconnected client index: {0}", client);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
@@ -490,7 +495,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
/// </summary>
|
||||
public void DisconnectAllClientsForShutdown()
|
||||
{
|
||||
this.LogInformation("Disconnecting All Clients");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients");
|
||||
if (SecureServer != null)
|
||||
{
|
||||
SecureServer.SocketStatusChange -= SecureServer_SocketStatusChange;
|
||||
@@ -502,17 +507,17 @@ public class GenericSecureTcpIpServer : Device
|
||||
try
|
||||
{
|
||||
SecureServer.Disconnect(i);
|
||||
this.LogInformation("Disconnected client index: {0}", i);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
|
||||
}
|
||||
}
|
||||
this.LogInformation("Server Status: {0}", SecureServer.ServerSocketStatus);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", SecureServer.ServerSocketStatus);
|
||||
}
|
||||
|
||||
this.LogInformation("Disconnected All Clients");
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
|
||||
ConnectedClientsIndexes.Clear();
|
||||
|
||||
if (!ProgramIsStopping)
|
||||
@@ -530,8 +535,8 @@ public class GenericSecureTcpIpServer : Device
|
||||
/// <param name="text"></param>
|
||||
public void BroadcastText(string text)
|
||||
{
|
||||
lock (_broadcastLock)
|
||||
{
|
||||
CCriticalSection CCBroadcast = new CCriticalSection();
|
||||
CCBroadcast.Enter();
|
||||
try
|
||||
{
|
||||
if (ConnectedClientsIndexes.Count > 0)
|
||||
@@ -547,12 +552,13 @@ public class GenericSecureTcpIpServer : Device
|
||||
}
|
||||
}
|
||||
}
|
||||
CCBroadcast.Leave();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
||||
CCBroadcast.Leave();
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
||||
}
|
||||
} // end lock
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -573,7 +579,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
||||
Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,19 +597,13 @@ public class GenericSecureTcpIpServer : Device
|
||||
if (noDelimiter.Contains(HeartbeatStringToMatch))
|
||||
{
|
||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
{
|
||||
HeartbeatTimerDictionary[clientIndex].Stop();
|
||||
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
|
||||
HeartbeatTimerDictionary[clientIndex].Start();
|
||||
}
|
||||
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||
else
|
||||
{
|
||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
||||
heartbeatTimer.Start();
|
||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||
}
|
||||
this.LogDebug("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
||||
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
||||
// Return Heartbeat
|
||||
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
||||
return remainingText;
|
||||
@@ -612,25 +612,19 @@ public class GenericSecureTcpIpServer : Device
|
||||
else
|
||||
{
|
||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
{
|
||||
HeartbeatTimerDictionary[clientIndex].Stop();
|
||||
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
|
||||
HeartbeatTimerDictionary[clientIndex].Start();
|
||||
}
|
||||
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||
else
|
||||
{
|
||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
||||
heartbeatTimer.Start();
|
||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||
}
|
||||
this.LogInformation("Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
||||
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
|
||||
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||
}
|
||||
return received;
|
||||
}
|
||||
@@ -642,11 +636,11 @@ public class GenericSecureTcpIpServer : Device
|
||||
/// <returns></returns>
|
||||
public string GetClientIPAddress(uint clientIndex)
|
||||
{
|
||||
this.LogInformation("GetClientIPAddress Index: {0}", clientIndex);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex);
|
||||
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
||||
{
|
||||
var ipa = this.SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||
this.LogInformation("GetClientIPAddress IPAddreess: {0}", ipa);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa);
|
||||
return ipa;
|
||||
|
||||
}
|
||||
@@ -669,13 +663,14 @@ public class GenericSecureTcpIpServer : Device
|
||||
clientIndex = (uint)o;
|
||||
address = SecureServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||
|
||||
this.LogInformation("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
||||
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
||||
|
||||
if (SecureServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
||||
|
||||
var discoResult = SecureServer.Disconnect(clientIndex);
|
||||
//Debug.Console(1, this, "{0}", discoResult);
|
||||
|
||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
{
|
||||
@@ -704,9 +699,11 @@ public class GenericSecureTcpIpServer : Device
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
// Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.SecureServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.SecureServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
this.LogInformation("SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange ConnectedCLients: {0} ServerState: {1} Port: {2}", SecureServer.NumberOfClientsConnected, SecureServer.State, SecureServer.PortNumber);
|
||||
|
||||
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||
ConnectedClientsIndexes.Remove(clientIndex);
|
||||
@@ -728,12 +725,12 @@ public class GenericSecureTcpIpServer : Device
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex.Message);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
|
||||
}
|
||||
//Use a thread for this event so that the server state updates to listening while this event is processed. Listening must be added to the server state
|
||||
//after every client connection so that the server can check and see if it is at max clients. Due to this the event fires and server listening enum bit flag
|
||||
//is not set. Putting in a thread allows the state to update before this event processes so that the subscribers to this event get accurate isListening in the event.
|
||||
System.Threading.Tasks.Task.Run(() => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)));
|
||||
CrestronInvoke.BeginInvoke(o => onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex)), null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -748,7 +745,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogInformation("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
||||
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
||||
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||
if (clientIndex != 0)
|
||||
@@ -768,7 +765,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
}
|
||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
||||
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
||||
this.LogInformation("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -778,10 +775,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
{
|
||||
if (!HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
{
|
||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
||||
heartbeatTimer.Start();
|
||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
||||
HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -790,19 +784,19 @@ public class GenericSecureTcpIpServer : Device
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogError("Client attempt faulty.");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex.Message);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
|
||||
}
|
||||
|
||||
// Rearm the listner
|
||||
SocketErrorCodes status = server.WaitForConnectionAsync(IPAddress.Any, SecureConnectCallback);
|
||||
if (status != SocketErrorCodes.SOCKET_OPERATION_PENDING)
|
||||
{
|
||||
this.LogError("Socket status connect callback status {0}", status);
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Socket status connect callback status {0}", status);
|
||||
if (status == SocketErrorCodes.SOCKET_CONNECTION_IN_PROGRESS)
|
||||
{
|
||||
// There is an issue where on a failed negotiation we need to stop and start the server. This should still leave connected clients intact.
|
||||
@@ -839,7 +833,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
if (received != SharedKey)
|
||||
{
|
||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
||||
this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
||||
mySecureTCPServer.SendData(clientIndex, b, b.Length);
|
||||
mySecureTCPServer.Disconnect(clientIndex);
|
||||
|
||||
@@ -850,7 +844,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
||||
mySecureTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
||||
OnServerClientReadyForCommunications(clientIndex);
|
||||
this.LogInformation("Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
||||
{
|
||||
@@ -863,7 +857,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex.Message);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
|
||||
}
|
||||
if (mySecureTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
mySecureTCPServer.ReceiveDataAsync(clientIndex, SecureReceivedDataAsyncCallback);
|
||||
@@ -871,8 +865,9 @@ public class GenericSecureTcpIpServer : Device
|
||||
//Check to see if there is a subscription to the TextReceivedQueueInvoke event. If there is start the dequeue thread.
|
||||
if (handler != null)
|
||||
{
|
||||
if (System.Threading.Monitor.TryEnter(_dequeueLock))
|
||||
System.Threading.Tasks.Task.Run(() => DequeueEvent());
|
||||
var gotLock = DequeueLock.TryEnter();
|
||||
if (gotLock)
|
||||
CrestronInvoke.BeginInvoke((o) => DequeueEvent());
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -882,7 +877,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method gets spooled up in its own thread an protected by a lock to prevent multiple threads from running concurrently.
|
||||
/// This method gets spooled up in its own thread an protected by a CCriticalSection to prevent multiple threads from running concurrently.
|
||||
/// It will dequeue items as they are enqueued automatically.
|
||||
/// </summary>
|
||||
void DequeueEvent()
|
||||
@@ -902,10 +897,13 @@ public class GenericSecureTcpIpServer : Device
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogError(e, "DequeueEvent error");
|
||||
this.LogException(e, "DequeueEvent error");
|
||||
}
|
||||
// Make sure to leave the CCritical section in case an exception above stops this thread, or we won't be able to restart it.
|
||||
if (DequeueLock != null)
|
||||
{
|
||||
DequeueLock.Leave();
|
||||
}
|
||||
// Make sure to release the lock in case an exception above stops this thread, or we won't be able to restart it.
|
||||
System.Threading.Monitor.Exit(_dequeueLock);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -976,7 +974,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
if (MonitorClient != null)
|
||||
MonitorClient.Disconnect();
|
||||
|
||||
this.LogInformation("Program stopping. Closing server");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server");
|
||||
KillServer();
|
||||
}
|
||||
}
|
||||
@@ -1002,9 +1000,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
{
|
||||
return;
|
||||
}
|
||||
MonitorClientTimer = new Timer(60000) { AutoReset = false };
|
||||
MonitorClientTimer.Elapsed += (s, e) => RunMonitorClient();
|
||||
MonitorClientTimer.Start();
|
||||
MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1019,7 +1015,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
||||
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
||||
|
||||
this.LogInformation("Starting monitor check");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check");
|
||||
|
||||
MonitorClient.Connect();
|
||||
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
||||
@@ -1046,7 +1042,7 @@ public class GenericSecureTcpIpServer : Device
|
||||
{
|
||||
if (args.IsReady)
|
||||
{
|
||||
this.LogInformation("Monitor client connection success. Disconnecting in 2s");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s");
|
||||
MonitorClientTimer.Stop();
|
||||
MonitorClientTimer = null;
|
||||
MonitorClientFailureCount = 0;
|
||||
@@ -1067,13 +1063,13 @@ public class GenericSecureTcpIpServer : Device
|
||||
StopMonitorClient();
|
||||
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
||||
{
|
||||
this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}",
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
|
||||
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
||||
StartMonitorClient();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogError(
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error,
|
||||
"\r***************************\rMonitor client connection has hung a maximum of {0} times. \r***************************",
|
||||
MonitorClientMaxFailureCount);
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
using System.Threading;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using Org.BouncyCastle.Utilities;
|
||||
using PepperDash.Core.Logging;
|
||||
using Renci.SshNet;
|
||||
using Renci.SshNet.Common;
|
||||
@@ -78,7 +79,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SSH Client
|
||||
///
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
@@ -131,9 +132,11 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
|
||||
ShellStream TheStream;
|
||||
|
||||
Timer ReconnectTimer;
|
||||
CTimer ReconnectTimer;
|
||||
|
||||
private System.Threading.SemaphoreSlim connectLock = new System.Threading.SemaphoreSlim(1);
|
||||
//Lock object to prevent simulatneous connect/disconnect operations
|
||||
//private CCriticalSection connectLock = new CCriticalSection();
|
||||
private SemaphoreSlim connectLock = new SemaphoreSlim(1);
|
||||
|
||||
private bool DisconnectLogged = false;
|
||||
|
||||
@@ -144,53 +147,51 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
Key = key;
|
||||
Hostname = hostname;
|
||||
Port = port;
|
||||
Username = username;
|
||||
Password = password;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
Key = key;
|
||||
Hostname = hostname;
|
||||
Port = port;
|
||||
Username = username;
|
||||
Password = password;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
|
||||
ReconnectTimer = new Timer { AutoReset = false, Enabled = false };
|
||||
ReconnectTimer.Elapsed += (s, e) =>
|
||||
ReconnectTimer = new CTimer(o =>
|
||||
{
|
||||
if (ConnectEnabled)
|
||||
{
|
||||
if (ConnectEnabled)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
};
|
||||
}
|
||||
Connect();
|
||||
}
|
||||
}, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor - Must set all properties before calling Connect
|
||||
/// </summary>
|
||||
public GenericSshClient()
|
||||
: base(SPlusKey)
|
||||
{
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
/// <summary>
|
||||
/// S+ Constructor - Must set all properties before calling Connect
|
||||
/// </summary>
|
||||
public GenericSshClient()
|
||||
: base(SPlusKey)
|
||||
{
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
|
||||
ReconnectTimer = new Timer { AutoReset = false, Enabled = false };
|
||||
ReconnectTimer.Elapsed += (s, e) =>
|
||||
ReconnectTimer = new CTimer(o =>
|
||||
{
|
||||
if (ConnectEnabled)
|
||||
{
|
||||
Connect();
|
||||
}
|
||||
};
|
||||
}
|
||||
}, System.Threading.Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles closing this up when the program shuts down
|
||||
/// </summary>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
if (Client != null)
|
||||
{
|
||||
this.LogDebug("Program stopping. Closing connection");
|
||||
/// <summary>
|
||||
/// Handles closing this up when the program shuts down
|
||||
/// </summary>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
if (Client != null)
|
||||
{
|
||||
this.LogDebug("Program stopping. Closing connection");
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
@@ -223,10 +224,10 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
this.LogDebug("Attempting connect");
|
||||
|
||||
// Cancel reconnect if running.
|
||||
if (ReconnectTimer != null)
|
||||
{
|
||||
ReconnectTimer.Stop();
|
||||
}
|
||||
if (ReconnectTimer != null)
|
||||
{
|
||||
ReconnectTimer.Stop();
|
||||
}
|
||||
|
||||
// Cleanup the old client if it already exists
|
||||
if (Client != null)
|
||||
@@ -264,10 +265,11 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
catch (SshConnectionException e)
|
||||
{
|
||||
var ie = e.InnerException; // The details are inside!!
|
||||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||
|
||||
if (ie is SocketException)
|
||||
{
|
||||
this.LogException(ie, "CONNECTION failure: Cannot reach host");
|
||||
this.LogException(ie, "CONNECTION failure: Cannot reach host");
|
||||
}
|
||||
|
||||
if (ie is System.Net.Sockets.SocketException socketException)
|
||||
@@ -287,12 +289,10 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {autoReconnect}, {autoReconnectInterval}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Stop();
|
||||
ReconnectTimer.Interval = AutoReconnectIntervalMs;
|
||||
ReconnectTimer.Start();
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
}
|
||||
catch (SshOperationTimeoutException ex)
|
||||
catch(SshOperationTimeoutException ex)
|
||||
{
|
||||
this.LogWarning("Connection attempt timed out: {message}", ex.Message);
|
||||
|
||||
@@ -301,22 +301,19 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Stop();
|
||||
ReconnectTimer.Interval = AutoReconnectIntervalMs;
|
||||
ReconnectTimer.Start();
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var errorLogLevel = DisconnectLogged == true ? Debug.ErrorLogLevel.None : Debug.ErrorLogLevel.Error;
|
||||
this.LogException(e, "Unhandled exception on connect");
|
||||
DisconnectLogged = true;
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
if (AutoReconnect)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Stop();
|
||||
ReconnectTimer.Interval = AutoReconnectIntervalMs;
|
||||
ReconnectTimer.Start();
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,14 +324,18 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect method
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
ConnectEnabled = false;
|
||||
// Stop trying reconnects, if we are
|
||||
ReconnectTimer.Stop();
|
||||
/// <summary>
|
||||
/// Disconnect the clients and put away it's resources.
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
ConnectEnabled = false;
|
||||
// Stop trying reconnects, if we are
|
||||
if (ReconnectTimer != null)
|
||||
{
|
||||
ReconnectTimer.Stop();
|
||||
// ReconnectTimer = null;
|
||||
}
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_BROKEN_LOCALLY);
|
||||
}
|
||||
@@ -360,15 +361,15 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Exception in Kill Client");
|
||||
this.LogException(ex,"Exception in Kill Client");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills the stream
|
||||
/// </summary>
|
||||
void KillStream()
|
||||
{
|
||||
void KillStream()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (TheStream != null)
|
||||
@@ -384,60 +385,60 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
{
|
||||
this.LogException(ex, "Exception in Kill Stream:{0}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the keyboard interactive authentication, should it be required.
|
||||
/// </summary>
|
||||
void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
|
||||
{
|
||||
foreach (AuthenticationPrompt prompt in e.Prompts)
|
||||
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
|
||||
prompt.Response = Password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
|
||||
/// </summary>
|
||||
void Stream_DataReceived(object sender, ShellDataEventArgs e)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles the keyboard interactive authentication, should it be required.
|
||||
/// </summary>
|
||||
void kauth_AuthenticationPrompt(object sender, AuthenticationPromptEventArgs e)
|
||||
{
|
||||
foreach (AuthenticationPrompt prompt in e.Prompts)
|
||||
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
|
||||
prompt.Response = Password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for data receive on ShellStream. Passes data across to queue for line parsing.
|
||||
/// </summary>
|
||||
void Stream_DataReceived(object sender, ShellDataEventArgs e)
|
||||
{
|
||||
if (((ShellStream)sender).Length <= 0L)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var response = ((ShellStream)sender).Read();
|
||||
var response = ((ShellStream)sender).Read();
|
||||
|
||||
var bytesHandler = BytesReceived;
|
||||
|
||||
if (bytesHandler != null)
|
||||
{
|
||||
var bytesHandler = BytesReceived;
|
||||
|
||||
if (bytesHandler != null)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(response);
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
}
|
||||
|
||||
var textHandler = TextReceived;
|
||||
if (textHandler != null)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Received: '{0}'", ComTextHelper.GetDebugText(response));
|
||||
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(response));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
|
||||
/// event
|
||||
/// </summary>
|
||||
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
||||
{
|
||||
System.Threading.Tasks.Task.Run(() =>
|
||||
/// <summary>
|
||||
/// Error event handler for client events - disconnect, etc. Will forward those events via ConnectionChange
|
||||
/// event
|
||||
/// </summary>
|
||||
void Client_ErrorOccurred(object sender, ExceptionEventArgs e)
|
||||
{
|
||||
CrestronInvoke.BeginInvoke(o =>
|
||||
{
|
||||
if (e.Exception is SshConnectionException || e.Exception is System.Net.Sockets.SocketException)
|
||||
this.LogError("Disconnected by remote");
|
||||
@@ -455,9 +456,7 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
if (AutoReconnect && ConnectEnabled)
|
||||
{
|
||||
this.LogDebug("Checking autoreconnect: {0}, {1}ms", AutoReconnect, AutoReconnectIntervalMs);
|
||||
ReconnectTimer.Stop();
|
||||
ReconnectTimer.Interval = AutoReconnectIntervalMs;
|
||||
ReconnectTimer.Start();
|
||||
ReconnectTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -473,50 +472,49 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
|
||||
#region IBasicCommunication Members
|
||||
|
||||
/// <summary>
|
||||
/// Sends text to the server
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public void SendText(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Client != null && TheStream != null && IsConnected)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
this.LogInformation(
|
||||
"Sending {length} characters of text: '{text}'",
|
||||
text.Length,
|
||||
ComTextHelper.GetDebugText(text));
|
||||
/// <summary>
|
||||
/// Sends text to the server
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public void SendText(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Client != null && TheStream != null && IsConnected)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
this.LogInformation(
|
||||
"Sending {length} characters of text: '{text}'",
|
||||
text.Length,
|
||||
ComTextHelper.GetDebugText(text));
|
||||
|
||||
TheStream.Write(text);
|
||||
TheStream.Flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogDebug("Client is null or disconnected. Cannot Send Text");
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
TheStream.Write(text);
|
||||
TheStream.Flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogDebug("Client is null or disconnected. Cannot Send Text");
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
||||
this.LogError("ObjectDisposedException sending '{message}'. Restarting connection...", text.Trim());
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
ReconnectTimer.Stop();
|
||||
ReconnectTimer.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ReconnectTimer.Reset();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Exception sending text: '{message}'", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends Bytes to the server
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Client != null && TheStream != null && IsConnected)
|
||||
@@ -537,57 +535,57 @@ public class GenericSshClient : Device, ISocketStatusWithStreamDebugging, IAutoR
|
||||
this.LogException(ex, "ObjectDisposedException sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
|
||||
KillClient(SocketStatus.SOCKET_STATUS_CONNECT_FAILED);
|
||||
ReconnectTimer.Stop();
|
||||
ReconnectTimer.Start();
|
||||
ReconnectTimer.Reset();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Exception sending {message}", ComTextHelper.GetEscapedText(bytes));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
//*****************************************************************************************************
|
||||
//*****************************************************************************************************
|
||||
}
|
||||
|
||||
//*****************************************************************************************************
|
||||
//*****************************************************************************************************
|
||||
/// <summary>
|
||||
/// Fired when connection changes
|
||||
/// </summary>
|
||||
public class SshConnectionChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Fired when connection changes
|
||||
/// Connection State
|
||||
/// </summary>
|
||||
public class SshConnectionChangeEventArgs : EventArgs
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Connection Status represented as a ushort
|
||||
/// </summary>
|
||||
public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } }
|
||||
|
||||
/// <summary>
|
||||
/// The client
|
||||
/// </summary>
|
||||
public GenericSshClient Client { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Socket Status as represented by
|
||||
/// </summary>
|
||||
public ushort Status { get { return Client.UStatus; } }
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public SshConnectionChangeEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class
|
||||
/// </summary>
|
||||
/// <param name="isConnected">Connection State</param>
|
||||
/// <param name="client">The Client</param>
|
||||
public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client)
|
||||
{
|
||||
/// <summary>
|
||||
/// Connection State
|
||||
/// </summary>
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Connection Status represented as a ushort
|
||||
/// </summary>
|
||||
public ushort UIsConnected { get { return (ushort)(Client.IsConnected ? 1 : 0); } }
|
||||
|
||||
/// <summary>
|
||||
/// The client
|
||||
/// </summary>
|
||||
public GenericSshClient Client { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Socket Status as represented by
|
||||
/// </summary>
|
||||
public ushort Status { get { return Client.UStatus; } }
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public SshConnectionChangeEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class
|
||||
/// </summary>
|
||||
/// <param name="isConnected">Connection State</param>
|
||||
/// <param name="client">The Client</param>
|
||||
public SshConnectionChangeEventArgs(bool isConnected, GenericSshClient client)
|
||||
{
|
||||
IsConnected = isConnected;
|
||||
Client = client;
|
||||
}
|
||||
IsConnected = isConnected;
|
||||
Client = client;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,17 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Timer = System.Timers.Timer;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||
using Required = NewtonsoftJson::Newtonsoft.Json.Required;
|
||||
using PepperDash.Core.Logging;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// A class to handle basic TCP/IP communications with a server
|
||||
/// </summary>
|
||||
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||
public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAutoReconnect
|
||||
{
|
||||
private const string SplusKey = "Uninitialized TcpIpClient";
|
||||
/// <summary>
|
||||
@@ -26,44 +19,44 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
/// </summary>
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as a Byte array
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as a Byte array
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as text
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
/// <summary>
|
||||
/// Fires when data is received from the server and returns it as text
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
//public event GenericSocketStatusChangeEventDelegate SocketStatusChange;
|
||||
public event EventHandler<GenericSocketStatusChageEventArgs> ConnectionChange;
|
||||
|
||||
|
||||
private string _hostname;
|
||||
private string _hostname;
|
||||
|
||||
/// <summary>
|
||||
/// Address of server
|
||||
/// </summary>
|
||||
public string Hostname
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hostname;
|
||||
}
|
||||
get
|
||||
{
|
||||
return _hostname;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_hostname = value;
|
||||
if (_client != null)
|
||||
{
|
||||
_client.AddressClientConnectedTo = _hostname;
|
||||
}
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
_hostname = value;
|
||||
if (_client != null)
|
||||
{
|
||||
_client.AddressClientConnectedTo = _hostname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port on server
|
||||
@@ -85,19 +78,19 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The actual client class
|
||||
/// </summary>
|
||||
private TCPClient _client;
|
||||
/// <summary>
|
||||
/// The actual client class
|
||||
/// </summary>
|
||||
private TCPClient _client;
|
||||
|
||||
/// <summary>
|
||||
/// Bool showing if socket is connected
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
/// <summary>
|
||||
/// Bool showing if socket is connected
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get { return _client != null && _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for IsConnected
|
||||
/// </summary>
|
||||
@@ -106,15 +99,15 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
get { return (ushort)(IsConnected ? 1 : 0); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// _client socket status Read only
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
get
|
||||
/// <summary>
|
||||
/// _client socket status Read only
|
||||
/// </summary>
|
||||
public SocketStatus ClientStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
|
||||
}
|
||||
return _client == null ? SocketStatus.SOCKET_STATUS_NO_CONNECT : _client.ClientStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -126,20 +119,26 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
get { return (ushort)ClientStatus; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Status text shows the message associated with socket status
|
||||
/// </summary>
|
||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
||||
/// </summary>
|
||||
public string ClientStatusText { get { return ClientStatus.ToString(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Connection failure reason
|
||||
/// </summary>
|
||||
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
|
||||
/// <summary>
|
||||
/// Ushort representation of client status
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public ushort UClientStatus { get { return (ushort)ClientStatus; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AutoReconnect
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
/// <summary>
|
||||
/// Connection failure reason
|
||||
/// </summary>
|
||||
public string ConnectionFailure { get { return ClientStatus.ToString(); } }
|
||||
|
||||
/// <summary>
|
||||
/// bool to track if auto reconnect should be set on the socket
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper for AutoReconnect
|
||||
@@ -150,29 +149,29 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
set { AutoReconnect = value == 1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
/// <summary>
|
||||
/// Milliseconds to wait before attempting to reconnect. Defaults to 5000
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set only when the disconnect method is called
|
||||
/// </summary>
|
||||
bool DisconnectCalledByUser;
|
||||
/// <summary>
|
||||
/// Set only when the disconnect method is called
|
||||
/// </summary>
|
||||
bool DisconnectCalledByUser;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Connected
|
||||
{
|
||||
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Connected
|
||||
{
|
||||
get { return _client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED; }
|
||||
}
|
||||
|
||||
//Lock object to prevent simulatneous connect/disconnect operations
|
||||
private readonly object _connectLock = new();
|
||||
private CCriticalSection connectLock = new CCriticalSection();
|
||||
|
||||
// private Timer for auto reconnect
|
||||
private Timer RetryTimer;
|
||||
private CTimer RetryTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
@@ -181,9 +180,9 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
/// <param name="address"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="bufferSize"></param>
|
||||
public GenericTcpIpClient(string key, string address, int port, int bufferSize)
|
||||
: base(key)
|
||||
{
|
||||
public GenericTcpIpClient(string key, string address, int port, int bufferSize)
|
||||
: base(key)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
@@ -191,7 +190,10 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
Port = port;
|
||||
BufferSize = bufferSize;
|
||||
|
||||
SetupRetryTimer();
|
||||
RetryTimer = new CTimer(o =>
|
||||
{
|
||||
Reconnect();
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -206,30 +208,28 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
BufferSize = 2000;
|
||||
|
||||
SetupRetryTimer();
|
||||
RetryTimer = new CTimer(o =>
|
||||
{
|
||||
Reconnect();
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor for S+
|
||||
/// </summary>
|
||||
public GenericTcpIpClient()
|
||||
: base(SplusKey)
|
||||
: base(SplusKey)
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(SplusKey);
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
CrestronEnvironment.ProgramStatusEventHandler += new ProgramStatusEventHandler(CrestronEnvironment_ProgramStatusEventHandler);
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
BufferSize = 2000;
|
||||
|
||||
SetupRetryTimer();
|
||||
}
|
||||
|
||||
private void SetupRetryTimer()
|
||||
{
|
||||
RetryTimer = new Timer { AutoReset = false, Enabled = false };
|
||||
RetryTimer.Elapsed += (s, e) => Reconnect();
|
||||
}
|
||||
|
||||
|
||||
RetryTimer = new CTimer(o =>
|
||||
{
|
||||
Reconnect();
|
||||
}, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Just to help S+ set the key
|
||||
@@ -246,7 +246,7 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
this.LogInformation("Program stopping. Closing connection");
|
||||
Debug.Console(1, this, "Program stopping. Closing connection");
|
||||
Deactivate();
|
||||
}
|
||||
}
|
||||
@@ -255,41 +255,42 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool Deactivate()
|
||||
{
|
||||
public override bool Deactivate()
|
||||
{
|
||||
RetryTimer.Stop();
|
||||
RetryTimer.Dispose();
|
||||
if (_client != null)
|
||||
{
|
||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
||||
_client.SocketStatusChange -= this.Client_SocketStatusChange;
|
||||
DisconnectClient();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to connect to the server
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
public void Connect()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Hostname))
|
||||
{
|
||||
this.LogWarning("GenericTcpIpClient '{0}': No address set", Key);
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': No address set", Key);
|
||||
return;
|
||||
}
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
{
|
||||
this.LogWarning("GenericTcpIpClient '{0}': Invalid port", Key);
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericTcpIpClient '{0}': Invalid port", Key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lock (_connectLock)
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
if (IsConnected)
|
||||
{
|
||||
this.LogInformation("Connection already connected. Exiting Connect()");
|
||||
Debug.Console(1, this, "Connection already connected. Exiting Connect()");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -302,7 +303,11 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
private void Reconnect()
|
||||
{
|
||||
@@ -310,34 +315,44 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
{
|
||||
return;
|
||||
}
|
||||
lock (_connectLock)
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
if (IsConnected || DisconnectCalledByUser == true)
|
||||
{
|
||||
this.LogInformation("Reconnect no longer needed. Exiting Reconnect()");
|
||||
Debug.Console(1, this, "Reconnect no longer needed. Exiting Reconnect()");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogInformation("Attempting reconnect now");
|
||||
Debug.Console(1, this, "Attempting reconnect now");
|
||||
_client.ConnectToServerAsync(ConnectToServerCallback);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to disconnect the client
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
lock (_connectLock)
|
||||
public void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
DisconnectCalledByUser = true;
|
||||
|
||||
// Stop trying reconnects, if we are
|
||||
RetryTimer.Stop();
|
||||
DisconnectClient();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the actual disconnect business
|
||||
@@ -346,7 +361,7 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
{
|
||||
if (_client != null)
|
||||
{
|
||||
this.LogInformation("Disconnecting client");
|
||||
Debug.Console(1, this, "Disconnecting client");
|
||||
if (IsConnected)
|
||||
_client.DisconnectFromServer();
|
||||
}
|
||||
@@ -356,47 +371,50 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
/// Callback method for connection attempt
|
||||
/// </summary>
|
||||
/// <param name="c"></param>
|
||||
void ConnectToServerCallback(TCPClient c)
|
||||
{
|
||||
void ConnectToServerCallback(TCPClient c)
|
||||
{
|
||||
if (c.ClientStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
this.LogInformation("Server connection result: {0}", c.ClientStatus);
|
||||
Debug.Console(0, this, "Server connection result: {0}", c.ClientStatus);
|
||||
WaitAndTryReconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogInformation("Server connection result: {0}", c.ClientStatus);
|
||||
Debug.Console(1, this, "Server connection result: {0}", c.ClientStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects, waits and attemtps to connect again
|
||||
/// </summary>
|
||||
void WaitAndTryReconnect()
|
||||
{
|
||||
Task.Run(() =>
|
||||
void WaitAndTryReconnect()
|
||||
{
|
||||
CrestronInvoke.BeginInvoke(o =>
|
||||
{
|
||||
lock (_connectLock)
|
||||
try
|
||||
{
|
||||
connectLock.Enter();
|
||||
if (!IsConnected && AutoReconnect && !DisconnectCalledByUser && _client != null)
|
||||
{
|
||||
DisconnectClient();
|
||||
this.LogInformation("Attempting reconnect, status={0}", _client.ClientStatus);
|
||||
RetryTimer.Stop();
|
||||
RetryTimer.Interval = AutoReconnectIntervalMs;
|
||||
RetryTimer.Start();
|
||||
Debug.Console(1, this, "Attempting reconnect, status={0}", _client.ClientStatus);
|
||||
RetryTimer.Reset(AutoReconnectIntervalMs);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
connectLock.Leave();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recieves incoming data
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="numBytes"></param>
|
||||
void Receive(TCPClient client, int numBytes)
|
||||
{
|
||||
void Receive(TCPClient client, int numBytes)
|
||||
{
|
||||
if (client != null)
|
||||
{
|
||||
if (numBytes > 0)
|
||||
@@ -407,7 +425,7 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
@@ -418,135 +436,129 @@ public class GenericTcpIpClient : Device, ISocketStatusWithStreamDebugging, IAut
|
||||
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
this.LogInformation("Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||
}
|
||||
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||
}
|
||||
}
|
||||
}
|
||||
client.ReceiveDataAsync(Receive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// General send method
|
||||
/// </summary>
|
||||
public void SendText(string text)
|
||||
{
|
||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||
// Check debug level before processing byte array
|
||||
/// <summary>
|
||||
/// General send method
|
||||
/// </summary>
|
||||
public void SendText(string text)
|
||||
{
|
||||
var bytes = Encoding.GetEncoding(28591).GetBytes(text);
|
||||
// Check debug level before processing byte array
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||
if (_client != null)
|
||||
_client.SendData(bytes, bytes.Length);
|
||||
}
|
||||
_client.SendData(bytes, bytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SendEscapedText method
|
||||
/// </summary>
|
||||
public void SendEscapedText(string text)
|
||||
{
|
||||
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
|
||||
{
|
||||
var hex = s.Groups[1].Value;
|
||||
return ((char)Convert.ToByte(hex, 16)).ToString();
|
||||
});
|
||||
SendText(unescapedText);
|
||||
}
|
||||
/// <summary>
|
||||
/// This is useful from console and...?
|
||||
/// </summary>
|
||||
public void SendEscapedText(string text)
|
||||
{
|
||||
var unescapedText = Regex.Replace(text, @"\\x([0-9a-fA-F][0-9a-fA-F])", s =>
|
||||
{
|
||||
var hex = s.Groups[1].Value;
|
||||
return ((char)Convert.ToByte(hex, 16)).ToString();
|
||||
});
|
||||
SendText(unescapedText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends Bytes to the server
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
if (_client != null)
|
||||
_client.SendData(bytes, bytes.Length);
|
||||
}
|
||||
_client.SendData(bytes, bytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Socket Status Change Handler
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="clientSocketStatus"></param>
|
||||
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
|
||||
{
|
||||
void Client_SocketStatusChange(TCPClient client, SocketStatus clientSocketStatus)
|
||||
{
|
||||
if (clientSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
this.LogDebug("Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||
Debug.Console(0, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||
WaitAndTryReconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogDebug("Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||
_client.ReceiveDataAsync(Receive);
|
||||
Debug.Console(1, this, "Socket status change {0} ({1})", clientSocketStatus, ClientStatusText);
|
||||
_client.ReceiveDataAsync(Receive);
|
||||
}
|
||||
|
||||
var handler = ConnectionChange;
|
||||
if (handler != null)
|
||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
||||
}
|
||||
}
|
||||
var handler = ConnectionChange;
|
||||
if (handler != null)
|
||||
ConnectionChange(this, new GenericSocketStatusChageEventArgs(this));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration properties for TCP/SSH Connections
|
||||
/// </summary>
|
||||
public class TcpSshPropertiesConfig
|
||||
{
|
||||
public class TcpSshPropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Address to connect to
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string Address { get; set; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Port to connect to
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public int Port { get; set; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Username credential
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
public string Username { get; set; }
|
||||
/// <summary>
|
||||
/// Passord credential
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defaults to 32768
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
/// <summary>
|
||||
/// Defaults to 32768
|
||||
/// </summary>
|
||||
public int BufferSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AutoReconnect
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
/// <summary>
|
||||
/// Defaults to true
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the AutoReconnectIntervalMs
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, turns off echo for the SSH session
|
||||
/// </summary>
|
||||
[JsonProperty("disableSshEcho")]
|
||||
public bool DisableSshEcho { get; set; }
|
||||
/// <summary>
|
||||
/// Defaults to 5000ms
|
||||
/// </summary>
|
||||
public int AutoReconnectIntervalMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public TcpSshPropertiesConfig()
|
||||
{
|
||||
BufferSize = 32768;
|
||||
AutoReconnect = true;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
public TcpSshPropertiesConfig()
|
||||
{
|
||||
BufferSize = 32768;
|
||||
AutoReconnect = true;
|
||||
AutoReconnectIntervalMs = 5000;
|
||||
Username = "";
|
||||
Password = "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ PepperDash Technology Corporation reserves all rights under applicable laws.
|
||||
------------------------------------ */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
using System.Text.RegularExpressions;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using PepperDash.Core.Logging;
|
||||
@@ -209,7 +210,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
/// <summary>
|
||||
/// private Timer for auto reconnect
|
||||
/// </summary>
|
||||
Timer RetryTimer;
|
||||
CTimer RetryTimer;
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -236,13 +237,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
/// </summary>
|
||||
public int HeartbeatInterval = 50000;
|
||||
|
||||
Timer HeartbeatSendTimer;
|
||||
Timer HeartbeatAckTimer;
|
||||
CTimer HeartbeatSendTimer;
|
||||
CTimer HeartbeatAckTimer;
|
||||
/// <summary>
|
||||
/// Used to force disconnection on a dead connect attempt
|
||||
/// </summary>
|
||||
Timer ConnectFailTimer;
|
||||
Timer WaitForSharedKey;
|
||||
CTimer ConnectFailTimer;
|
||||
CTimer WaitForSharedKey;
|
||||
private int ConnectionCount;
|
||||
/// <summary>
|
||||
/// Internal secure client
|
||||
@@ -302,7 +303,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping || programEventType == eProgramStatusEventType.Paused)
|
||||
{
|
||||
this.LogInformation("Program stopping. Closing Client connection");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing Client connection");
|
||||
ProgramIsStopping = true;
|
||||
Disconnect();
|
||||
}
|
||||
@@ -315,17 +316,17 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
public void Connect()
|
||||
{
|
||||
ConnectionCount++;
|
||||
this.LogDebug("Attempting connect Count:{0}", ConnectionCount);
|
||||
Debug.Console(2, this, "Attempting connect Count:{0}", ConnectionCount);
|
||||
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
this.LogInformation("Already connected. Ignoring.");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already connected. Ignoring.");
|
||||
return;
|
||||
}
|
||||
if (IsTryingToConnect)
|
||||
{
|
||||
this.LogInformation("Already trying to connect. Ignoring.");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Notice, "Already trying to connect. Ignoring.");
|
||||
return;
|
||||
}
|
||||
try
|
||||
@@ -338,17 +339,17 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
if (string.IsNullOrEmpty(Hostname))
|
||||
{
|
||||
this.LogWarning("DynamicTcpClient: No address set");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No address set");
|
||||
return;
|
||||
}
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
this.LogWarning("DynamicTcpClient: Invalid port");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: Invalid port");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||
{
|
||||
this.LogWarning("DynamicTcpClient: No Shared Key set");
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Warning, "DynamicTcpClient: No Shared Key set");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -369,10 +370,9 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
|
||||
//var timeOfConnect = DateTime.Now.ToString("HH:mm:ss.fff");
|
||||
|
||||
ConnectFailTimer = new Timer(30000) { AutoReset = false };
|
||||
ConnectFailTimer.Elapsed += (s, e) =>
|
||||
ConnectFailTimer = new CTimer(o =>
|
||||
{
|
||||
this.LogError("Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Connect attempt has not finished after 30sec Count:{0}", ConnectionCount);
|
||||
if (IsTryingToConnect)
|
||||
{
|
||||
IsTryingToConnect = false;
|
||||
@@ -384,13 +384,12 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
//SecureClient.DisconnectFromServer();
|
||||
//CheckClosedAndTryReconnect();
|
||||
}
|
||||
};
|
||||
ConnectFailTimer.Start();
|
||||
}, 30000);
|
||||
|
||||
this.LogDebug("Making Connection Count:{0}", ConnectionCount);
|
||||
Debug.Console(2, this, "Making Connection Count:{0}", ConnectionCount);
|
||||
Client.ConnectToServerAsync(o =>
|
||||
{
|
||||
this.LogDebug("ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||
Debug.Console(2, this, "ConnectToServerAsync Count:{0} Ran!", ConnectionCount);
|
||||
|
||||
if (ConnectFailTimer != null)
|
||||
{
|
||||
@@ -400,22 +399,22 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
|
||||
if (o.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
this.LogVerbose("Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||
Debug.Console(2, this, "Client connected to {0} on port {1}", o.AddressClientConnectedTo, o.LocalPortNumberOfClient);
|
||||
o.ReceiveDataAsync(Receive);
|
||||
|
||||
if (SharedKeyRequired)
|
||||
{
|
||||
WaitingForSharedKeyResponse = true;
|
||||
WaitForSharedKey = new Timer(15000) { AutoReset = false };
|
||||
WaitForSharedKey.Elapsed += (s, e) =>
|
||||
WaitForSharedKey = new CTimer(timer =>
|
||||
{
|
||||
this.LogWarning("Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Shared key exchange timer expired. IsReadyForCommunication={0}", IsReadyForCommunication);
|
||||
// Debug.Console(1, this, "Connect attempt failed {0}", c.ClientStatus);
|
||||
// This is the only case where we should call DisconectFromServer...Event handeler will trigger the cleanup
|
||||
o.DisconnectFromServer();
|
||||
//CheckClosedAndTryReconnect();
|
||||
//OnClientReadyForcommunications(false); // Should send false event
|
||||
};
|
||||
WaitForSharedKey.Start();
|
||||
}, 15000);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -429,15 +428,14 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogWarning("Connect attempt failed {0}", o.ClientStatus);
|
||||
Debug.Console(1, this, "Connect attempt failed {0}", o.ClientStatus);
|
||||
CheckClosedAndTryReconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Client connection exception: {0}", ex.Message);
|
||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
||||
Debug.Console(0, this, Debug.ErrorLogLevel.Error, "Client connection exception: {0}", ex.Message);
|
||||
IsTryingToConnect = false;
|
||||
CheckClosedAndTryReconnect();
|
||||
}
|
||||
@@ -474,7 +472,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
if (Client != null)
|
||||
{
|
||||
//SecureClient.DisconnectFromServer();
|
||||
this.LogVerbose("Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||
Debug.Console(2, this, "Disconnecting Client {0}", DisconnectCalledByUser ? ", Called by user" : "");
|
||||
Client.SocketStatusChange -= Client_SocketStatusChange;
|
||||
Client.Dispose();
|
||||
Client = null;
|
||||
@@ -496,22 +494,20 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
{
|
||||
if (Client != null)
|
||||
{
|
||||
this.LogVerbose("Cleaning up remotely closed/failed connection.");
|
||||
Debug.Console(2, this, "Cleaning up remotely closed/failed connection.");
|
||||
Cleanup();
|
||||
}
|
||||
if (!DisconnectCalledByUser && AutoReconnect)
|
||||
{
|
||||
var halfInterval = AutoReconnectIntervalMs / 2;
|
||||
var rndTime = new Random().Next(-halfInterval, halfInterval) + AutoReconnectIntervalMs;
|
||||
this.LogVerbose("Attempting reconnect in {0} ms, randomized", rndTime);
|
||||
Debug.Console(2, this, "Attempting reconnect in {0} ms, randomized", rndTime);
|
||||
if (RetryTimer != null)
|
||||
{
|
||||
RetryTimer.Stop();
|
||||
RetryTimer = null;
|
||||
}
|
||||
RetryTimer = new Timer(rndTime) { AutoReset = false };
|
||||
RetryTimer.Elapsed += (s, e) => Connect();
|
||||
RetryTimer.Start();
|
||||
RetryTimer = new CTimer(o => Connect(), rndTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,18 +526,18 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
{
|
||||
var bytes = client.IncomingDataBuffer.Take(numBytes).ToArray();
|
||||
str = Encoding.GetEncoding(28591).GetString(bytes, 0, bytes.Length);
|
||||
this.LogVerbose("Client Received:\r--------\r{0}\r--------", str);
|
||||
Debug.Console(2, this, "Client Received:\r--------\r{0}\r--------", str);
|
||||
if (!string.IsNullOrEmpty(checkHeartbeat(str)))
|
||||
{
|
||||
if (SharedKeyRequired && str == "SharedKey:")
|
||||
{
|
||||
this.LogVerbose("Server asking for shared key, sending");
|
||||
Debug.Console(2, this, "Server asking for shared key, sending");
|
||||
SendText(SharedKey + "\n");
|
||||
}
|
||||
else if (SharedKeyRequired && str == "Shared Key Match")
|
||||
{
|
||||
StopWaitForSharedKeyTimer();
|
||||
this.LogVerbose("Shared key confirmed. Ready for communication");
|
||||
Debug.Console(2, this, "Shared key confirmed. Ready for communication");
|
||||
OnClientReadyForcommunications(true); // Successful key exchange
|
||||
}
|
||||
else
|
||||
@@ -557,8 +553,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
||||
Debug.Console(1, this, "Error receiving data: {1}. Error: {0}", ex.Message, str);
|
||||
}
|
||||
}
|
||||
if (client.ClientStatus == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
@@ -569,19 +564,15 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
{
|
||||
if (HeartbeatEnabled)
|
||||
{
|
||||
this.LogVerbose("Starting Heartbeat");
|
||||
Debug.Console(2, this, "Starting Heartbeat");
|
||||
if (HeartbeatSendTimer == null)
|
||||
{
|
||||
|
||||
HeartbeatSendTimer = new Timer(HeartbeatInterval) { AutoReset = true };
|
||||
HeartbeatSendTimer.Elapsed += (s, e) => SendHeartbeat(null);
|
||||
HeartbeatSendTimer.Start();
|
||||
HeartbeatSendTimer = new CTimer(this.SendHeartbeat, null, HeartbeatInterval, HeartbeatInterval);
|
||||
}
|
||||
if (HeartbeatAckTimer == null)
|
||||
{
|
||||
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
|
||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
||||
HeartbeatAckTimer.Start();
|
||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,13 +582,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
|
||||
if (HeartbeatSendTimer != null)
|
||||
{
|
||||
this.LogVerbose("Stoping Heartbeat Send");
|
||||
Debug.Console(2, this, "Stoping Heartbeat Send");
|
||||
HeartbeatSendTimer.Stop();
|
||||
HeartbeatSendTimer = null;
|
||||
}
|
||||
if (HeartbeatAckTimer != null)
|
||||
{
|
||||
this.LogVerbose("Stoping Heartbeat Ack");
|
||||
Debug.Console(2, this, "Stoping Heartbeat Ack");
|
||||
HeartbeatAckTimer.Stop();
|
||||
HeartbeatAckTimer = null;
|
||||
}
|
||||
@@ -606,7 +597,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
void SendHeartbeat(object notused)
|
||||
{
|
||||
this.SendText(HeartbeatString);
|
||||
this.LogVerbose("Sending Heartbeat");
|
||||
Debug.Console(2, this, "Sending Heartbeat");
|
||||
|
||||
}
|
||||
|
||||
@@ -625,17 +616,13 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
{
|
||||
if (HeartbeatAckTimer != null)
|
||||
{
|
||||
HeartbeatAckTimer.Stop();
|
||||
HeartbeatAckTimer.Interval = HeartbeatInterval * 2;
|
||||
HeartbeatAckTimer.Start();
|
||||
HeartbeatAckTimer.Reset(HeartbeatInterval * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
HeartbeatAckTimer = new Timer(HeartbeatInterval * 2) { AutoReset = true };
|
||||
HeartbeatAckTimer.Elapsed += (s, e) => HeartbeatAckTimerFail(null);
|
||||
HeartbeatAckTimer.Start();
|
||||
HeartbeatAckTimer = new CTimer(HeartbeatAckTimerFail, null, (HeartbeatInterval * 2), (HeartbeatInterval * 2));
|
||||
}
|
||||
this.LogVerbose("Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||
Debug.Console(2, this, "Heartbeat Received: {0}, from Server", HeartbeatString);
|
||||
return remainingText;
|
||||
}
|
||||
}
|
||||
@@ -643,8 +630,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
|
||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
||||
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||
}
|
||||
return received;
|
||||
}
|
||||
@@ -658,7 +644,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
this.LogWarning("Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "Heartbeat not received from Server...DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE");
|
||||
SendText("Heartbeat not received by server, closing connection");
|
||||
CheckClosedAndTryReconnect();
|
||||
}
|
||||
@@ -666,8 +652,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Heartbeat timeout Error on Client: {0}, {1}", Key, ex.Message);
|
||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
||||
ErrorLog.Error("Heartbeat timeout Error on Client: {0}, {1}", Key, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,15 +685,14 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
// HOW IN THE HELL DO WE CATCH AN EXCEPTION IN SENDING?????
|
||||
if (n <= 0)
|
||||
{
|
||||
this.LogWarning("[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "[{0}] Sent zero bytes. Was there an error?", this.Key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
||||
Debug.Console(0, this, "Error sending text: {1}. Error: {0}", ex.Message, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -727,8 +711,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error sending bytes. Error: {0}", ex.Message);
|
||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
||||
Debug.Console(0, this, "Error sending bytes. Error: {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -747,7 +730,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
try
|
||||
{
|
||||
this.LogVerbose("Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||
Debug.Console(2, this, "Socket status change: {0} ({1})", client.ClientStatus, (ushort)(client.ClientStatus));
|
||||
|
||||
OnConnectionChange();
|
||||
|
||||
@@ -761,8 +744,7 @@ public class GenericTcpIpClient_ForServer : Device, IAutoReconnect
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error in socket status change callback. Error: {0}", ex.Message);
|
||||
this.LogVerbose("Stack Trace: {0}", ex.StackTrace);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Error in socket status change callback. Error: {0}\r\r{1}", ex, ex.InnerException);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using PepperDash.Core.Logging;
|
||||
@@ -62,14 +61,9 @@ public class GenericTcpIpServer : Device
|
||||
#region Properties/Variables
|
||||
|
||||
/// <summary>
|
||||
/// Server listen lock
|
||||
///
|
||||
/// </summary>
|
||||
object _serverLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Broadcast lock
|
||||
/// </summary>
|
||||
private readonly object _broadcastLock = new();
|
||||
CCriticalSection ServerCCSection = new CCriticalSection();
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -80,7 +74,7 @@ public class GenericTcpIpServer : Device
|
||||
/// <summary>
|
||||
/// Timer to operate the bandaid monitor client in a loop.
|
||||
/// </summary>
|
||||
Timer MonitorClientTimer;
|
||||
CTimer MonitorClientTimer;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -250,7 +244,7 @@ public class GenericTcpIpServer : Device
|
||||
public string HeartbeatStringToMatch { get; set; }
|
||||
|
||||
//private timers for Heartbeats per client
|
||||
Dictionary<uint, Timer> HeartbeatTimerDictionary = new Dictionary<uint, Timer>();
|
||||
Dictionary<uint, CTimer> HeartbeatTimerDictionary = new Dictionary<uint, CTimer>();
|
||||
|
||||
//flags to show the secure server is waiting for client at index to send the shared key
|
||||
List<uint> WaitingForSharedKey = new List<uint>();
|
||||
@@ -371,12 +365,12 @@ public class GenericTcpIpServer : Device
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogError("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.LogError("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||
ErrorLog.Error("Could not initialize server with key: {0}", serverConfigObject.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,18 +379,19 @@ public class GenericTcpIpServer : Device
|
||||
/// </summary>
|
||||
public void Listen()
|
||||
{
|
||||
lock (_serverLock)
|
||||
{
|
||||
ServerCCSection.Enter();
|
||||
try
|
||||
{
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
this.LogError("Server '{0}': Invalid port", Key);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': Invalid port", Key);
|
||||
ErrorLog.Warn(string.Format("Server '{0}': Invalid port", Key));
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(SharedKey) && SharedKeyRequired)
|
||||
{
|
||||
this.LogError("Server '{0}': No Shared Key set", Key);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Server '{0}': No Shared Key set", Key);
|
||||
ErrorLog.Warn(string.Format("Server '{0}': No Shared Key set", Key));
|
||||
return;
|
||||
}
|
||||
if (IsListening)
|
||||
@@ -422,15 +417,18 @@ public class GenericTcpIpServer : Device
|
||||
ServerStopped = false;
|
||||
myTcpServer.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||
OnServerStateChange(myTcpServer.State);
|
||||
this.LogInformation("TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "TCP Server Status: {0}, Socket Status: {1}", myTcpServer.State, myTcpServer.ServerSocketStatus);
|
||||
|
||||
// StartMonitorClient();
|
||||
|
||||
|
||||
ServerCCSection.Leave();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error with Dynamic Server: {0}", ex.Message);
|
||||
ServerCCSection.Leave();
|
||||
ErrorLog.Error("{1} Error with Dynamic Server: {0}", ex.ToString(), Key);
|
||||
}
|
||||
} // end lock
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -440,18 +438,18 @@ public class GenericTcpIpServer : Device
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogDebug("Stopping Listener");
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Stopping Listener");
|
||||
if (myTcpServer != null)
|
||||
{
|
||||
myTcpServer.Stop();
|
||||
this.LogDebug("Server State: {0}", myTcpServer.State);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server State: {0}", myTcpServer.State);
|
||||
OnServerStateChange(myTcpServer.State);
|
||||
}
|
||||
ServerStopped = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error stopping server. Error: {0}", ex.Message);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error stopping server. Error: {0}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,11 +462,11 @@ public class GenericTcpIpServer : Device
|
||||
try
|
||||
{
|
||||
myTcpServer.Disconnect(client);
|
||||
this.LogVerbose("Disconnected client index: {0}", client);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", client);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", client, ex.Message);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", client, ex);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
@@ -476,7 +474,7 @@ public class GenericTcpIpServer : Device
|
||||
/// </summary>
|
||||
public void DisconnectAllClientsForShutdown()
|
||||
{
|
||||
this.LogInformation("Disconnecting All Clients");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Disconnecting All Clients");
|
||||
if (myTcpServer != null)
|
||||
{
|
||||
myTcpServer.SocketStatusChange -= TcpServer_SocketStatusChange;
|
||||
@@ -488,17 +486,17 @@ public class GenericTcpIpServer : Device
|
||||
try
|
||||
{
|
||||
myTcpServer.Disconnect(i);
|
||||
this.LogVerbose("Disconnected client index: {0}", i);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected client index: {0}", i);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error Disconnecting client index: {0}. Error: {1}", i, ex.Message);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Disconnecting client index: {0}. Error: {1}", i, ex);
|
||||
}
|
||||
}
|
||||
this.LogVerbose("Server Status: {0}", myTcpServer.ServerSocketStatus);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Server Status: {0}", myTcpServer.ServerSocketStatus);
|
||||
}
|
||||
|
||||
this.LogInformation("Disconnected All Clients");
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Disconnected All Clients");
|
||||
ConnectedClientsIndexes.Clear();
|
||||
|
||||
if (!ProgramIsStopping)
|
||||
@@ -516,8 +514,8 @@ public class GenericTcpIpServer : Device
|
||||
/// <param name="text"></param>
|
||||
public void BroadcastText(string text)
|
||||
{
|
||||
lock (_broadcastLock)
|
||||
{
|
||||
CCriticalSection CCBroadcast = new CCriticalSection();
|
||||
CCBroadcast.Enter();
|
||||
try
|
||||
{
|
||||
if (ConnectedClientsIndexes.Count > 0)
|
||||
@@ -533,12 +531,13 @@ public class GenericTcpIpServer : Device
|
||||
}
|
||||
}
|
||||
}
|
||||
CCBroadcast.Leave();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
||||
CCBroadcast.Leave();
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Broadcasting messages from server. Error: {0}", ex.Message);
|
||||
}
|
||||
} // end lock
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -559,7 +558,7 @@ public class GenericTcpIpServer : Device
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
||||
Debug.Console(2, this, "Error sending text to client. Text: {1}. Error: {0}", ex.Message, text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,19 +576,13 @@ public class GenericTcpIpServer : Device
|
||||
if (noDelimiter.Contains(HeartbeatStringToMatch))
|
||||
{
|
||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
{
|
||||
HeartbeatTimerDictionary[clientIndex].Stop();
|
||||
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
|
||||
HeartbeatTimerDictionary[clientIndex].Start();
|
||||
}
|
||||
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||
else
|
||||
{
|
||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
||||
heartbeatTimer.Start();
|
||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||
}
|
||||
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
||||
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", HeartbeatStringToMatch, clientIndex);
|
||||
// Return Heartbeat
|
||||
SendTextToClient(HeartbeatStringToMatch, clientIndex);
|
||||
return remainingText;
|
||||
@@ -598,25 +591,19 @@ public class GenericTcpIpServer : Device
|
||||
else
|
||||
{
|
||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
{
|
||||
HeartbeatTimerDictionary[clientIndex].Stop();
|
||||
HeartbeatTimerDictionary[clientIndex].Interval = HeartbeatRequiredIntervalMs;
|
||||
HeartbeatTimerDictionary[clientIndex].Start();
|
||||
}
|
||||
HeartbeatTimerDictionary[clientIndex].Reset(HeartbeatRequiredIntervalMs);
|
||||
else
|
||||
{
|
||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
||||
heartbeatTimer.Start();
|
||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
||||
CTimer HeartbeatTimer = new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs);
|
||||
HeartbeatTimerDictionary.Add(clientIndex, HeartbeatTimer);
|
||||
}
|
||||
this.LogVerbose("Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
||||
Debug.Console(1, this, "Heartbeat Received: {0}, from client index: {1}", received, clientIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error checking heartbeat: {0}", ex.Message);
|
||||
Debug.Console(1, this, "Error checking heartbeat: {0}", ex.Message);
|
||||
}
|
||||
return received;
|
||||
}
|
||||
@@ -628,11 +615,11 @@ public class GenericTcpIpServer : Device
|
||||
/// <returns>IP address of the client</returns>
|
||||
public string GetClientIPAddress(uint clientIndex)
|
||||
{
|
||||
this.LogVerbose("GetClientIPAddress Index: {0}", clientIndex);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress Index: {0}", clientIndex);
|
||||
if (!SharedKeyRequired || (SharedKeyRequired && ClientReadyAfterKeyExchange.Contains(clientIndex)))
|
||||
{
|
||||
var ipa = this.myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||
this.LogVerbose("GetClientIPAddress IPAddreess: {0}", ipa);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "GetClientIPAddress IPAddreess: {0}", ipa);
|
||||
return ipa;
|
||||
|
||||
}
|
||||
@@ -655,13 +642,14 @@ public class GenericTcpIpServer : Device
|
||||
clientIndex = (uint)o;
|
||||
address = myTcpServer.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex);
|
||||
|
||||
this.LogWarning("Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Heartbeat not received for Client index {2} IP: {0}, DISCONNECTING BECAUSE HEARTBEAT REQUIRED IS TRUE {1}",
|
||||
address, string.IsNullOrEmpty(HeartbeatStringToMatch) ? "" : ("HeartbeatStringToMatch: " + HeartbeatStringToMatch), clientIndex);
|
||||
|
||||
if (myTcpServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
SendTextToClient("Heartbeat not received by server, closing connection", clientIndex);
|
||||
|
||||
var discoResult = myTcpServer.Disconnect(clientIndex);
|
||||
//Debug.Console(1, this, "{0}", discoResult);
|
||||
|
||||
if (HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
{
|
||||
@@ -672,8 +660,7 @@ public class GenericTcpIpServer : Device
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message);
|
||||
this.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
||||
ErrorLog.Error("{3}: Heartbeat timeout Error on Client Index: {0}, at address: {1}, error: {2}", clientIndex, address, ex.Message, Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,7 +678,7 @@ public class GenericTcpIpServer : Device
|
||||
try
|
||||
{
|
||||
|
||||
this.LogInformation("SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "SecureServerSocketStatusChange Index:{0} status:{1} Port:{2} IP:{3}", clientIndex, serverSocketStatus, this.myTcpServer.GetPortNumberServerAcceptedConnectionFromForSpecificClient(clientIndex), this.myTcpServer.GetLocalAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||
if (serverSocketStatus != SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
{
|
||||
if (ConnectedClientsIndexes.Contains(clientIndex))
|
||||
@@ -710,7 +697,7 @@ public class GenericTcpIpServer : Device
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error in Socket Status Change Callback. Error: {0}", ex);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Change Callback. Error: {0}", ex);
|
||||
}
|
||||
onConnectionChange(clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||
}
|
||||
@@ -727,7 +714,7 @@ public class GenericTcpIpServer : Device
|
||||
{
|
||||
try
|
||||
{
|
||||
this.LogDebug("ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "ConnectCallback: IPAddress: {0}. Index: {1}. Status: {2}",
|
||||
server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex),
|
||||
clientIndex, server.GetServerSocketStatusForSpecificClient(clientIndex));
|
||||
if (clientIndex != 0)
|
||||
@@ -747,21 +734,17 @@ public class GenericTcpIpServer : Device
|
||||
}
|
||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("SharedKey:");
|
||||
server.SendDataAsync(clientIndex, b, b.Length, (x, y, z) => { });
|
||||
this.LogDebug("Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Sent Shared Key Request to client at {0}", server.GetAddressServerAcceptedConnectionFromForSpecificClient(clientIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogDebug("Client at index {0} is ready for communications", clientIndex);
|
||||
OnServerClientReadyForCommunications(clientIndex);
|
||||
}
|
||||
if (HeartbeatRequired)
|
||||
{
|
||||
if (!HeartbeatTimerDictionary.ContainsKey(clientIndex))
|
||||
{
|
||||
var heartbeatTimer = new Timer(HeartbeatRequiredIntervalMs) { AutoReset = false };
|
||||
heartbeatTimer.Elapsed += (s, e) => HeartbeatTimer_CallbackFunction(clientIndex);
|
||||
heartbeatTimer.Start();
|
||||
HeartbeatTimerDictionary.Add(clientIndex, heartbeatTimer);
|
||||
HeartbeatTimerDictionary.Add(clientIndex, new CTimer(HeartbeatTimer_CallbackFunction, clientIndex, HeartbeatRequiredIntervalMs));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,7 +753,7 @@ public class GenericTcpIpServer : Device
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogError("Client attempt faulty.");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Error, "Client attempt faulty.");
|
||||
if (!ServerStopped)
|
||||
{
|
||||
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||
@@ -780,15 +763,15 @@ public class GenericTcpIpServer : Device
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error in Socket Status Connect Callback. Error: {0}", ex.Message);
|
||||
this.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error in Socket Status Connect Callback. Error: {0}", ex);
|
||||
}
|
||||
//Debug.Console(1, this, Debug.ErrorLogLevel, "((((((Server State bitfield={0}; maxclient={1}; ServerStopped={2}))))))",
|
||||
// server.State,
|
||||
// MaxClients,
|
||||
// ServerStopped);
|
||||
if ((server.State & ServerState.SERVER_LISTENING) != ServerState.SERVER_LISTENING && MaxClients > 1 && !ServerStopped)
|
||||
{
|
||||
this.LogDebug("Waiting for next connection");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Waiting for next connection");
|
||||
server.WaitForConnectionAsync(IPAddress.Any, TcpConnectCallback);
|
||||
|
||||
}
|
||||
@@ -819,7 +802,7 @@ public class GenericTcpIpServer : Device
|
||||
if (received != SharedKey)
|
||||
{
|
||||
byte[] b = Encoding.GetEncoding(28591).GetBytes("Shared key did not match server. Disconnecting");
|
||||
this.LogWarning("Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Warning, "Client at index {0} Shared key did not match the server, disconnecting client. Key: {1}", clientIndex, received);
|
||||
myTCPServer.SendData(clientIndex, b, b.Length);
|
||||
myTCPServer.Disconnect(clientIndex);
|
||||
return;
|
||||
@@ -829,7 +812,7 @@ public class GenericTcpIpServer : Device
|
||||
byte[] success = Encoding.GetEncoding(28591).GetBytes("Shared Key Match");
|
||||
myTCPServer.SendDataAsync(clientIndex, success, success.Length, null);
|
||||
OnServerClientReadyForCommunications(clientIndex);
|
||||
this.LogDebug("Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Client with index {0} provided the shared key and successfully connected to the server", clientIndex);
|
||||
}
|
||||
|
||||
else if (!string.IsNullOrEmpty(checkHeartbeat(clientIndex, received)))
|
||||
@@ -837,7 +820,7 @@ public class GenericTcpIpServer : Device
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "Error Receiving data: {0}. Error: {1}", received, ex);
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error, "Error Receiving data: {0}. Error: {1}", received, ex);
|
||||
}
|
||||
if (myTCPServer.GetServerSocketStatusForSpecificClient(clientIndex) == SocketStatus.SOCKET_STATUS_CONNECTED)
|
||||
myTCPServer.ReceiveDataAsync(clientIndex, TcpServerReceivedDataAsyncCallback);
|
||||
@@ -918,7 +901,7 @@ public class GenericTcpIpServer : Device
|
||||
if (MonitorClient != null)
|
||||
MonitorClient.Disconnect();
|
||||
|
||||
this.LogInformation("Program stopping. Closing server");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Program stopping. Closing server");
|
||||
KillServer();
|
||||
}
|
||||
}
|
||||
@@ -944,9 +927,7 @@ public class GenericTcpIpServer : Device
|
||||
{
|
||||
return;
|
||||
}
|
||||
MonitorClientTimer = new Timer(60000) { AutoReset = false };
|
||||
MonitorClientTimer.Elapsed += (s, e) => RunMonitorClient();
|
||||
MonitorClientTimer.Start();
|
||||
MonitorClientTimer = new CTimer(o => RunMonitorClient(), 60000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -961,7 +942,7 @@ public class GenericTcpIpServer : Device
|
||||
//MonitorClient.ConnectionChange += MonitorClient_ConnectionChange;
|
||||
MonitorClient.ClientReadyForCommunications += MonitorClient_IsReadyForComm;
|
||||
|
||||
this.LogDebug("Starting monitor check");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Starting monitor check");
|
||||
|
||||
MonitorClient.Connect();
|
||||
// From here MonitorCLient either connects or hangs, MonitorClient will call back
|
||||
@@ -988,7 +969,7 @@ public class GenericTcpIpServer : Device
|
||||
{
|
||||
if (args.IsReady)
|
||||
{
|
||||
this.LogInformation("Monitor client connection success. Disconnecting in 2s");
|
||||
Debug.Console(1, this, Debug.ErrorLogLevel.Notice, "Monitor client connection success. Disconnecting in 2s");
|
||||
MonitorClientTimer.Stop();
|
||||
MonitorClientTimer = null;
|
||||
MonitorClientFailureCount = 0;
|
||||
@@ -1009,13 +990,13 @@ public class GenericTcpIpServer : Device
|
||||
StopMonitorClient();
|
||||
if (MonitorClientFailureCount < MonitorClientMaxFailureCount)
|
||||
{
|
||||
this.LogWarning("Monitor client connection has hung {0} time{1}, maximum {2}",
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Warning, "Monitor client connection has hung {0} time{1}, maximum {2}",
|
||||
MonitorClientFailureCount, MonitorClientFailureCount > 1 ? "s" : "", MonitorClientMaxFailureCount);
|
||||
StartMonitorClient();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogError(
|
||||
Debug.Console(2, this, Debug.ErrorLogLevel.Error,
|
||||
"\r***************************\rMonitor client connection has hung a maximum of {0} times.\r***************************",
|
||||
MonitorClientMaxFailureCount);
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core.Logging;
|
||||
using Required = NewtonsoftJson::Newtonsoft.Json.Required;
|
||||
|
||||
namespace PepperDash.Core;
|
||||
|
||||
@@ -60,7 +57,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a GenericUdpReceiveTextExtraArgs
|
||||
///
|
||||
/// </summary>
|
||||
public ushort UStatus
|
||||
{
|
||||
@@ -183,7 +180,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
if (programEventType != eProgramStatusEventType.Stopping)
|
||||
return;
|
||||
|
||||
this.LogInformation("Program stopping. Disabling Server");
|
||||
Debug.Console(1, this, "Program stopping. Disabling Server");
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
@@ -199,20 +196,20 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
|
||||
if (string.IsNullOrEmpty(Hostname))
|
||||
{
|
||||
this.LogWarning("GenericUdpServer '{0}': No address set", Key);
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': No address set", Key);
|
||||
return;
|
||||
}
|
||||
if (Port < 1 || Port > 65535)
|
||||
{
|
||||
{
|
||||
this.LogWarning("GenericUdpServer '{0}': Invalid port", Key);
|
||||
Debug.Console(1, Debug.ErrorLogLevel.Warning, "GenericUdpServer '{0}': Invalid port", Key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var status = Server.EnableUDPServer(Hostname, Port);
|
||||
|
||||
this.LogVerbose("SocketErrorCode: {0}", status);
|
||||
Debug.Console(2, this, "SocketErrorCode: {0}", status);
|
||||
if (status == SocketErrorCodes.SOCKET_OK)
|
||||
IsConnected = true;
|
||||
|
||||
@@ -247,7 +244,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
/// <param name="numBytes"></param>
|
||||
void Receive(UDPServer server, int numBytes)
|
||||
{
|
||||
this.LogVerbose("Received {0} bytes", numBytes);
|
||||
Debug.Console(2, this, "Received {0} bytes", numBytes);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -263,13 +260,13 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
if (dataRecivedExtra != null)
|
||||
dataRecivedExtra(this, new GenericUdpReceiveTextExtraArgs(str, sourceIp, sourcePort, bytes));
|
||||
|
||||
this.LogVerbose("Bytes: {0}", bytes.ToString());
|
||||
Debug.Console(2, this, "Bytes: {0}", bytes.ToString());
|
||||
var bytesHandler = BytesReceived;
|
||||
if (bytesHandler != null)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
{
|
||||
this.LogInformation("Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
Debug.Console(0, this, "Received {1} bytes: '{0}'", ComTextHelper.GetEscapedText(bytes), bytes.Length);
|
||||
}
|
||||
bytesHandler(this, new GenericCommMethodReceiveBytesArgs(bytes));
|
||||
}
|
||||
@@ -277,7 +274,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
if (textHandler != null)
|
||||
{
|
||||
if (StreamDebugging.RxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||
Debug.Console(0, this, "Received {1} characters of text: '{0}'", ComTextHelper.GetDebugText(str), str.Length);
|
||||
textHandler(this, new GenericCommMethodReceiveTextArgs(str));
|
||||
}
|
||||
}
|
||||
@@ -302,7 +299,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
if (IsConnected && Server != null)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
this.LogVerbose("Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||
Debug.Console(0, this, "Sending {0} characters of text: '{1}'", text.Length, ComTextHelper.GetDebugText(text));
|
||||
|
||||
Server.SendData(bytes, bytes.Length);
|
||||
}
|
||||
@@ -315,7 +312,7 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
if (StreamDebugging.TxStreamDebuggingIsEnabled)
|
||||
this.LogInformation("Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
Debug.Console(0, this, "Sending {0} bytes: '{1}'", bytes.Length, ComTextHelper.GetEscapedText(bytes));
|
||||
|
||||
if (IsConnected && Server != null)
|
||||
Server.SendData(bytes, bytes.Length);
|
||||
@@ -353,18 +350,18 @@ public class GenericUdpServer : Device, ISocketStatusWithStreamDebugging
|
||||
/// <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;
|
||||
}
|
||||
{
|
||||
Text = text;
|
||||
IpAddress = ipAddress;
|
||||
Port = port;
|
||||
Bytes = bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stupid S+ Constructor
|
||||
/// </summary>
|
||||
public GenericUdpReceiveTextExtraArgs() { }
|
||||
}
|
||||
/// <summary>
|
||||
/// Stupid S+ Constructor
|
||||
/// </summary>
|
||||
public GenericUdpReceiveTextExtraArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
using System;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for stream debugging
|
||||
/// </summary>
|
||||
public static class StreamDebuggingExtensions
|
||||
{
|
||||
private static readonly string app = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? $"App {InitialParametersClass.ApplicationNumber}" : $"{InitialParametersClass.RoomId}";
|
||||
|
||||
/// <summary>
|
||||
/// Print the sent bytes to the console
|
||||
/// </summary>
|
||||
/// <param name="comms">comms device</param>
|
||||
/// <param name="bytes">bytes to print</param>
|
||||
public static void PrintSentBytes(this IStreamDebugging comms, byte[] bytes)
|
||||
{
|
||||
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
|
||||
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
|
||||
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print the received bytes to the console
|
||||
/// </summary>
|
||||
/// <param name="comms">comms device</param>
|
||||
/// <param name="bytes">bytes to print</param>
|
||||
public static void PrintReceivedBytes(this IStreamDebugging comms, byte[] bytes)
|
||||
{
|
||||
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
|
||||
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
|
||||
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received {bytes.Length} bytes: '{ComTextHelper.GetEscapedText(bytes)}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print the sent text to the console
|
||||
/// </summary>
|
||||
/// <param name="comms">comms device</param>
|
||||
/// <param name="text">text to print</param>
|
||||
public static void PrintSentText(this IStreamDebugging comms, string text)
|
||||
{
|
||||
if (!comms.StreamDebugging.TxStreamDebuggingIsEnabled) return;
|
||||
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
|
||||
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Sending Text: '{ComTextHelper.GetDebugText(text)}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print the received text to the console
|
||||
/// </summary>
|
||||
/// <param name="comms">comms device</param>
|
||||
/// <param name="text">text to print</param>
|
||||
public static void PrintReceivedText(this IStreamDebugging comms, string text)
|
||||
{
|
||||
if (!comms.StreamDebugging.RxStreamDebuggingIsEnabled) return;
|
||||
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
|
||||
CrestronConsole.PrintLine($"[{timestamp}][{app}][{comms.Key}] Received Text: '{ComTextHelper.GetDebugText(text)}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core;
|
||||
|
||||
|
||||
@@ -74,13 +74,5 @@ public enum eControlMethod
|
||||
/// <summary>
|
||||
/// Secure TCP/IP
|
||||
/// </summary>
|
||||
SecureTcpIp,
|
||||
/// <summary>
|
||||
/// Crestron COM bridge
|
||||
/// </summary>
|
||||
ComBridge,
|
||||
/// <summary>
|
||||
/// Crestron Infinet EX device
|
||||
/// </summary>
|
||||
InfinetEx
|
||||
SecureTcpIp
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace PepperDash.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// The available settings for stream debugging data format types
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum eStreamDebuggingDataTypeSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug data in byte format
|
||||
/// </summary>
|
||||
Bytes = 0,
|
||||
/// <summary>
|
||||
/// Debug data in text format
|
||||
/// </summary>
|
||||
Text = 1,
|
||||
/// <summary>
|
||||
/// Debug data in both byte and text formats
|
||||
/// </summary>
|
||||
Both = Bytes | Text
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using JsonConverter = NewtonsoftJson::Newtonsoft.Json.JsonConverterAttribute;
|
||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||
using StringEnumConverter = NewtonsoftJson::Newtonsoft.Json.Converters.StringEnumConverter;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core;
|
||||
|
||||
@@ -43,7 +39,7 @@ public interface ICommunicationReceiver : IKeyed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extends <see cref="ICommunicationReceiver"/> with methods for sending text and bytes to a device.
|
||||
/// Represents a device that uses basic connection
|
||||
/// </summary>
|
||||
public interface IBasicCommunication : ICommunicationReceiver
|
||||
{
|
||||
@@ -58,7 +54,7 @@ public interface IBasicCommunication : ICommunicationReceiver
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
void SendBytes(byte[] bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a device that implements IBasicCommunication and IStreamDebugging
|
||||
@@ -71,7 +67,7 @@ public interface IBasicCommunicationWithStreamDebugging : IBasicCommunication, I
|
||||
/// <summary>
|
||||
/// Represents a device with stream debugging capablities
|
||||
/// </summary>
|
||||
public interface IStreamDebugging : IKeyed
|
||||
public interface IStreamDebugging
|
||||
{
|
||||
/// <summary>
|
||||
/// Object to enable stream debugging
|
||||
@@ -95,7 +91,7 @@ public interface IStreamDebugging : IKeyed
|
||||
/// The current socket status of the client
|
||||
/// </summary>
|
||||
[JsonProperty("clientStatus")]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
SocketStatus ClientStatus { get; }
|
||||
}
|
||||
|
||||
@@ -139,60 +135,59 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
|
||||
Disconnected
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This delegate defines handler for IBasicCommunication status changes
|
||||
/// </summary>
|
||||
/// <param name="comm">Device firing the status change</param>
|
||||
/// <param name="status"></param>
|
||||
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
||||
/// <summary>
|
||||
/// This delegate defines handler for IBasicCommunication status changes
|
||||
/// </summary>
|
||||
/// <param name="comm">Device firing the status change</param>
|
||||
/// <param name="status"></param>
|
||||
public delegate void GenericCommMethodStatusHandler(IBasicCommunication comm, eGenericCommMethodStatusChangeType status);
|
||||
|
||||
/// <summary>
|
||||
/// Event args for bytes received from a communication method
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericCommMethodReceiveBytesArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The bytes received
|
||||
///
|
||||
/// </summary>
|
||||
public byte[] Bytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
public GenericCommMethodReceiveBytesArgs(byte[] bytes)
|
||||
{
|
||||
Bytes = bytes;
|
||||
}
|
||||
{
|
||||
Bytes = bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericCommMethodReceiveBytesArgs() { }
|
||||
}
|
||||
/// <summary>
|
||||
/// S+ Constructor
|
||||
/// </summary>
|
||||
public GenericCommMethodReceiveBytesArgs() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event args for text received
|
||||
///
|
||||
/// </summary>
|
||||
public class GenericCommMethodReceiveTextArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The text received
|
||||
///
|
||||
/// </summary>
|
||||
public string Text { get; private set; }
|
||||
/// <summary>
|
||||
/// The delimiter used to determine the end of a message, if applicable
|
||||
///
|
||||
/// </summary>
|
||||
public string Delimiter { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
public GenericCommMethodReceiveTextArgs(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -214,7 +209,7 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to get escaped text for debugging communication streams
|
||||
///
|
||||
/// </summary>
|
||||
public class ComTextHelper
|
||||
{
|
||||
@@ -248,4 +243,4 @@ public interface ISocketStatusWithStreamDebugging : ISocketStatus, IStreamDebugg
|
||||
{
|
||||
return Regex.Replace(text, @"[^\u0020-\u007E]", a => GetEscapedText(a.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
||||
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
|
||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||
using JToken = NewtonsoftJson::Newtonsoft.Json.Linq.JToken;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
@@ -18,52 +14,35 @@ namespace PepperDash.Core.Config;
|
||||
/// </summary>
|
||||
public class PortalConfigReader
|
||||
{
|
||||
const string template = "template";
|
||||
const string system = "system";
|
||||
const string systemUrl = "system_url";
|
||||
const string templateUrl = "template_url";
|
||||
const string info = "info";
|
||||
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)
|
||||
/// <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
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
using (StreamReader fs = new StreamReader(filePath))
|
||||
{
|
||||
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.
|
||||
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;
|
||||
@@ -88,9 +67,6 @@ namespace PepperDash.Core.Config;
|
||||
/// </summary>
|
||||
/// <param name="doubleConfig"></param>
|
||||
/// <returns></returns>
|
||||
/// <summary>
|
||||
/// MergeConfigs method
|
||||
/// </summary>
|
||||
public static JObject MergeConfigs(JObject doubleConfig)
|
||||
{
|
||||
var system = JObject.FromObject(doubleConfig["system"]);
|
||||
@@ -98,30 +74,30 @@ namespace PepperDash.Core.Config;
|
||||
var merged = new JObject();
|
||||
|
||||
// Put together top-level objects
|
||||
if (system[info] != null)
|
||||
merged.Add(info, Merge(template[info], system[info], info));
|
||||
if (system["info"] != null)
|
||||
merged.Add("info", Merge(template["info"], system["info"], "infO"));
|
||||
else
|
||||
merged.Add(info, template[info]);
|
||||
merged.Add("info", template["info"]);
|
||||
|
||||
merged.Add(devices, MergeArraysOnTopLevelProperty(template[devices] as JArray,
|
||||
system[devices] as JArray, "key", devices));
|
||||
merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray,
|
||||
system["devices"] as JArray, "key", "devices"));
|
||||
|
||||
if (system[rooms] == null)
|
||||
merged.Add(rooms, template[rooms]);
|
||||
if (system["rooms"] == null)
|
||||
merged.Add("rooms", template["rooms"]);
|
||||
else
|
||||
merged.Add(rooms, MergeArraysOnTopLevelProperty(template[rooms] as JArray,
|
||||
system[rooms] as JArray, "key", rooms));
|
||||
merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray,
|
||||
system["rooms"] as JArray, "key", "rooms"));
|
||||
|
||||
if (system[sourceLists] == null)
|
||||
merged.Add(sourceLists, template[sourceLists]);
|
||||
if (system["sourceLists"] == null)
|
||||
merged.Add("sourceLists", template["sourceLists"]);
|
||||
else
|
||||
merged.Add(sourceLists, Merge(template[sourceLists], system[sourceLists], sourceLists));
|
||||
merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"], "sourceLists"));
|
||||
|
||||
if (system[destinationLists] == null)
|
||||
merged.Add(destinationLists, template[destinationLists]);
|
||||
if (system["destinationLists"] == null)
|
||||
merged.Add("destinationLists", template["destinationLists"]);
|
||||
else
|
||||
merged.Add(destinationLists,
|
||||
Merge(template[destinationLists], system[destinationLists], destinationLists));
|
||||
merged.Add("destinationLists",
|
||||
Merge(template["destinationLists"], system["destinationLists"], "destinationLists"));
|
||||
|
||||
|
||||
if (system["cameraLists"] == null)
|
||||
@@ -143,18 +119,19 @@ namespace PepperDash.Core.Config;
|
||||
else if (system["tieLines"] != null)
|
||||
merged.Add("tieLines", system["tieLines"]);
|
||||
else
|
||||
merged.Add(tieLines, new JArray());
|
||||
merged.Add("tieLines", new JArray());
|
||||
|
||||
if (template["joinMaps"] != null)
|
||||
merged.Add("joinMaps", template["joinMaps"]);
|
||||
else
|
||||
merged.Add("joinMaps", new JObject());
|
||||
|
||||
if (system[global] != null)
|
||||
merged.Add(global, Merge(template[global], system[global], global));
|
||||
if (system["global"] != null)
|
||||
merged.Add("global", Merge(template["global"], system["global"], "global"));
|
||||
else
|
||||
merged.Add(global, template[global]);
|
||||
merged.Add("global", template["global"]);
|
||||
|
||||
//Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
|
||||
return merged;
|
||||
}
|
||||
|
||||
@@ -248,7 +225,7 @@ namespace PepperDash.Core.Config;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace PepperDash.Core;
|
||||
|
||||
@@ -4,192 +4,188 @@ using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Core;
|
||||
|
||||
//*********************************************************************************************************
|
||||
/// <summary>
|
||||
/// Represents a Device
|
||||
/// </summary>
|
||||
public class Device : IKeyName
|
||||
{
|
||||
|
||||
//*********************************************************************************************************
|
||||
/// <summary>
|
||||
/// Unique Key
|
||||
/// The core event and status-bearing class that most if not all device and connectors can derive from.
|
||||
/// </summary>
|
||||
public string Key { get; protected set; }
|
||||
/// <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)
|
||||
public class Device : IKeyName
|
||||
{
|
||||
Key = key;
|
||||
if (key.Contains(".")) Debug.LogMessage(LogEventLevel.Information, "WARNING: Device key should not include '.'", this);
|
||||
Name = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with key and name
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="name"></param>
|
||||
public Device(string key, string name) : this(key)
|
||||
{
|
||||
Name = name;
|
||||
/// <summary>
|
||||
/// Unique Key
|
||||
/// </summary>
|
||||
public string Key { get; protected set; }
|
||||
/// <summary>
|
||||
/// Name of the devie
|
||||
/// </summary>
|
||||
public string Name { get; protected set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public 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)
|
||||
// : this(config.Key, config.Name)
|
||||
//{
|
||||
// Config = config;
|
||||
//}
|
||||
List<Action> _PreActivationActions;
|
||||
List<Action> _PostActivationActions;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a pre activation action
|
||||
/// </summary>
|
||||
/// <param name="act"></param>
|
||||
public void AddPreActivationAction(Action act)
|
||||
{
|
||||
if (_PreActivationActions == null)
|
||||
_PreActivationActions = new List<Action>();
|
||||
_PreActivationActions.Add(act);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static Device DefaultDevice { get { return _DefaultDevice; } }
|
||||
static Device _DefaultDevice = new Device("Default", "Default");
|
||||
|
||||
/// <summary>
|
||||
/// Adds a post activation action
|
||||
/// </summary>
|
||||
/// <param name="act"></param>
|
||||
/// <summary>
|
||||
/// AddPostActivationAction method
|
||||
/// </summary>
|
||||
public void AddPostActivationAction(Action act)
|
||||
{
|
||||
if (_PostActivationActions == null)
|
||||
_PostActivationActions = new List<Action>();
|
||||
_PostActivationActions.Add(act);
|
||||
}
|
||||
/// <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>
|
||||
/// PreActivate method
|
||||
/// </summary>
|
||||
public void PreActivate()
|
||||
{
|
||||
if (_PreActivationActions != null)
|
||||
_PreActivationActions.ForEach(a =>
|
||||
{
|
||||
try
|
||||
/// <summary>
|
||||
/// Constructor with key and name
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="name"></param>
|
||||
public Device(string key, string name) : this(key)
|
||||
{
|
||||
Name = name;
|
||||
|
||||
}
|
||||
|
||||
//public Device(DeviceConfig config)
|
||||
// : this(config.Key, config.Name)
|
||||
//{
|
||||
// Config = config;
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a pre activation action
|
||||
/// </summary>
|
||||
/// <param name="act"></param>
|
||||
public void AddPreActivationAction(Action act)
|
||||
{
|
||||
if (_PreActivationActions == null)
|
||||
_PreActivationActions = new List<Action>();
|
||||
_PreActivationActions.Add(act);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a post activation action
|
||||
/// </summary>
|
||||
/// <param name="act"></param>
|
||||
public void AddPostActivationAction(Action act)
|
||||
{
|
||||
if (_PostActivationActions == null)
|
||||
_PostActivationActions = new List<Action>();
|
||||
_PostActivationActions.Add(act);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the preactivation actions
|
||||
/// </summary>
|
||||
public void PreActivate()
|
||||
{
|
||||
if (_PreActivationActions != null)
|
||||
_PreActivationActions.ForEach(a =>
|
||||
{
|
||||
a.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
try
|
||||
{
|
||||
a.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets this device ready to be used in the system. Runs any added pre-activation items, and
|
||||
/// all post-activation at end. Classes needing additional logic to
|
||||
/// run should override CustomActivate()
|
||||
/// </summary>
|
||||
public bool Activate()
|
||||
{
|
||||
//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 =>
|
||||
{
|
||||
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
try
|
||||
{
|
||||
a.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogMessage(e, "Error in PostActivationAction: " + 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>
|
||||
/// 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>
|
||||
/// PostActivate method
|
||||
/// </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>
|
||||
/// 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>
|
||||
/// 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>
|
||||
/// <summary>
|
||||
/// CustomActivate method
|
||||
/// </summary>
|
||||
public virtual bool CustomActivate() { return true; }
|
||||
/// <summary>
|
||||
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call 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>
|
||||
/// 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>
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a string representation of the object, including its key and name.
|
||||
/// </summary>
|
||||
/// <remarks>The returned string is formatted as "{Key} - {Name}". If the <c>Name</c> property is
|
||||
/// null or empty, "---" is used in place of the name.</remarks>
|
||||
/// <returns>A string that represents the object, containing the key and name in the format "{Key} - {Name}".</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} - {1}", Key, string.IsNullOrEmpty(Name) ? "---" : Name);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using Crestron.SimplSharp;
|
||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||
using Crestron.SimplSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an EthernetHelper.
|
||||
/// Class to help with accessing values from the CrestronEthernetHelper class
|
||||
/// </summary>
|
||||
public class EthernetHelper
|
||||
{
|
||||
@@ -26,9 +24,9 @@ namespace PepperDash.Core;
|
||||
|
||||
// ADD OTHER HELPERS HERE
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the PortNumber
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int PortNumber { get; private set; }
|
||||
|
||||
private EthernetHelper(int portNumber)
|
||||
|
||||
@@ -16,19 +16,19 @@ namespace PepperDash.Core;
|
||||
/// </summary>
|
||||
public bool State { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the IntValue
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Boolean ushort value property
|
||||
/// </summary>
|
||||
public ushort IntValue { get { return (ushort)(State ? 1 : 0); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Type
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Boolean change event args type
|
||||
/// </summary>
|
||||
public ushort Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Index
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Boolean change event args index
|
||||
/// </summary>
|
||||
public ushort Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -64,9 +64,9 @@ namespace PepperDash.Core;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a UshrtChangeEventArgs
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Ushort change event args
|
||||
/// </summary>
|
||||
public class UshrtChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
@@ -74,14 +74,14 @@ namespace PepperDash.Core;
|
||||
/// </summary>
|
||||
public ushort IntValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Type
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Ushort change event args type
|
||||
/// </summary>
|
||||
public ushort Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Index
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Ushort change event args index
|
||||
/// </summary>
|
||||
public ushort Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -117,9 +117,9 @@ namespace PepperDash.Core;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a StringChangeEventArgs
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// String change event args
|
||||
/// </summary>
|
||||
public class StringChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
@@ -127,14 +127,14 @@ namespace PepperDash.Core;
|
||||
/// </summary>
|
||||
public string StringValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Type
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// String change event args type
|
||||
/// </summary>
|
||||
public ushort Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Index
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// string change event args index
|
||||
/// </summary>
|
||||
public ushort Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core.GenericRESTfulCommunications;
|
||||
|
||||
/// <summary>
|
||||
/// Constants
|
||||
/// </summary>
|
||||
public class GenericRESTfulConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic boolean change
|
||||
/// </summary>
|
||||
public const ushort BoolValueChange = 1;
|
||||
/// <summary>
|
||||
/// Generic Ushort change
|
||||
/// </summary>
|
||||
public const ushort UshrtValueChange = 101;
|
||||
/// <summary>
|
||||
/// Response Code Ushort change
|
||||
/// </summary>
|
||||
public const ushort ResponseCodeChange = 102;
|
||||
/// <summary>
|
||||
/// Generic String chagne
|
||||
/// </summary>
|
||||
public const ushort StringValueChange = 201;
|
||||
/// <summary>
|
||||
/// Response string change
|
||||
/// </summary>
|
||||
public const ushort ResponseStringChange = 202;
|
||||
/// <summary>
|
||||
/// Error string change
|
||||
/// </summary>
|
||||
public const ushort ErrorStringChange = 203;
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.Net.Http;
|
||||
using Crestron.SimplSharp.Net.Https;
|
||||
|
||||
namespace PepperDash.Core.GenericRESTfulCommunications;
|
||||
|
||||
/// <summary>
|
||||
/// Generic RESTful communication class
|
||||
/// </summary>
|
||||
public class GenericRESTfulClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Boolean event handler
|
||||
/// </summary>
|
||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
||||
/// <summary>
|
||||
/// Ushort event handler
|
||||
/// </summary>
|
||||
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
||||
/// <summary>
|
||||
/// String event handler
|
||||
/// </summary>
|
||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public GenericRESTfulClient()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic RESTful submit request
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="requestType"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <param name="contentType"></param>
|
||||
public void SubmitRequest(string url, ushort port, ushort requestType, string contentType, string username, string password)
|
||||
{
|
||||
if (url.StartsWith("https:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SubmitRequestHttps(url, port, requestType, contentType, username, password);
|
||||
}
|
||||
else if (url.StartsWith("http:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SubmitRequestHttp(url, port, requestType, contentType, username, password);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnStringChange(string.Format("Invalid URL {0}", url), 0, GenericRESTfulConstants.ErrorStringChange);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private HTTP submit request
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="requestType"></param>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="password"></param>
|
||||
private void SubmitRequestHttp(string url, ushort port, ushort requestType, string contentType, string username, string password)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
HttpClientRequest request = new HttpClientRequest();
|
||||
HttpClientResponse response;
|
||||
|
||||
client.KeepAlive = false;
|
||||
|
||||
if(port >= 1 || port <= 65535)
|
||||
client.Port = port;
|
||||
else
|
||||
client.Port = 80;
|
||||
|
||||
var authorization = "";
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
authorization = EncodeBase64(username, password);
|
||||
|
||||
if (!string.IsNullOrEmpty(authorization))
|
||||
request.Header.SetHeaderValue("Authorization", authorization);
|
||||
|
||||
if (!string.IsNullOrEmpty(contentType))
|
||||
request.Header.ContentType = contentType;
|
||||
|
||||
request.Url.Parse(url);
|
||||
request.RequestType = (Crestron.SimplSharp.Net.Http.RequestType)requestType;
|
||||
|
||||
response = client.Dispatch(request);
|
||||
|
||||
CrestronConsole.PrintLine(string.Format("SubmitRequestHttp Response[{0}]: {1}", response.Code, response.ContentString.ToString()));
|
||||
|
||||
if (!string.IsNullOrEmpty(response.ContentString.ToString()))
|
||||
OnStringChange(response.ContentString.ToString(), 0, GenericRESTfulConstants.ResponseStringChange);
|
||||
|
||||
if (response.Code > 0)
|
||||
OnUshrtChange((ushort)response.Code, 0, GenericRESTfulConstants.ResponseCodeChange);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//var msg = string.Format("SubmitRequestHttp({0}, {1}, {2}) failed:{3}", url, port, requestType, e.Message);
|
||||
//CrestronConsole.PrintLine(msg);
|
||||
//ErrorLog.Error(msg);
|
||||
|
||||
CrestronConsole.PrintLine(e.Message);
|
||||
OnStringChange(e.Message, 0, GenericRESTfulConstants.ErrorStringChange);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private HTTPS submit request
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <param name="requestType"></param>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="password"></param>
|
||||
private void SubmitRequestHttps(string url, ushort port, ushort requestType, string contentType, string username, string password)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpsClient client = new HttpsClient();
|
||||
HttpsClientRequest request = new HttpsClientRequest();
|
||||
HttpsClientResponse response;
|
||||
|
||||
client.KeepAlive = false;
|
||||
client.HostVerification = false;
|
||||
client.PeerVerification = false;
|
||||
|
||||
var authorization = "";
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
authorization = EncodeBase64(username, password);
|
||||
|
||||
if (!string.IsNullOrEmpty(authorization))
|
||||
request.Header.SetHeaderValue("Authorization", authorization);
|
||||
|
||||
if (!string.IsNullOrEmpty(contentType))
|
||||
request.Header.ContentType = contentType;
|
||||
|
||||
request.Url.Parse(url);
|
||||
request.RequestType = (Crestron.SimplSharp.Net.Https.RequestType)requestType;
|
||||
|
||||
response = client.Dispatch(request);
|
||||
|
||||
CrestronConsole.PrintLine(string.Format("SubmitRequestHttp Response[{0}]: {1}", response.Code, response.ContentString.ToString()));
|
||||
|
||||
if(!string.IsNullOrEmpty(response.ContentString.ToString()))
|
||||
OnStringChange(response.ContentString.ToString(), 0, GenericRESTfulConstants.ResponseStringChange);
|
||||
|
||||
if(response.Code > 0)
|
||||
OnUshrtChange((ushort)response.Code, 0, GenericRESTfulConstants.ResponseCodeChange);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//var msg = string.Format("SubmitRequestHttps({0}, {1}, {2}, {3}, {4}) failed:{5}", url, port, requestType, username, password, e.Message);
|
||||
//CrestronConsole.PrintLine(msg);
|
||||
//ErrorLog.Error(msg);
|
||||
|
||||
CrestronConsole.PrintLine(e.Message);
|
||||
OnStringChange(e.Message, 0, GenericRESTfulConstants.ErrorStringChange);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private method to encode username and password to Base64 string
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns>authorization</returns>
|
||||
private string EncodeBase64(string username, string password)
|
||||
{
|
||||
var authorization = "";
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
string base64String = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(string.Format("{0}:{1}", username, password)));
|
||||
authorization = string.Format("Basic {0}", base64String);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var msg = string.Format("EncodeBase64({0}, {1}) failed:\r{2}", username, password, e);
|
||||
CrestronConsole.PrintLine(msg);
|
||||
ErrorLog.Error(msg);
|
||||
return "" ;
|
||||
}
|
||||
|
||||
return authorization;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protected method to handle boolean change events
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnBoolChange(bool state, ushort index, ushort type)
|
||||
{
|
||||
var handler = BoolChange;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new BoolChangeEventArgs(state, type);
|
||||
args.Index = index;
|
||||
BoolChange(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protected mehtod to handle ushort change events
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnUshrtChange(ushort value, ushort index, ushort type)
|
||||
{
|
||||
var handler = UshrtChange;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new UshrtChangeEventArgs(value, type);
|
||||
args.Index = index;
|
||||
UshrtChange(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protected method to handle string change events
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnStringChange(string value, ushort index, ushort type)
|
||||
{
|
||||
var handler = StringChange;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new StringChangeEventArgs(value, type);
|
||||
args.Index = index;
|
||||
StringChange(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,14 +32,14 @@ namespace PepperDash.Core.JsonStandardObjects;
|
||||
/// </summary>
|
||||
public DeviceConfig Device { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Type
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Device change event args type
|
||||
/// </summary>
|
||||
public ushort Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Index
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Device change event args index
|
||||
/// </summary>
|
||||
public ushort Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -58,9 +58,6 @@ namespace PepperDash.Core.JsonStandardObjects;
|
||||
/// </summary>
|
||||
/// <param name="uniqueID"></param>
|
||||
/// <param name="deviceKey"></param>
|
||||
/// <summary>
|
||||
/// Initialize method
|
||||
/// </summary>
|
||||
public void Initialize(string uniqueID, string deviceKey)
|
||||
{
|
||||
// S+ set EvaluateFb low
|
||||
|
||||
@@ -47,9 +47,9 @@ namespace PepperDash.Core.JsonStandardObjects;
|
||||
]
|
||||
}
|
||||
*/
|
||||
/// <summary>
|
||||
/// Represents a ComParamsConfig
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Device communication parameter class
|
||||
/// </summary>
|
||||
public class ComParamsConfig
|
||||
{
|
||||
/// <summary>
|
||||
@@ -207,9 +207,9 @@ namespace PepperDash.Core.JsonStandardObjects;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a PropertiesConfig
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Device properties class
|
||||
/// </summary>
|
||||
public class PropertiesConfig
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -122,9 +122,9 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of SPlusType values
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// S+ types enum
|
||||
/// </summary>
|
||||
public enum SPlusType
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -49,9 +49,9 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetMasterByFile method
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Gets a master by its key. Case-insensitive
|
||||
/// </summary>
|
||||
public static JsonToSimplMaster GetMasterByFile(string file)
|
||||
{
|
||||
return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl;
|
||||
@@ -130,7 +128,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
var item = array.FirstOrDefault(o =>
|
||||
{
|
||||
var prop = o[SearchPropertyName];
|
||||
return prop != null && ((string)prop)
|
||||
return prop != null && prop.Value<string>()
|
||||
.Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
if (item == null)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using PepperDash.Core.Logging;
|
||||
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl;
|
||||
|
||||
@@ -31,9 +28,9 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
/// </summary>
|
||||
public SPlusValuesDelegate GetAllValuesDelegate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SetAllPathsDelegate
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Use a callback to reduce task switch/threading
|
||||
/// </summary>
|
||||
public SPlusValuesDelegate SetAllPathsDelegate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -92,7 +89,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
if (Master != null)
|
||||
Master.AddChild(this);
|
||||
else
|
||||
this.LogWarning("JSON Child [{0}] cannot link to master {1}", key, masterUniqueId);
|
||||
Debug.Console(1, "JSON Child [{0}] cannot link to master {1}", key, masterUniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,46 +105,46 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
/// </summary>
|
||||
public void SetBoolPath(ushort index, string path)
|
||||
{
|
||||
this.LogDebug("JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path);
|
||||
Debug.Console(1, "JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path);
|
||||
if (path == null || path.Trim() == string.Empty) return;
|
||||
BoolPaths[index] = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SetUshortPath method
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Set the JPath for a ushort out index.
|
||||
/// </summary>
|
||||
public void SetUshortPath(ushort index, string path)
|
||||
{
|
||||
this.LogDebug("JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
|
||||
Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
|
||||
if (path == null || path.Trim() == string.Empty) return;
|
||||
UshortPaths[index] = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SetStringPath method
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Set the JPath for a string output index.
|
||||
/// </summary>
|
||||
public void SetStringPath(ushort index, string path)
|
||||
{
|
||||
this.LogDebug("JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
|
||||
Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
|
||||
if (path == null || path.Trim() == string.Empty) return;
|
||||
StringPaths[index] = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ProcessAll method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Evalutates all outputs with defined paths. called by S+ when paths are ready to process
|
||||
/// and by Master when file is read.
|
||||
/// </summary>
|
||||
public virtual void ProcessAll()
|
||||
{
|
||||
if (!LinkedToObject)
|
||||
{
|
||||
this.LogDebug("Not linked to object in file. Skipping");
|
||||
Debug.Console(1, this, "Not linked to object in file. Skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SetAllPathsDelegate == null)
|
||||
{
|
||||
this.LogDebug("No SetAllPathsDelegate set. Ignoring ProcessAll");
|
||||
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring ProcessAll");
|
||||
return;
|
||||
}
|
||||
SetAllPathsDelegate();
|
||||
@@ -207,11 +204,11 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
bool Process(string path, out string response)
|
||||
{
|
||||
path = GetFullPath(path);
|
||||
this.LogDebug("JSON Child[{0}] Processing {1}", Key, path);
|
||||
Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path);
|
||||
response = "";
|
||||
if (Master == null)
|
||||
{
|
||||
this.LogWarning("JSONChild[{0}] cannot process without Master attached", Key);
|
||||
Debug.Console(1, "JSONChild[{0}] cannot process without Master attached", Key);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -233,8 +230,8 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
if (isCount)
|
||||
response = (t.HasValues ? t.Children().Count() : 0).ToString();
|
||||
else
|
||||
response = (string)t;
|
||||
this.LogDebug(" ='{0}'", response);
|
||||
response = t.Value<string>();
|
||||
Debug.Console(1, " ='{0}'", response);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -260,13 +257,13 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
{
|
||||
if (!LinkedToObject)
|
||||
{
|
||||
this.LogDebug("Not linked to object in file. Skipping");
|
||||
Debug.Console(1, this, "Not linked to object in file. Skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SetAllPathsDelegate == null)
|
||||
{
|
||||
this.LogDebug("No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
|
||||
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
|
||||
return;
|
||||
}
|
||||
SetAllPathsDelegate();
|
||||
@@ -328,7 +325,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
var path = GetFullPath(keyPath);
|
||||
try
|
||||
{
|
||||
this.LogDebug("JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
|
||||
Debug.Console(1, "JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
|
||||
|
||||
//var token = Master.JsonObject.SelectToken(path);
|
||||
//if (token != null) // The path exists in the file
|
||||
@@ -336,7 +333,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogDebug("JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
|
||||
Debug.Console(1, "JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using PepperDash.Core.Logging;
|
||||
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl;
|
||||
|
||||
@@ -130,7 +125,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
||||
var fileName = Path.GetFileName(Filepath);
|
||||
|
||||
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange);
|
||||
this.LogInformation("Checking '{0}' for '{1}'", fileDirectory, fileName);
|
||||
Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName);
|
||||
|
||||
if (Directory.Exists(fileDirectory))
|
||||
{
|
||||
@@ -144,7 +139,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
||||
var msg = string.Format("JSON file not found: {0}", Filepath);
|
||||
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
|
||||
CrestronConsole.PrintLine(msg);
|
||||
this.LogError(msg);
|
||||
ErrorLog.Error(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,18 +148,18 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
||||
ActualFilePath = actualFile.FullName;
|
||||
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
|
||||
OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange);
|
||||
this.LogInformation("Actual JSON file is {0}", ActualFilePath);
|
||||
Debug.Console(1, "Actual JSON file is {0}", ActualFilePath);
|
||||
|
||||
Filename = actualFile.Name;
|
||||
OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange);
|
||||
OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange);
|
||||
this.LogInformation("JSON Filename is {0}", Filename);
|
||||
Debug.Console(1, "JSON Filename is {0}", Filename);
|
||||
|
||||
|
||||
FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator);
|
||||
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
|
||||
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange);
|
||||
this.LogInformation("JSON File Path is {0}", FilePathName);
|
||||
Debug.Console(1, "JSON File Path is {0}", FilePathName);
|
||||
|
||||
var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
||||
|
||||
@@ -177,7 +172,7 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
||||
else
|
||||
{
|
||||
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange);
|
||||
this.LogError("'{0}' not found", fileDirectory);
|
||||
Debug.Console(1, "'{0}' not found", fileDirectory);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -185,12 +180,12 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
||||
var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message);
|
||||
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
|
||||
CrestronConsole.PrintLine(msg);
|
||||
this.LogException(e, "EvaluateFile Exception: {0}", e.Message);
|
||||
ErrorLog.Error(msg);
|
||||
|
||||
var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
|
||||
OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange);
|
||||
CrestronConsole.PrintLine(stackTrace);
|
||||
this.LogVerbose("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
|
||||
ErrorLog.Error(stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,31 +209,63 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
||||
// Make each child update their values into master object
|
||||
foreach (var child in Children)
|
||||
{
|
||||
this.LogInformation("Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
||||
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
||||
child.UpdateInputsForMaster();
|
||||
}
|
||||
|
||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
||||
{
|
||||
this.LogInformation("Master [{0}] No updated values to save. Skipping", UniqueID);
|
||||
Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
|
||||
return;
|
||||
}
|
||||
lock (FileLock)
|
||||
{
|
||||
this.LogInformation("Saving");
|
||||
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]);
|
||||
this.LogInformation("JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
||||
Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
||||
}
|
||||
else // No token. Let's make one
|
||||
{
|
||||
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
|
||||
this.LogWarning("JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
|
||||
Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
|
||||
|
||||
// JContainer jpart = JsonObject;
|
||||
// // walk down the path and find where it goes
|
||||
//#warning Does not handle arrays.
|
||||
// foreach (var part in path.Split('.'))
|
||||
// {
|
||||
|
||||
// var openPos = part.IndexOf('[');
|
||||
// if (openPos > -1)
|
||||
// {
|
||||
// openPos++; // move to number
|
||||
// var closePos = part.IndexOf(']');
|
||||
// var arrayName = part.Substring(0, openPos - 1); // get the name
|
||||
// var index = Convert.ToInt32(part.Substring(openPos, closePos - openPos));
|
||||
|
||||
// // Check if the array itself exists and add the item if so
|
||||
// if (jpart[arrayName] != null)
|
||||
// {
|
||||
// var arrayObj = jpart[arrayName] as JArray;
|
||||
// var item = arrayObj[index];
|
||||
// if (item == null)
|
||||
// arrayObj.Add(new JObject());
|
||||
// }
|
||||
|
||||
// Debug.Console(0, "IGNORING MISSING ARRAY VALUE FOR NOW");
|
||||
// continue;
|
||||
// }
|
||||
// // Build the
|
||||
// if (jpart[part] == null)
|
||||
// jpart.Add(new JProperty(part, new JObject()));
|
||||
// jpart = jpart[part] as JContainer;
|
||||
// }
|
||||
// jpart.Replace(UnsavedValues[path]);
|
||||
}
|
||||
}
|
||||
using (StreamWriter sw = new StreamWriter(ActualFilePath))
|
||||
@@ -251,13 +278,11 @@ public class JsonToSimplFileMaster : JsonToSimplMaster
|
||||
catch (Exception e)
|
||||
{
|
||||
string err = string.Format("Error writing JSON file:\r{0}", e);
|
||||
this.LogException(e, "Error writing JSON file: {0}", e.Message);
|
||||
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
|
||||
Debug.Console(0, err);
|
||||
ErrorLog.Warn(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Crestron.SimplSharp;
|
||||
using PepperDash.Core.Logging;
|
||||
using Renci.SshNet.Messages;
|
||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl;
|
||||
|
||||
@@ -65,9 +60,6 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
/// Loads JSON into JsonObject, but does not trigger evaluation by children
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
/// <summary>
|
||||
/// SetJsonWithoutEvaluating method
|
||||
/// </summary>
|
||||
public void SetJsonWithoutEvaluating(string json)
|
||||
{
|
||||
try
|
||||
@@ -76,15 +68,13 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.LogException(e, "JSON parsing failed:\r{0}", e.Message);
|
||||
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
|
||||
Debug.Console(0, this, "JSON parsing failed:\r{0}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public override void Save()
|
||||
{
|
||||
// this code is duplicated in the other masters!!!!!!!!!!!!!
|
||||
@@ -92,36 +82,36 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
// Make each child update their values into master object
|
||||
foreach (var child in Children)
|
||||
{
|
||||
this.LogDebug("Master. checking child [{0}] for updates to save", child.Key);
|
||||
Debug.Console(1, this, "Master. checking child [{0}] for updates to save", child.Key);
|
||||
child.UpdateInputsForMaster();
|
||||
}
|
||||
|
||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
||||
{
|
||||
this.LogDebug("Master. No updated values to save. Skipping");
|
||||
Debug.Console(1, this, "Master. No updated values to save. Skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
lock (WriteLock)
|
||||
{
|
||||
this.LogDebug("Saving");
|
||||
Debug.Console(1, this, "Saving");
|
||||
foreach (var path in UnsavedValues.Keys)
|
||||
{
|
||||
var tokenToReplace = JsonObject.SelectToken(path);
|
||||
if (tokenToReplace != null)
|
||||
{// It's found
|
||||
tokenToReplace.Replace(UnsavedValues[path]);
|
||||
this.LogDebug("Master Updating '{0}'", path);
|
||||
Debug.Console(1, this, "Master Updating '{0}'", path);
|
||||
}
|
||||
else // No token. Let's make one
|
||||
{
|
||||
this.LogDebug("Master Cannot write value onto missing property: '{0}'", path);
|
||||
Debug.Console(1, "Master Cannot write value onto missing property: '{0}'", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (SaveCallback != null)
|
||||
SaveCallback(JsonObject.ToString());
|
||||
else
|
||||
this.LogDebug("WARNING: No save callback defined.");
|
||||
Debug.Console(0, this, "WARNING: No save callback defined.");
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,9 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using JArray = NewtonsoftJson::Newtonsoft.Json.Linq.JArray;
|
||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
||||
using JsonSerializationException = NewtonsoftJson::Newtonsoft.Json.JsonSerializationException;
|
||||
using JsonTextReader = NewtonsoftJson::Newtonsoft.Json.JsonTextReader;
|
||||
using PepperDash.Core.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl;
|
||||
|
||||
@@ -59,9 +52,10 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
}
|
||||
string _DebugName = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the PathPrefix
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// This will be prepended to all paths to allow path swapping or for more organized
|
||||
/// sub-paths
|
||||
/// </summary>
|
||||
public string PathPrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -125,9 +119,6 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
/// Adds a child "module" to this master
|
||||
/// </summary>
|
||||
/// <param name="child"></param>
|
||||
/// <summary>
|
||||
/// AddChild method
|
||||
/// </summary>
|
||||
public void AddChild(JsonToSimplChildObjectBase child)
|
||||
{
|
||||
if (!Children.Contains(child))
|
||||
@@ -136,17 +127,18 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AddUnsavedValue method
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Called from the child to add changed or new values for saving
|
||||
/// </summary>
|
||||
public void AddUnsavedValue(string path, JValue value)
|
||||
{
|
||||
if (UnsavedValues.ContainsKey(path))
|
||||
{
|
||||
this.LogWarning("Master[{0}] WARNING - Attempt to add duplicate value for path '{1}'.\r Ingoring. Please ensure that path does not exist on multiple modules.", UniqueID, path);
|
||||
Debug.Console(0, "Master[{0}] WARNING - Attempt to add duplicate value for path '{1}'.\r Ingoring. Please ensure that path does not exist on multiple modules.", UniqueID, path);
|
||||
}
|
||||
else
|
||||
UnsavedValues.Add(path, value);
|
||||
//Debug.Console(0, "Master[{0}] Unsaved size={1}", UniqueID, UnsavedValues.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -167,7 +159,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
/// <returns></returns>
|
||||
public static JObject ParseObject(string json)
|
||||
{
|
||||
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
||||
using (var reader = new JsonTextReader(new StringReader(json)))
|
||||
{
|
||||
var startDepth = reader.Depth;
|
||||
var obj = JObject.Load(reader);
|
||||
@@ -185,7 +177,7 @@ namespace PepperDash.Core.JsonToSimpl;
|
||||
public static JArray ParseArray(string json)
|
||||
{
|
||||
|
||||
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
|
||||
using (var reader = new JsonTextReader(new StringReader(json)))
|
||||
{
|
||||
var startDepth = reader.Depth;
|
||||
var obj = JArray.Load(reader);
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||
using JValue = NewtonsoftJson::Newtonsoft.Json.Linq.JValue;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core.Config;
|
||||
using PepperDash.Core.Logging;
|
||||
|
||||
namespace PepperDash.Core.JsonToSimpl;
|
||||
|
||||
@@ -62,7 +58,7 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
||||
// If the portal file is xyz.json, then
|
||||
// the file we want to check for first will be called xyz.local.json
|
||||
var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json");
|
||||
this.LogInformation("Checking for local file {0}", localFilepath);
|
||||
Debug.Console(0, this, "Checking for local file {0}", localFilepath);
|
||||
var actualLocalFile = GetActualFileInfoFromPath(localFilepath);
|
||||
|
||||
if (actualLocalFile != null)
|
||||
@@ -74,7 +70,7 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
||||
// and create the local.
|
||||
else
|
||||
{
|
||||
this.LogInformation("Local JSON file not found {0}\rLoading portal JSON file", localFilepath);
|
||||
Debug.Console(1, this, "Local JSON file not found {0}\rLoading portal JSON file", localFilepath);
|
||||
var actualPortalFile = GetActualFileInfoFromPath(portalFilepath);
|
||||
if (actualPortalFile != null)
|
||||
{
|
||||
@@ -87,13 +83,14 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
||||
else
|
||||
{
|
||||
var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath);
|
||||
this.LogError(msg);
|
||||
Debug.Console(1, this, msg);
|
||||
ErrorLog.Error(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we should have a local file. Do it.
|
||||
this.LogInformation("Reading local JSON file {0}", ActualFilePath);
|
||||
Debug.Console(1, "Reading local JSON file {0}", ActualFilePath);
|
||||
|
||||
string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
|
||||
|
||||
@@ -107,7 +104,8 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
||||
catch (Exception e)
|
||||
{
|
||||
var msg = string.Format("JSON parsing failed:\r{0}", e);
|
||||
this.LogError(msg);
|
||||
CrestronConsole.PrintLine(msg);
|
||||
ErrorLog.Error(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -130,9 +128,6 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
/// <summary>
|
||||
/// setDebugLevel method
|
||||
/// </summary>
|
||||
public void setDebugLevel(uint level)
|
||||
{
|
||||
Debug.SetDebugLevel(level);
|
||||
@@ -148,30 +143,30 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
||||
// Make each child update their values into master object
|
||||
foreach (var child in Children)
|
||||
{
|
||||
this.LogInformation("Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
||||
Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
|
||||
child.UpdateInputsForMaster();
|
||||
}
|
||||
|
||||
if (UnsavedValues == null || UnsavedValues.Count == 0)
|
||||
{
|
||||
this.LogInformation("Master [{0}] No updated values to save. Skipping", UniqueID);
|
||||
Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
|
||||
return;
|
||||
}
|
||||
lock (FileLock)
|
||||
{
|
||||
this.LogInformation("Saving");
|
||||
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]);
|
||||
this.LogInformation("JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
||||
Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path);
|
||||
}
|
||||
else // No token. Let's make one
|
||||
{
|
||||
//http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
|
||||
this.LogWarning("JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
|
||||
Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -185,8 +180,8 @@ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
|
||||
catch (Exception e)
|
||||
{
|
||||
string err = string.Format("Error writing JSON file:\r{0}", e);
|
||||
this.LogException(e, "Error writing JSON file: {0}", e.Message);
|
||||
this.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
|
||||
Debug.Console(0, err);
|
||||
ErrorLog.Warn(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace PepperDash.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// Enriches log events with Crestron-specific context properties, such as the application name based on the device platform.
|
||||
/// </summary>
|
||||
public class CrestronEnricher : ILogEventEnricher
|
||||
{
|
||||
static readonly string _appName;
|
||||
@@ -30,11 +27,6 @@ public class CrestronEnricher : ILogEventEnricher
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Enriches the log event with Crestron-specific properties.
|
||||
/// </summary>
|
||||
/// <param name="logEvent"></param>
|
||||
/// <param name="propertyFactory"></param>
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
var property = propertyFactory.CreateProperty("App", _appName);
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronDataStore;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.CrestronLogger;
|
||||
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
||||
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core.Logging;
|
||||
using Serilog;
|
||||
using Serilog.Context;
|
||||
@@ -45,32 +41,24 @@ public static class Debug
|
||||
|
||||
private static ILogger _logger;
|
||||
|
||||
private static readonly LoggingLevelSwitch consoleLoggingLevelSwitch;
|
||||
private static readonly LoggingLevelSwitch _consoleLoggingLevelSwitch;
|
||||
|
||||
private static readonly LoggingLevelSwitch websocketLoggingLevelSwitch;
|
||||
private static readonly LoggingLevelSwitch _websocketLoggingLevelSwitch;
|
||||
|
||||
private static readonly LoggingLevelSwitch errorLogLevelSwitch;
|
||||
private static readonly LoggingLevelSwitch _errorLogLevelSwitch;
|
||||
|
||||
private static readonly LoggingLevelSwitch fileLevelSwitch;
|
||||
private static readonly LoggingLevelSwitch _fileLevelSwitch;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum log level for messages to be sent to the console sink
|
||||
/// </summary>
|
||||
public static LogEventLevel WebsocketMinimumLogLevel
|
||||
{
|
||||
get { return websocketLoggingLevelSwitch.MinimumLevel; }
|
||||
get { return _websocketLoggingLevelSwitch.MinimumLevel; }
|
||||
}
|
||||
|
||||
private static readonly DebugWebsocketSink websocketSink;
|
||||
private static readonly DebugWebsocketSink _websocketSink;
|
||||
|
||||
/// <summary>
|
||||
/// The DebugWebsocketSink instance used for sending log messages to connected websocket clients.
|
||||
/// This is exposed publicly in case there is a need to call methods on the sink directly, such as SendMessageToClients.
|
||||
/// For general logging purposes, use the LogMessage and LogError methods in this class which will send messages to all configured sinks including the websocket sink.
|
||||
/// </summary>
|
||||
public static DebugWebsocketSink WebsocketSink
|
||||
{
|
||||
get { return websocketSink; }
|
||||
get { return _websocketSink; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -104,9 +92,6 @@ public static class Debug
|
||||
|
||||
private const int SaveTimeoutMs = 30000;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the code is running on an appliance or not. Used to determine file paths and other appliance vs server differences
|
||||
/// </summary>
|
||||
public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
|
||||
|
||||
/// <summary>
|
||||
@@ -114,12 +99,7 @@ public static class Debug
|
||||
/// </summary>
|
||||
public static string PepperDashCoreVersion { get; private set; }
|
||||
|
||||
private static Timer _saveTimer;
|
||||
|
||||
|
||||
private const int defaultConsoleDebugTimeoutMin = 120;
|
||||
|
||||
private static Timer consoleDebugTimer;
|
||||
private static CTimer _saveTimer;
|
||||
|
||||
/// <summary>
|
||||
/// When true, the IncludedExcludedKeys dict will contain keys to include.
|
||||
@@ -135,10 +115,6 @@ public static class Debug
|
||||
|
||||
private static LoggerConfiguration _loggerConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The default logger configuration used by the Debug class. Can be used as a base for creating custom logger configurations.
|
||||
/// If changes are made to this configuration after initialization, call ResetLoggerConfiguration to have those changes reflected in the logger.
|
||||
/// </summary>
|
||||
public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration;
|
||||
|
||||
static Debug()
|
||||
@@ -147,13 +123,6 @@ public static class Debug
|
||||
{
|
||||
CrestronDataStoreStatic.InitCrestronDataStore();
|
||||
|
||||
consoleDebugTimer = new Timer(defaultConsoleDebugTimeoutMin * 60000) { AutoReset = false };
|
||||
consoleDebugTimer.Elapsed += (s, e) =>
|
||||
{
|
||||
SetDebugLevel(LogEventLevel.Information);
|
||||
CrestronConsole.ConsoleCommandResponse($"Console debug level reset to {LogEventLevel.Information} after timeout of {defaultConsoleDebugTimeoutMin} minutes");
|
||||
};
|
||||
|
||||
var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey);
|
||||
|
||||
var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
|
||||
@@ -162,15 +131,15 @@ public static class Debug
|
||||
|
||||
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
|
||||
|
||||
consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
|
||||
_consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
|
||||
|
||||
websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
|
||||
_websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
|
||||
|
||||
errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
|
||||
_errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
|
||||
|
||||
fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
|
||||
_fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
|
||||
|
||||
websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
|
||||
_websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
|
||||
|
||||
var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ?
|
||||
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" :
|
||||
@@ -186,14 +155,14 @@ public static class Debug
|
||||
.MinimumLevel.Verbose()
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.With(new CrestronEnricher())
|
||||
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: consoleLoggingLevelSwitch)
|
||||
.WriteTo.Sink(websocketSink, levelSwitch: websocketLoggingLevelSwitch)
|
||||
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: errorLogLevelSwitch)
|
||||
.WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLoggingLevelSwitch)
|
||||
.WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch)
|
||||
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch)
|
||||
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
restrictedToMinimumLevel: LogEventLevel.Debug,
|
||||
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
|
||||
levelSwitch: fileLevelSwitch
|
||||
levelSwitch: _fileLevelSwitch
|
||||
);
|
||||
|
||||
// Instantiate the root logger
|
||||
@@ -242,9 +211,9 @@ public static class Debug
|
||||
if (DoNotLoadConfigOnNextBoot)
|
||||
CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber));
|
||||
|
||||
consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
|
||||
_consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
|
||||
{
|
||||
LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", consoleLoggingLevelSwitch.MinimumLevel);
|
||||
LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", _consoleLoggingLevelSwitch.MinimumLevel);
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -266,12 +235,6 @@ public static class Debug
|
||||
return doNotLoad;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the LoggerConfiguration used by the Debug class.
|
||||
/// This allows for changing logger settings such as sinks and output templates.
|
||||
/// After calling this method, the new configuration will be used for all subsequent log messages.
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
public static void UpdateLoggerConfiguration(LoggerConfiguration config)
|
||||
{
|
||||
_loggerConfiguration = config;
|
||||
@@ -279,9 +242,6 @@ public static class Debug
|
||||
_logger = config.CreateLogger();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the LoggerConfiguration to the default configuration defined in this class.
|
||||
/// </summary>
|
||||
public static void ResetLoggerConfiguration()
|
||||
{
|
||||
_loggerConfiguration = _defaultLoggerConfiguration;
|
||||
@@ -369,11 +329,7 @@ public static class Debug
|
||||
if (levelString.Trim() == "?")
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse(
|
||||
|
||||
"Used to set the minimum level of debug messages to be printed to the console:\r\n" +
|
||||
"[LogLevel] [TimeoutInMinutes]\r\n" +
|
||||
"If TimeoutInMinutes is not provided, it will default to 120 minutes. If provided, the level will reset to Information after the timeout period elapses.\r\n" +
|
||||
"LogLevel can be either a number from 0-5 or a log level name. If using a number, the mapping is as follows:\r\n" +
|
||||
$"{_logLevels[0]} = 0\r\n" +
|
||||
$"{_logLevels[1]} = 1\r\n" +
|
||||
$"{_logLevels[2]} = 2\r\n" +
|
||||
@@ -385,22 +341,10 @@ public static class Debug
|
||||
|
||||
if (string.IsNullOrEmpty(levelString.Trim()))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", consoleLoggingLevelSwitch.MinimumLevel);
|
||||
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", _consoleLoggingLevelSwitch.MinimumLevel);
|
||||
return;
|
||||
}
|
||||
|
||||
// split on space to allow for potential future addition of timeout parameter without breaking existing command usage
|
||||
var parts = Regex.Split(levelString.Trim(), @"\s+");
|
||||
levelString = parts[0];
|
||||
|
||||
if (parts.Length > 1 && long.TryParse(parts[1], out var timeout))
|
||||
{
|
||||
timeout = Math.Max(timeout, 1); // enforce minimum timeout of 1 minute
|
||||
consoleDebugTimer.Interval = timeout * 60000;
|
||||
}
|
||||
|
||||
// first try to parse as int for backward compatibility with existing usage of numeric levels
|
||||
|
||||
if (int.TryParse(levelString, out var levelInt))
|
||||
{
|
||||
if (levelInt < 0 || levelInt > 5)
|
||||
@@ -430,8 +374,7 @@ public static class Debug
|
||||
/// Sets the debug level
|
||||
/// </summary>
|
||||
/// <param name="level"> Valid values 0-5</param>
|
||||
/// <param name="timeout"> Timeout in minutes</param>
|
||||
public static void SetDebugLevel(uint level, int timeout = defaultConsoleDebugTimeoutMin)
|
||||
public static void SetDebugLevel(uint level)
|
||||
{
|
||||
if (!_logLevels.TryGetValue(level, out var logLevel))
|
||||
{
|
||||
@@ -439,27 +382,18 @@ public static class Debug
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
|
||||
|
||||
SetDebugLevel(logLevel, timeout);
|
||||
SetDebugLevel(logLevel);
|
||||
}
|
||||
|
||||
SetDebugLevel(logLevel, timeout);
|
||||
SetDebugLevel(logLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debug level
|
||||
/// </summary>
|
||||
/// <param name="level"> The log level to set</param>
|
||||
/// <param name="timeout"> Timeout in minutes</param>
|
||||
public static void SetDebugLevel(LogEventLevel level, int timeout = defaultConsoleDebugTimeoutMin)
|
||||
public static void SetDebugLevel(LogEventLevel level)
|
||||
{
|
||||
consoleDebugTimer.Stop();
|
||||
consoleDebugTimer.Interval = timeout * 60000;
|
||||
consoleDebugTimer.Start();
|
||||
|
||||
consoleLoggingLevelSwitch.MinimumLevel = level;
|
||||
_consoleLoggingLevelSwitch.MinimumLevel = level;
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n",
|
||||
InitialParametersClass.ApplicationNumber, consoleLoggingLevelSwitch.MinimumLevel);
|
||||
InitialParametersClass.ApplicationNumber, _consoleLoggingLevelSwitch.MinimumLevel);
|
||||
|
||||
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}");
|
||||
|
||||
@@ -471,52 +405,40 @@ public static class Debug
|
||||
CrestronConsole.PrintLine($"Error saving console debug level setting: {err}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debug level for the websocket sink
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
public static void SetWebSocketMinimumDebugLevel(LogEventLevel level)
|
||||
{
|
||||
websocketLoggingLevelSwitch.MinimumLevel = level;
|
||||
_websocketLoggingLevelSwitch.MinimumLevel = level;
|
||||
|
||||
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint)level);
|
||||
|
||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err);
|
||||
|
||||
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel);
|
||||
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum debug level for the error log sink
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
public static void SetErrorLogMinimumDebugLevel(LogEventLevel level)
|
||||
{
|
||||
errorLogLevelSwitch.MinimumLevel = level;
|
||||
_errorLogLevelSwitch.MinimumLevel = level;
|
||||
|
||||
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
|
||||
|
||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err);
|
||||
|
||||
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel);
|
||||
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the minimum debug level for the file sink
|
||||
/// </summary>
|
||||
public static void SetFileMinimumDebugLevel(LogEventLevel level)
|
||||
{
|
||||
errorLogLevelSwitch.MinimumLevel = level;
|
||||
_errorLogLevelSwitch.MinimumLevel = level;
|
||||
|
||||
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
|
||||
|
||||
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
|
||||
LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err);
|
||||
|
||||
LogMessage(LogEventLevel.Information, "File debug level set to {0}", websocketLoggingLevelSwitch.MinimumLevel);
|
||||
LogMessage(LogEventLevel.Information, "File debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -701,45 +623,21 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with at the specified level.
|
||||
/// </summary>
|
||||
public static void LogMessage(LogEventLevel level, string message, params object[] args)
|
||||
{
|
||||
_logger.Write(level, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with at the specified level and exception.
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
/// <param name="ex"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args)
|
||||
{
|
||||
_logger.Write(level, ex, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with at the specified level and device context.
|
||||
/// </summary> <param name="level"></param>
|
||||
/// <param name="keyed"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
LogMessage(level, message, keyed, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with at the specified level, exception, and device context.
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
/// <param name="ex"></param>
|
||||
/// <param name="device"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void LogMessage(LogEventLevel level, Exception ex, IKeyed device, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", device?.Key))
|
||||
@@ -749,13 +647,6 @@ public static class Debug
|
||||
}
|
||||
|
||||
#region Explicit methods for logging levels
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Verbose level and device context.
|
||||
/// </summary>
|
||||
/// <param name="keyed"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void LogVerbose(IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -764,9 +655,6 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Verbose level, exception, and device context.
|
||||
/// </summary>
|
||||
public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -775,25 +663,16 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Verbose level.
|
||||
/// </summary>
|
||||
public static void LogVerbose(string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Verbose, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Verbose level and exception.
|
||||
/// </summary>
|
||||
public static void LogVerbose(Exception ex, string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Verbose, ex, null, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Debug level and device context.
|
||||
/// </summary>
|
||||
public static void LogDebug(IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -802,9 +681,6 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Debug level, exception, and device context.
|
||||
/// </summary>
|
||||
public static void LogDebug(Exception ex, IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -813,25 +689,16 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Debug level.
|
||||
/// </summary>
|
||||
public static void LogDebug(string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Debug, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Debug level and exception.
|
||||
/// </summary>
|
||||
public static void LogDebug(Exception ex, string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Debug, ex, null, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Information level and device context.
|
||||
/// </summary>
|
||||
public static void LogInformation(IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -840,9 +707,6 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Information level, exception, and device context.
|
||||
/// </summary>
|
||||
public static void LogInformation(Exception ex, IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -851,25 +715,16 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Information level.
|
||||
/// </summary>
|
||||
public static void LogInformation(string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Information, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Information level and exception.
|
||||
/// </summary>
|
||||
public static void LogInformation(Exception ex, string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Information, ex, null, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Warning level and device context.
|
||||
/// </summary>
|
||||
public static void LogWarning(IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -878,9 +733,6 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Warning level, exception, and device context.
|
||||
/// </summary>
|
||||
public static void LogWarning(Exception ex, IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -889,25 +741,16 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Warning level.
|
||||
/// </summary>
|
||||
public static void LogWarning(string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Warning, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Warning level and exception.
|
||||
/// </summary>
|
||||
public static void LogWarning(Exception ex, string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Warning, ex, null, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Error level and device context.
|
||||
/// </summary>
|
||||
public static void LogError(IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -916,9 +759,6 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Error level, exception, and device context.
|
||||
/// </summary>
|
||||
public static void LogError(Exception ex, IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -927,25 +767,16 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Error level.
|
||||
/// </summary>
|
||||
public static void LogError(string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Error, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Error level and exception.
|
||||
/// </summary>
|
||||
public static void LogError(Exception ex, string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Error, ex, null, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Fatal level and device context.
|
||||
/// </summary>
|
||||
public static void LogFatal(IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -954,9 +785,6 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Fatal level, exception, and device context.
|
||||
/// </summary>
|
||||
public static void LogFatal(Exception ex, IKeyed keyed, string message, params object[] args)
|
||||
{
|
||||
using (LogContext.PushProperty("Key", keyed?.Key))
|
||||
@@ -965,19 +793,11 @@ public static class Debug
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Fatal level.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void LogFatal(string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Fatal, message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log a message with Fatal level and exception.
|
||||
/// </summary>
|
||||
public static void LogFatal(Exception ex, string message, params object[] args)
|
||||
{
|
||||
_logger.Write(LogEventLevel.Fatal, ex, null, message, args);
|
||||
@@ -1005,28 +825,127 @@ public static class Debug
|
||||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
[Obsolete("Use LogMessage methods. Will be removed in 2.2.0 and later versions")]
|
||||
public static void Console(uint level, string format, params object[] items)
|
||||
{
|
||||
|
||||
LogMessage(level, format, items);
|
||||
|
||||
//if (IsRunningOnAppliance)
|
||||
//{
|
||||
// CrestronConsole.PrintLine("[{0}]App {1} Lvl {2}:{3}", DateTime.Now.ToString("HH:mm:ss.fff"),
|
||||
// InitialParametersClass.ApplicationNumber,
|
||||
// level,
|
||||
// string.Format(format, items));
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs to Console when at-level, and all messages to error log, including device key
|
||||
/// </summary>
|
||||
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
|
||||
public static void Console(uint level, IKeyed dev, string format, params object[] items)
|
||||
{
|
||||
LogMessage(level, dev, format, items);
|
||||
|
||||
//if (Level >= level)
|
||||
// Console(level, "[{0}] {1}", dev.Key, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints message to console if current debug level is equal to or higher than the level of this message. Always sends message to Error Log.
|
||||
/// Uses CrestronConsole.PrintLine.
|
||||
/// </summary>
|
||||
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
|
||||
public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel,
|
||||
string format, params object[] items)
|
||||
{
|
||||
LogMessage(level, dev, format, items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs to Console when at-level, and all messages to error log
|
||||
/// </summary>
|
||||
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
|
||||
public static void Console(uint level, ErrorLogLevel errorLogLevel,
|
||||
string format, params object[] items)
|
||||
{
|
||||
LogMessage(level, format, items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at
|
||||
/// or above the level provided, then the output will be written to both console and the log. Otherwise
|
||||
/// it will only be written to the log.
|
||||
/// </summary>
|
||||
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
|
||||
public static void ConsoleWithLog(uint level, string format, params object[] items)
|
||||
{
|
||||
LogMessage(level, format, items);
|
||||
|
||||
// var str = string.Format(format, items);
|
||||
//if (Level >= level)
|
||||
// CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
|
||||
// CrestronLogger.WriteToLog(str, level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at
|
||||
/// or above the level provided, then the output will be written to both console and the log. Otherwise
|
||||
/// it will only be written to the log.
|
||||
/// </summary>
|
||||
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
|
||||
public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items)
|
||||
{
|
||||
LogMessage(level, dev, format, items);
|
||||
|
||||
// var str = string.Format(format, items);
|
||||
// CrestronLogger.WriteToLog(string.Format("[{0}] {1}", dev.Key, str), level);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints to log and error log
|
||||
/// </summary>
|
||||
/// <param name="errorLogLevel"></param>
|
||||
/// <param name="str"></param>
|
||||
[Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")]
|
||||
public static void LogError(ErrorLogLevel errorLogLevel, string str)
|
||||
{
|
||||
switch (errorLogLevel)
|
||||
{
|
||||
case ErrorLogLevel.Error:
|
||||
LogMessage(LogEventLevel.Error, str);
|
||||
break;
|
||||
case ErrorLogLevel.Warning:
|
||||
LogMessage(LogEventLevel.Warning, str);
|
||||
break;
|
||||
case ErrorLogLevel.Notice:
|
||||
LogMessage(LogEventLevel.Information, str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the memory object after timeout
|
||||
/// </summary>
|
||||
static void SaveMemoryOnTimeout()
|
||||
{
|
||||
LogInformation("Saving debug settings");
|
||||
Console(0, "Saving debug settings");
|
||||
if (_saveTimer == null)
|
||||
{
|
||||
_saveTimer = new Timer(SaveTimeoutMs) { AutoReset = false };
|
||||
_saveTimer.Elapsed += (s, e) =>
|
||||
_saveTimer = new CTimer(o =>
|
||||
{
|
||||
_saveTimer = null;
|
||||
SaveMemory();
|
||||
};
|
||||
_saveTimer.Start();
|
||||
}
|
||||
}, SaveTimeoutMs);
|
||||
else
|
||||
{
|
||||
_saveTimer.Stop();
|
||||
_saveTimer.Interval = SaveTimeoutMs;
|
||||
_saveTimer.Start();
|
||||
}
|
||||
_saveTimer.Reset(SaveTimeoutMs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1038,24 +957,15 @@ public static class Debug
|
||||
//if (!Directory.Exists(dir))
|
||||
// Directory.Create(dir);
|
||||
|
||||
try
|
||||
{
|
||||
var fileName = GetMemoryFileName();
|
||||
var fileName = GetMemoryFileName();
|
||||
|
||||
LogInformation("Loading debug settings file from {fileName}", fileName);
|
||||
LogMessage(LogEventLevel.Information, "Loading debug settings file from {fileName}", fileName);
|
||||
|
||||
using (var sw = new StreamWriter(fileName))
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(_contexts);
|
||||
sw.Write(json);
|
||||
sw.Flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
using (var sw = new StreamWriter(fileName))
|
||||
{
|
||||
ErrorLog.Error("Exception saving debug settings: {message}", ex);
|
||||
CrestronConsole.PrintLine("Exception saving debug settings: {message}", ex.Message);
|
||||
return;
|
||||
var json = JsonConvert.SerializeObject(_contexts);
|
||||
sw.Write(json);
|
||||
sw.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1096,4 +1006,27 @@ public static class Debug
|
||||
|
||||
return string.Format("{0}{1}user{1}debugSettings{1}{2}.json", Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error level to for message to be logged at
|
||||
/// </summary>
|
||||
public enum ErrorLogLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Error
|
||||
/// </summary>
|
||||
Error,
|
||||
/// <summary>
|
||||
/// Warning
|
||||
/// </summary>
|
||||
Warning,
|
||||
/// <summary>
|
||||
/// Notice
|
||||
/// </summary>
|
||||
Notice,
|
||||
/// <summary>
|
||||
/// None
|
||||
/// </summary>
|
||||
None,
|
||||
}
|
||||
}
|
||||
280
src/PepperDash.Core/Logging/DebugContext.cs
Normal file
280
src/PepperDash.Core/Logging/DebugContext.cs
Normal file
@@ -0,0 +1,280 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a debugging context
|
||||
/// </summary>
|
||||
public class DebugContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the folder location where a given program stores it's debug level memory. By default, the
|
||||
/// file written will be named appNdebug where N is 1-10.
|
||||
/// </summary>
|
||||
public string Key { get; private set; }
|
||||
|
||||
///// <summary>
|
||||
///// The name of the file containing the current debug settings.
|
||||
///// </summary>
|
||||
//string FileName = string.Format(@"\nvram\debug\app{0}Debug.json", InitialParametersClass.ApplicationNumber);
|
||||
|
||||
DebugContextSaveData SaveData;
|
||||
|
||||
int SaveTimeoutMs = 30000;
|
||||
|
||||
CTimer SaveTimer;
|
||||
|
||||
|
||||
static List<DebugContext> Contexts = new List<DebugContext>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates or gets a debug context
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public static DebugContext GetDebugContext(string key)
|
||||
{
|
||||
var context = Contexts.FirstOrDefault(c => c.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||
if (context == null)
|
||||
{
|
||||
context = new DebugContext(key);
|
||||
Contexts.Add(context);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do not use. For S+ access.
|
||||
/// </summary>
|
||||
public DebugContext() { }
|
||||
|
||||
DebugContext(string key)
|
||||
{
|
||||
Key = key;
|
||||
if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
|
||||
{
|
||||
// Add command to console
|
||||
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
|
||||
"appdebug:P [0-2]: Sets the application's console debug message level",
|
||||
ConsoleAccessLevelEnum.AccessOperator);
|
||||
}
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
||||
|
||||
LoadMemory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to save memory when shutting down
|
||||
/// </summary>
|
||||
/// <param name="programEventType"></param>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType == eProgramStatusEventType.Stopping)
|
||||
{
|
||||
if (SaveTimer != null)
|
||||
{
|
||||
SaveTimer.Stop();
|
||||
SaveTimer = null;
|
||||
}
|
||||
Console(0, "Saving debug settings");
|
||||
SaveMemory();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for console command
|
||||
/// </summary>
|
||||
/// <param name="levelString"></param>
|
||||
public void SetDebugFromConsole(string levelString)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(levelString.Trim()))
|
||||
{
|
||||
CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", SaveData.Level);
|
||||
return;
|
||||
}
|
||||
|
||||
SetDebugLevel(Convert.ToInt32(levelString));
|
||||
}
|
||||
catch
|
||||
{
|
||||
CrestronConsole.PrintLine("Usage: appdebug:P [0-2]");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the debug level
|
||||
/// </summary>
|
||||
/// <param name="level"> Valid values 0 (no debug), 1 (critical), 2 (all messages)</param>
|
||||
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>
|
||||
///
|
||||
/// </summary>
|
||||
public class DebugContextSaveData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int Level { get; set; }
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Serilog.Events;
|
||||
using Serilog.Events;
|
||||
using System;
|
||||
using Log = PepperDash.Core.Debug;
|
||||
|
||||
namespace PepperDash.Core.Logging;
|
||||
|
||||
@@ -1,70 +1,62 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using Crestron.SimplSharp;
|
||||
using JsonProperty = NewtonsoftJson::Newtonsoft.Json.JsonPropertyAttribute;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PepperDash.Core.Logging;
|
||||
|
||||
/// <summary>
|
||||
/// Class to persist current Debug settings across program restarts
|
||||
/// </summary>
|
||||
public class DebugContextCollection
|
||||
{
|
||||
public class DebugContextCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// To prevent threading issues with the DeviceDebugSettings collection
|
||||
/// </summary>
|
||||
private readonly object _deviceDebugSettingsLock = new();
|
||||
private readonly CCriticalSection _deviceDebugSettingsLock;
|
||||
|
||||
[JsonProperty("items")]
|
||||
private readonly Dictionary<string, DebugContextItem> _items = new Dictionary<string, DebugContextItem>();
|
||||
[JsonProperty("items")] private readonly Dictionary<string, DebugContextItem> _items;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of the debug settings for each device where the dictionary key is the device key
|
||||
/// </summary>
|
||||
[JsonProperty("deviceDebugSettings")]
|
||||
private Dictionary<string, object> DeviceDebugSettings { get; set; } = new Dictionary<string, object>();
|
||||
private Dictionary<string, object> DeviceDebugSettings { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public DebugContextCollection()
|
||||
{
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
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>
|
||||
/// 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>
|
||||
/// <summary>
|
||||
/// SetLevel method
|
||||
/// </summary>
|
||||
public void SetLevel(string contextKey, int level)
|
||||
{
|
||||
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>
|
||||
/// Gets a level or creates it if not existing
|
||||
/// </summary>
|
||||
/// <param name="contextKey"></param>
|
||||
/// <returns></returns>
|
||||
public DebugContextItem GetOrCreateItem(string contextKey)
|
||||
{
|
||||
if (!_items.ContainsKey(contextKey))
|
||||
_items[contextKey] = new DebugContextItem { Level = 0 };
|
||||
return _items[contextKey];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -75,8 +67,10 @@ public class DebugContextCollection
|
||||
/// <returns></returns>
|
||||
public void SetDebugSettingsForKey(string deviceKey, object settings)
|
||||
{
|
||||
lock (_deviceDebugSettingsLock)
|
||||
try
|
||||
{
|
||||
_deviceDebugSettingsLock.Enter();
|
||||
|
||||
if (DeviceDebugSettings.ContainsKey(deviceKey))
|
||||
{
|
||||
DeviceDebugSettings[deviceKey] = settings;
|
||||
@@ -84,6 +78,10 @@ public class DebugContextCollection
|
||||
else
|
||||
DeviceDebugSettings.Add(deviceKey, settings);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_deviceDebugSettingsLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -95,22 +93,22 @@ public class DebugContextCollection
|
||||
{
|
||||
return DeviceDebugSettings[deviceKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about
|
||||
/// </summary>
|
||||
public class DebugContextItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about
|
||||
/// </summary>
|
||||
public class DebugContextItem
|
||||
{
|
||||
/// <summary>
|
||||
/// The level of debug messages to print
|
||||
/// </summary>
|
||||
[JsonProperty("level")]
|
||||
public int Level { get; set; }
|
||||
[JsonProperty("level")]
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Property to tell the program not to intitialize when it boots, if desired
|
||||
/// </summary>
|
||||
[JsonProperty("doNotLoadOnNextBoot")]
|
||||
public bool DoNotLoadOnNextBoot { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using Crestron.SimplSharp;
|
||||
using Org.BouncyCastle.Asn1.X509;
|
||||
using Serilog;
|
||||
@@ -8,20 +5,19 @@ using Serilog.Configuration;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting;
|
||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||
using Serilog.Formatting.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Authentication;
|
||||
using WebSocketSharp;
|
||||
using WebSocketSharp.Server;
|
||||
using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
|
||||
using WebSocketSharp.Net;
|
||||
|
||||
namespace PepperDash.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected
|
||||
/// WebSocket clients.
|
||||
/// <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
|
||||
@@ -34,8 +30,8 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||
private const string _certificateName = "selfCres";
|
||||
private const string _certificatePassword = "cres12345";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the port number on which the HTTPS server is currently running.
|
||||
/// <summary>
|
||||
/// Gets the port number on which the HTTPS server is currently running.
|
||||
/// </summary>
|
||||
public int Port
|
||||
{ get
|
||||
@@ -46,8 +42,8 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the WebSocket URL for the current server instance.
|
||||
/// <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>
|
||||
@@ -60,8 +56,8 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the HTTPS server is currently listening for incoming connections.
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the HTTPS server is currently listening for incoming connections.
|
||||
/// </summary>
|
||||
public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
|
||||
|
||||
@@ -70,13 +66,13 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||
|
||||
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>
|
||||
/// <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)
|
||||
{
|
||||
@@ -105,7 +101,7 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||
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.LogInformation("DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress);
|
||||
CrestronConsole.PrintLine(string.Format("DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress));
|
||||
|
||||
var certificate = utility.CreateSelfSignedCertificate(string.Format("CN={0}.{1}", hostName, domainName), [string.Format("{0}.{1}", hostName, domainName), ipAddress], [KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth]);
|
||||
|
||||
@@ -118,17 +114,17 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(ex, "WSS CreateCert Failed: {0}", ex.Message);
|
||||
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
||||
//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>
|
||||
/// <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)
|
||||
{
|
||||
@@ -140,16 +136,16 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||
_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>
|
||||
/// <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.LogInformation("Starting Websocket Server on port: {0}", port);
|
||||
Debug.Console(0, "Starting Websocket Server on port: {0}", port);
|
||||
|
||||
|
||||
Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
|
||||
@@ -163,22 +159,22 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(certPath))
|
||||
{
|
||||
Debug.LogInformation("Assigning SSL Configuration");
|
||||
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.ClientCertificateRequired = false;
|
||||
_httpsServer.SslConfiguration.CheckCertificateRevocation = false;
|
||||
_httpsServer.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;
|
||||
//this is just to test, you might want to actually validate
|
||||
_httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
||||
{
|
||||
Debug.LogInformation("HTTPS ClientCerticateValidation Callback triggered");
|
||||
Debug.Console(0, "HTTPS ClientCerticateValidation Callback triggered");
|
||||
return true;
|
||||
};
|
||||
};
|
||||
}
|
||||
Debug.LogInformation("Adding Debug Client Service");
|
||||
Debug.Console(0, "Adding Debug Client Service");
|
||||
_httpsServer.AddWebSocketService<DebugClient>(_path);
|
||||
Debug.LogInformation("Assigning Log Info");
|
||||
Debug.Console(0, "Assigning Log Info");
|
||||
_httpsServer.Log.Level = LogLevel.Trace;
|
||||
_httpsServer.Log.Output = (d, s) =>
|
||||
{
|
||||
@@ -208,48 +204,48 @@ public class DebugWebsocketSink : ILogEventSink, IKeyed
|
||||
level = 4;
|
||||
break;
|
||||
}
|
||||
Debug.LogInformation("{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
|
||||
|
||||
Debug.Console(level, "{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
|
||||
};
|
||||
Debug.LogInformation("Starting");
|
||||
Debug.Console(0, "Starting");
|
||||
|
||||
_httpsServer.Start();
|
||||
Debug.LogInformation("Ready");
|
||||
Debug.Console(0, "Ready");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(ex, "WebSocket Failed to start {0}", ex.Message);
|
||||
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
||||
Debug.Console(0, "WebSocket Failed to start {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the WebSocket server if it is currently running.
|
||||
/// <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.LogInformation("Stopping Websocket Server");
|
||||
Debug.Console(0, "Stopping Websocket Server");
|
||||
_httpsServer?.Stop();
|
||||
|
||||
_httpsServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the logger to write log events to a debug WebSocket sink.
|
||||
/// <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>
|
||||
/// <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,
|
||||
@@ -259,9 +255,9 @@ public static class DebugWebsocketSinkExtensions
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a WebSocket client for debugging purposes, providing connection lifecycle management and message
|
||||
/// handling functionality.
|
||||
/// <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
|
||||
@@ -270,8 +266,8 @@ public class DebugClient : WebSocketBehavior
|
||||
{
|
||||
private DateTime _connectionTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duration of time the WebSocket connection has been active.
|
||||
/// <summary>
|
||||
/// Gets the duration of time the WebSocket connection has been active.
|
||||
/// </summary>
|
||||
public TimeSpan ConnectedDuration
|
||||
{
|
||||
@@ -288,21 +284,23 @@ public class DebugClient : WebSocketBehavior
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DebugClient"/> class.
|
||||
/// <summary>
|
||||
/// 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.LogInformation("DebugClient Created");
|
||||
Debug.Console(0, "DebugClient Created");
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnOpen()
|
||||
{
|
||||
base.OnOpen();
|
||||
|
||||
var url = Context.WebSocket.Url;
|
||||
Debug.LogInformation("New WebSocket Connection from: {0}", url);
|
||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url);
|
||||
|
||||
_connectionTime = DateTime.Now;
|
||||
}
|
||||
@@ -312,7 +310,7 @@ public class DebugClient : WebSocketBehavior
|
||||
{
|
||||
base.OnMessage(e);
|
||||
|
||||
Debug.LogVerbose("WebSocket UiClient Message: {0}", e.Data);
|
||||
Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -320,7 +318,8 @@ public class DebugClient : WebSocketBehavior
|
||||
{
|
||||
base.OnClose(e);
|
||||
|
||||
Debug.LogDebug("WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
|
||||
Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -328,7 +327,6 @@ public class DebugClient : WebSocketBehavior
|
||||
{
|
||||
base.OnError(e);
|
||||
|
||||
Debug.LogError(e.Exception, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
|
||||
Debug.LogVerbose("Stack Trace:\r{0}", e.Exception.StackTrace);
|
||||
Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,9 +59,6 @@ namespace PepperDash.Core.PasswordManagement;
|
||||
/// Retrieve password by index
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <summary>
|
||||
/// GetPasswordByIndex method
|
||||
/// </summary>
|
||||
public void GetPasswordByIndex(ushort key)
|
||||
{
|
||||
OnUshrtChange((ushort)PasswordManager.Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
|
||||
@@ -84,9 +81,6 @@ namespace PepperDash.Core.PasswordManagement;
|
||||
/// Password validation method
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <summary>
|
||||
/// ValidatePassword method
|
||||
/// </summary>
|
||||
public void ValidatePassword(string 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
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <summary>
|
||||
/// BuildPassword method
|
||||
/// </summary>
|
||||
public void BuildPassword(string data)
|
||||
{
|
||||
PasswordToValidate = String.Concat(PasswordToValidate, data);
|
||||
@@ -117,9 +108,9 @@ namespace PepperDash.Core.PasswordManagement;
|
||||
ValidatePassword(PasswordToValidate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ClearPassword method
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Clears the user entered password and resets the LEDs
|
||||
/// </summary>
|
||||
public void ClearPassword()
|
||||
{
|
||||
PasswordToValidate = "";
|
||||
|
||||
@@ -1,252 +1,240 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Timers;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core.PasswordManagement;
|
||||
|
||||
/// <summary>
|
||||
/// Allows passwords to be stored and managed
|
||||
/// </summary>
|
||||
public class PasswordManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Public dictionary of known passwords
|
||||
/// </summary>
|
||||
public static Dictionary<uint, string> Passwords = new Dictionary<uint, string>();
|
||||
/// <summary>
|
||||
/// Private dictionary, used when passwords are updated
|
||||
/// </summary>
|
||||
private Dictionary<uint, string> _passwords = new Dictionary<uint, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Timer used to wait until password changes have stopped before updating the dictionary
|
||||
/// </summary>
|
||||
Timer PasswordTimer;
|
||||
/// <summary>
|
||||
/// Timer length
|
||||
/// </summary>
|
||||
public long PasswordTimerElapsedMs = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// Boolean event
|
||||
/// </summary>
|
||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
||||
/// <summary>
|
||||
/// Ushort event
|
||||
/// </summary>
|
||||
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
||||
/// <summary>
|
||||
/// String event
|
||||
/// </summary>
|
||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
||||
/// <summary>
|
||||
/// Event to notify clients of an updated password at the specified index (uint)
|
||||
/// </summary>
|
||||
public static event EventHandler<StringChangeEventArgs> PasswordChange;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public PasswordManager()
|
||||
public class PasswordManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Public dictionary of known passwords
|
||||
/// </summary>
|
||||
public static Dictionary<uint, string> Passwords = new Dictionary<uint, string>();
|
||||
/// <summary>
|
||||
/// Private dictionary, used when passwords are updated
|
||||
/// </summary>
|
||||
private Dictionary<uint, string> _passwords = new Dictionary<uint, string>();
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Timer used to wait until password changes have stopped before updating the dictionary
|
||||
/// </summary>
|
||||
CTimer PasswordTimer;
|
||||
/// <summary>
|
||||
/// Timer length
|
||||
/// </summary>
|
||||
public long PasswordTimerElapsedMs = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize password manager
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
if (Passwords == null)
|
||||
Passwords = new Dictionary<uint, string>();
|
||||
/// <summary>
|
||||
/// Boolean event
|
||||
/// </summary>
|
||||
public event EventHandler<BoolChangeEventArgs> BoolChange;
|
||||
/// <summary>
|
||||
/// Ushort event
|
||||
/// </summary>
|
||||
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
|
||||
/// <summary>
|
||||
/// String event
|
||||
/// </summary>
|
||||
public event EventHandler<StringChangeEventArgs> StringChange;
|
||||
/// <summary>
|
||||
/// Event to notify clients of an updated password at the specified index (uint)
|
||||
/// </summary>
|
||||
public static event EventHandler<StringChangeEventArgs> PasswordChange;
|
||||
|
||||
if (_passwords == null)
|
||||
_passwords = new Dictionary<uint, string>();
|
||||
|
||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates password stored in the dictonary
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <summary>
|
||||
/// UpdatePassword method
|
||||
/// </summary>
|
||||
public void UpdatePassword(ushort key, string password)
|
||||
{
|
||||
// validate the parameters
|
||||
if (key > 0 && string.IsNullOrEmpty(password))
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public PasswordManager()
|
||||
{
|
||||
Debug.LogDebug("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
/// <summary>
|
||||
/// Initialize password manager
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
// if key exists, update the value
|
||||
if (_passwords.ContainsKey(key))
|
||||
_passwords[key] = password;
|
||||
// else add the key & value
|
||||
else
|
||||
_passwords.Add(key, password);
|
||||
if (Passwords == null)
|
||||
Passwords = new Dictionary<uint, string>();
|
||||
|
||||
Debug.LogDebug("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key]);
|
||||
if (_passwords == null)
|
||||
_passwords = new Dictionary<uint, string>();
|
||||
|
||||
if (PasswordTimer == null)
|
||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordInitializedChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates password stored in the dictonary
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="password"></param>
|
||||
public void UpdatePassword(ushort key, string password)
|
||||
{
|
||||
// validate the parameters
|
||||
if (key > 0 && string.IsNullOrEmpty(password))
|
||||
{
|
||||
PasswordTimer = new Timer(PasswordTimerElapsedMs) { AutoReset = false };
|
||||
PasswordTimer.Elapsed += (s, e) => PasswordTimerElapsed(s, e);
|
||||
PasswordTimer.Start();
|
||||
Debug.LogDebug("PasswordManager.UpdatePassword: Timer Started");
|
||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
||||
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: key [{0}] or password are not valid", key, password));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
PasswordTimer.Stop();
|
||||
PasswordTimer.Interval = PasswordTimerElapsedMs;
|
||||
PasswordTimer.Start();
|
||||
Debug.LogDebug("PasswordManager.UpdatePassword: Timer Reset");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e);
|
||||
Debug.LogError(e, msg);
|
||||
Debug.LogVerbose("Stack Trace:\r{0}", e.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timer callback function
|
||||
/// </summary>
|
||||
private void PasswordTimerElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
PasswordTimer.Stop();
|
||||
Debug.LogDebug("PasswordManager.PasswordTimerElapsed: Timer Stopped");
|
||||
OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
||||
foreach (var pw in _passwords)
|
||||
try
|
||||
{
|
||||
// if key exists, continue
|
||||
if (Passwords.ContainsKey(pw.Key))
|
||||
{
|
||||
Debug.LogDebug("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value);
|
||||
if (Passwords[pw.Key] != _passwords[pw.Key])
|
||||
{
|
||||
Passwords[pw.Key] = _passwords[pw.Key];
|
||||
Debug.LogDebug("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key]);
|
||||
OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange);
|
||||
}
|
||||
}
|
||||
// if key exists, update the value
|
||||
if(_passwords.ContainsKey(key))
|
||||
_passwords[key] = password;
|
||||
// else add the key & value
|
||||
else
|
||||
_passwords.Add(key, password);
|
||||
|
||||
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: _password[{0}] = {1}", key, _passwords[key]));
|
||||
|
||||
if (PasswordTimer == null)
|
||||
{
|
||||
Passwords.Add(pw.Key, pw.Value);
|
||||
PasswordTimer = new CTimer((o) => PasswordTimerElapsed(), PasswordTimerElapsedMs);
|
||||
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Started"));
|
||||
OnBoolChange(true, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
||||
}
|
||||
else
|
||||
{
|
||||
PasswordTimer.Reset(PasswordTimerElapsedMs);
|
||||
Debug.Console(1, string.Format("PasswordManager.UpdatePassword: CTimer Reset"));
|
||||
}
|
||||
}
|
||||
OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
|
||||
catch (Exception e)
|
||||
{
|
||||
var msg = string.Format("PasswordManager.UpdatePassword key-value[{0}, {1}] failed:\r{2}", key, password, e);
|
||||
Debug.Console(1, msg);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
/// <summary>
|
||||
/// CTimer callback function
|
||||
/// </summary>
|
||||
private void PasswordTimerElapsed()
|
||||
{
|
||||
var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", ex.Message);
|
||||
Debug.LogError(ex, msg);
|
||||
Debug.LogVerbose("Stack Trace:\r{0}", ex.StackTrace);
|
||||
try
|
||||
{
|
||||
PasswordTimer.Stop();
|
||||
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: CTimer Stopped"));
|
||||
OnBoolChange(false, 0, PasswordManagementConstants.PasswordUpdateBusyChange);
|
||||
foreach (var pw in _passwords)
|
||||
{
|
||||
// if key exists, continue
|
||||
if (Passwords.ContainsKey(pw.Key))
|
||||
{
|
||||
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: pw.key[{0}] = {1}", pw.Key, pw.Value));
|
||||
if (Passwords[pw.Key] != _passwords[pw.Key])
|
||||
{
|
||||
Passwords[pw.Key] = _passwords[pw.Key];
|
||||
Debug.Console(1, string.Format("PasswordManager.PasswordTimerElapsed: Updated Password[{0} = {1}", pw.Key, Passwords[pw.Key]));
|
||||
OnPasswordChange(Passwords[pw.Key], (ushort)pw.Key, PasswordManagementConstants.StringValueChange);
|
||||
}
|
||||
}
|
||||
// else add the key & value
|
||||
else
|
||||
{
|
||||
Passwords.Add(pw.Key, pw.Value);
|
||||
}
|
||||
}
|
||||
OnUshrtChange((ushort)Passwords.Count, 0, PasswordManagementConstants.PasswordManagerCountChange);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var msg = string.Format("PasswordManager.PasswordTimerElapsed failed:\r{0}", e);
|
||||
Debug.Console(1, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to change the default timer value, (default 5000ms/5s)
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <summary>
|
||||
/// PasswordTimerMs method
|
||||
/// </summary>
|
||||
public void PasswordTimerMs(ushort time)
|
||||
{
|
||||
PasswordTimerElapsedMs = Convert.ToInt64(time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for debugging to see what passwords are in the lists
|
||||
/// </summary>
|
||||
public void ListPasswords()
|
||||
{
|
||||
Debug.LogInformation("PasswordManager.ListPasswords:\r");
|
||||
foreach (var pw in Passwords)
|
||||
Debug.LogInformation("Passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
||||
Debug.LogInformation("\n");
|
||||
foreach (var pw in _passwords)
|
||||
Debug.LogInformation("_passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protected boolean change event handler
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnBoolChange(bool state, ushort index, ushort type)
|
||||
{
|
||||
var handler = BoolChange;
|
||||
if (handler != null)
|
||||
/// <summary>
|
||||
/// Method to change the default timer value, (default 5000ms/5s)
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
public void PasswordTimerMs(ushort time)
|
||||
{
|
||||
var args = new BoolChangeEventArgs(state, type);
|
||||
args.Index = index;
|
||||
BoolChange(this, args);
|
||||
PasswordTimerElapsedMs = Convert.ToInt64(time);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protected ushort change event handler
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnUshrtChange(ushort value, ushort index, ushort type)
|
||||
{
|
||||
var handler = UshrtChange;
|
||||
if (handler != null)
|
||||
/// <summary>
|
||||
/// Helper method for debugging to see what passwords are in the lists
|
||||
/// </summary>
|
||||
public void ListPasswords()
|
||||
{
|
||||
var args = new UshrtChangeEventArgs(value, type);
|
||||
args.Index = index;
|
||||
UshrtChange(this, args);
|
||||
Debug.Console(0, "PasswordManager.ListPasswords:\r");
|
||||
foreach (var pw in Passwords)
|
||||
Debug.Console(0, "Passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
||||
Debug.Console(0, "\n");
|
||||
foreach (var pw in _passwords)
|
||||
Debug.Console(0, "_passwords[{0}]: {1}\r", pw.Key, pw.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protected string change event handler
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnStringChange(string value, ushort index, ushort type)
|
||||
{
|
||||
var handler = StringChange;
|
||||
if (handler != null)
|
||||
/// <summary>
|
||||
/// Protected boolean change event handler
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnBoolChange(bool state, ushort index, ushort type)
|
||||
{
|
||||
var args = new StringChangeEventArgs(value, type);
|
||||
args.Index = index;
|
||||
StringChange(this, args);
|
||||
var handler = BoolChange;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new BoolChangeEventArgs(state, type);
|
||||
args.Index = index;
|
||||
BoolChange(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protected password change event handler
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnPasswordChange(string value, ushort index, ushort type)
|
||||
{
|
||||
var handler = PasswordChange;
|
||||
if (handler != null)
|
||||
/// <summary>
|
||||
/// Protected ushort change event handler
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnUshrtChange(ushort value, ushort index, ushort type)
|
||||
{
|
||||
var args = new StringChangeEventArgs(value, type);
|
||||
args.Index = index;
|
||||
PasswordChange(this, args);
|
||||
var handler = UshrtChange;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new UshrtChangeEventArgs(value, type);
|
||||
args.Index = index;
|
||||
UshrtChange(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protected string change event handler
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnStringChange(string value, ushort index, ushort type)
|
||||
{
|
||||
var handler = StringChange;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new StringChangeEventArgs(value, type);
|
||||
args.Index = index;
|
||||
StringChange(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protected password change event handler
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="type"></param>
|
||||
protected void OnPasswordChange(string value, ushort index, ushort type)
|
||||
{
|
||||
var handler = PasswordChange;
|
||||
if (handler != null)
|
||||
{
|
||||
var args = new StringChangeEventArgs(value, type);
|
||||
args.Index = index;
|
||||
PasswordChange(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,13 +35,16 @@
|
||||
<EmbeddedResource Remove="Properties\**" />
|
||||
<None Remove="lib\**" />
|
||||
<None Remove="Properties\**" />
|
||||
</ItemGroup>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.1" />
|
||||
<PackageReference Include="Crestron.SimplSharp.SDK.Library" Version="2.21.128" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4">
|
||||
<Aliases>global,NewtonsoftJson</Aliases>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||
@@ -50,6 +53,9 @@
|
||||
<PackageReference Include="SSH.NET" Version="2025.0.0" />
|
||||
<PackageReference Include="WebSocketSharp-netstandard" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6'">
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Comm\._GenericSshClient.cs" />
|
||||
<Compile Remove="Comm\._GenericTcpIpClient.cs" />
|
||||
|
||||
@@ -68,9 +68,9 @@ namespace PepperDash.Core.SystemInfo;
|
||||
public const ushort ProgramConfigChange = 305;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a ProcessorChangeEventArgs
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Processor Change Event Args Class
|
||||
/// </summary>
|
||||
public class ProcessorChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
@@ -114,9 +114,9 @@ namespace PepperDash.Core.SystemInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a EthernetChangeEventArgs
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Ethernet Change Event Args Class
|
||||
/// </summary>
|
||||
public class EthernetChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
@@ -165,9 +165,9 @@ namespace PepperDash.Core.SystemInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a ControlSubnetChangeEventArgs
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Control Subnet Chage Event Args Class
|
||||
/// </summary>
|
||||
public class ControlSubnetChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
@@ -211,9 +211,9 @@ namespace PepperDash.Core.SystemInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a ProgramChangeEventArgs
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Program Change Event Args Class
|
||||
/// </summary>
|
||||
public class ProgramChangeEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -100,9 +100,9 @@ namespace PepperDash.Core.SystemInfo;
|
||||
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetEthernetInfo method
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Gets the current ethernet info
|
||||
/// </summary>
|
||||
public void GetEthernetInfo()
|
||||
{
|
||||
OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange);
|
||||
@@ -161,9 +161,9 @@ namespace PepperDash.Core.SystemInfo;
|
||||
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetControlSubnetInfo method
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Gets the current control subnet info
|
||||
/// </summary>
|
||||
public void GetControlSubnetInfo()
|
||||
{
|
||||
OnBoolChange(true, 0, SystemInfoConstants.BusyBoolChange);
|
||||
@@ -206,9 +206,6 @@ namespace PepperDash.Core.SystemInfo;
|
||||
/// Gets the program info by index
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <summary>
|
||||
/// GetProgramInfoByIndex method
|
||||
/// </summary>
|
||||
public void GetProgramInfoByIndex(ushort index)
|
||||
{
|
||||
if (index < 1 || index > 10)
|
||||
@@ -266,9 +263,9 @@ namespace PepperDash.Core.SystemInfo;
|
||||
OnBoolChange(false, 0, SystemInfoConstants.BusyBoolChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RefreshProcessorUptime method
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Gets the processor uptime and passes it to S+
|
||||
/// </summary>
|
||||
public void RefreshProcessorUptime()
|
||||
{
|
||||
try
|
||||
@@ -290,9 +287,6 @@ namespace PepperDash.Core.SystemInfo;
|
||||
/// Gets the program uptime, by index, and passes it to S+
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <summary>
|
||||
/// RefreshProgramUptimeByIndex method
|
||||
/// </summary>
|
||||
public void RefreshProgramUptimeByIndex(int index)
|
||||
{
|
||||
try
|
||||
@@ -314,9 +308,6 @@ namespace PepperDash.Core.SystemInfo;
|
||||
/// Sends command to console, passes response back using string change event
|
||||
/// </summary>
|
||||
/// <param name="cmd"></param>
|
||||
/// <summary>
|
||||
/// SendConsoleCommand method
|
||||
/// </summary>
|
||||
public void SendConsoleCommand(string cmd)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cmd))
|
||||
|
||||
@@ -144,9 +144,6 @@ namespace PepperDash.Core.Web.RequestHandlers;
|
||||
/// Process request
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <summary>
|
||||
/// ProcessRequest method
|
||||
/// </summary>
|
||||
public void ProcessRequest(HttpCwsContext context)
|
||||
{
|
||||
Action<HttpCwsContext> handler;
|
||||
|
||||
@@ -1,252 +1,283 @@
|
||||
extern alias NewtonsoftJson;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharp.WebScripting;
|
||||
using Formatting = NewtonsoftJson::Newtonsoft.Json.Formatting;
|
||||
using JsonConvert = NewtonsoftJson::Newtonsoft.Json.JsonConvert;
|
||||
using JObject = NewtonsoftJson::Newtonsoft.Json.Linq.JObject;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core.Web.RequestHandlers;
|
||||
using PepperDash.Core.Logging;
|
||||
|
||||
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>
|
||||
/// Gets or sets the Key
|
||||
/// Web API server
|
||||
/// </summary>
|
||||
public string Key { get; private set; }
|
||||
|
||||
/// <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)
|
||||
public class WebApiServer : IKeyName
|
||||
{
|
||||
}
|
||||
private const string SplusKey = "Uninitialized Web API Server";
|
||||
private const string DefaultName = "Web API Server";
|
||||
private const string DefaultBasePath = "/api";
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="basePath"></param>
|
||||
public WebApiServer(string key, string basePath)
|
||||
: this(key, DefaultName, basePath)
|
||||
{
|
||||
}
|
||||
private const uint DebugTrace = 0;
|
||||
private const uint DebugInfo = 1;
|
||||
private const uint DebugVerbose = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </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;
|
||||
private readonly CCriticalSection _serverLock = new CCriticalSection();
|
||||
private HttpCwsServer _server;
|
||||
|
||||
if (_server == null) _server = new HttpCwsServer(BasePath);
|
||||
/// <summary>
|
||||
/// Web API server key
|
||||
/// </summary>
|
||||
public string Key { get; private set; }
|
||||
|
||||
_server.setProcessName(Key);
|
||||
_server.HttpRequestHandler = new DefaultRequestHandler();
|
||||
/// <summary>
|
||||
/// Web API server name
|
||||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
|
||||
CrestronEnvironment.EthernetEventHandler += CrestronEnvironment_EthernetEventHandler;
|
||||
}
|
||||
/// <summary>
|
||||
/// CWS base path, will default to "/api" if not set via initialize method
|
||||
/// </summary>
|
||||
public string BasePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Program status event handler
|
||||
/// </summary>
|
||||
/// <param name="programEventType"></param>
|
||||
void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
|
||||
{
|
||||
if (programEventType != eProgramStatusEventType.Stopping) return;
|
||||
/// <summary>
|
||||
/// Indicates CWS is registered with base path
|
||||
/// </summary>
|
||||
public bool IsRegistered { get; private set; }
|
||||
|
||||
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>
|
||||
/// Ethernet event handler
|
||||
/// </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)
|
||||
/// <summary>
|
||||
/// Constructor for S+. Make sure to set necessary properties using init method
|
||||
/// </summary>
|
||||
public WebApiServer()
|
||||
: this(SplusKey, DefaultName, null)
|
||||
{
|
||||
this.LogInformation("Ethernet link up. Server is already registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.LogInformation("Ethernet link up. Starting server");
|
||||
|
||||
Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize method
|
||||
/// </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)
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="basePath"></param>
|
||||
public WebApiServer(string key, string basePath)
|
||||
: this(key, DefaultName, basePath)
|
||||
{
|
||||
this.LogWarning("Failed to add route, route parameter is null");
|
||||
return;
|
||||
}
|
||||
|
||||
_server.Routes.Add(route);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a route from CWS
|
||||
/// </summary>
|
||||
/// <param name="route"></param>
|
||||
/// <summary>
|
||||
/// RemoveRoute method
|
||||
/// </summary>
|
||||
public void RemoveRoute(HttpCwsRoute route)
|
||||
{
|
||||
if (route == null)
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="basePath"></param>
|
||||
public WebApiServer(string key, string name, string basePath)
|
||||
{
|
||||
this.LogWarning("Failed to remove route, route parameter is null");
|
||||
return;
|
||||
Key = key;
|
||||
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>
|
||||
/// GetRouteCollection method
|
||||
/// </summary>
|
||||
public HttpCwsRouteCollection GetRouteCollection()
|
||||
{
|
||||
return _server.Routes;
|
||||
}
|
||||
Debug.Console(DebugInfo, this, "Program stopping. stopping server");
|
||||
|
||||
/// <summary>
|
||||
/// Starts CWS instance
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
lock (_serverLock)
|
||||
Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ethernet event handler
|
||||
/// </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
|
||||
{
|
||||
_serverLock.Enter();
|
||||
|
||||
if (_server == null)
|
||||
{
|
||||
this.LogDebug("Server is null, unable to start");
|
||||
Debug.Console(DebugInfo, this, "Server is null, unable to start");
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsRegistered)
|
||||
{
|
||||
this.LogDebug("Server has already been started");
|
||||
Debug.Console(DebugInfo, this, "Server has already been started");
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
this.LogException(ex, "Start Exception Message: {0}", ex.Message);
|
||||
this.LogVerbose("Start Exception StackTrace: {0}", ex.StackTrace);
|
||||
Debug.Console(DebugInfo, this, "Start Exception Message: {0}", ex.Message);
|
||||
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>
|
||||
/// Stop method
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
lock (_serverLock)
|
||||
/// <summary>
|
||||
/// Stop CWS instance
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
_serverLock.Enter();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 = null;
|
||||
}
|
||||
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);
|
||||
}
|
||||
} // end lock
|
||||
}
|
||||
finally
|
||||
{
|
||||
_serverLock.Leave();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
/// <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)
|
||||
{
|
||||
var j = JsonConvert.SerializeObject(args.Context, Formatting.Indented);
|
||||
this.LogVerbose("RecieveRequestEventHandler Context:\x0d\x0a{0}", j);
|
||||
try
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LogException(ex, "ReceivedRequestEventHandler Exception Message: {0}", ex.Message);
|
||||
this.LogVerbose("ReceivedRequestEventHandler Exception StackTrace: {0}", ex.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/PepperDash.Core/WebApi/Presets/Preset.cs
Normal file
86
src/PepperDash.Core/WebApi/Presets/Preset.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
|
||||
namespace PepperDash.Core.WebApi.Presets;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a preset
|
||||
/// </summary>
|
||||
public class Preset
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of preset
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User ID
|
||||
/// </summary>
|
||||
public int UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Room Type ID
|
||||
/// </summary>
|
||||
public int RoomTypeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Preset Name
|
||||
/// </summary>
|
||||
public string PresetName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Preset Number
|
||||
/// </summary>
|
||||
public int PresetNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Preset Data
|
||||
/// </summary>
|
||||
public string Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public Preset()
|
||||
{
|
||||
PresetName = "";
|
||||
PresetNumber = 1;
|
||||
Data = "{}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class PresetReceivedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// True when the preset is found
|
||||
/// </summary>
|
||||
public bool LookupSuccess { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// S+ helper
|
||||
/// </summary>
|
||||
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
|
||||
|
||||
/// <summary>
|
||||
/// The preset
|
||||
/// </summary>
|
||||
public Preset Preset { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// For Simpl+
|
||||
/// </summary>
|
||||
public PresetReceivedEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="preset"></param>
|
||||
/// <param name="success"></param>
|
||||
public PresetReceivedEventArgs(Preset preset, bool success)
|
||||
{
|
||||
LookupSuccess = success;
|
||||
Preset = preset;
|
||||
}
|
||||
}
|
||||
92
src/PepperDash.Core/WebApi/Presets/User.cs
Normal file
92
src/PepperDash.Core/WebApi/Presets/User.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp;
|
||||
|
||||
namespace PepperDash.Core.WebApi.Presets;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class User
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string ExternalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string FirstName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string LastName { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserReceivedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// True when user is found
|
||||
/// </summary>
|
||||
public bool LookupSuccess { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// For stupid S+
|
||||
/// </summary>
|
||||
public ushort ULookupSuccess { get { return (ushort)(LookupSuccess ? 1 : 0); } }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public User User { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// For Simpl+
|
||||
/// </summary>
|
||||
public UserReceivedEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="success"></param>
|
||||
public UserReceivedEventArgs(User user, bool success)
|
||||
{
|
||||
LookupSuccess = success;
|
||||
User = user;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UserAndRoomMessage
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int RoomTypeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int PresetNumber { get; set; }
|
||||
}
|
||||
272
src/PepperDash.Core/WebApi/Presets/WebApiPasscodeClient.cs
Normal file
272
src/PepperDash.Core/WebApi/Presets/WebApiPasscodeClient.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using Crestron.SimplSharp; // For Basic SIMPL# Classes
|
||||
using Crestron.SimplSharp.CrestronIO;
|
||||
using Crestron.SimplSharp.Net.Http;
|
||||
using Crestron.SimplSharp.Net.Https;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core.JsonToSimpl;
|
||||
|
||||
|
||||
namespace PepperDash.Core.WebApi.Presets;
|
||||
|
||||
/// <summary>
|
||||
/// Passcode client for the WebApi
|
||||
/// </summary>
|
||||
public class WebApiPasscodeClient : IKeyed
|
||||
{
|
||||
/// <summary>
|
||||
/// Notifies when user received
|
||||
/// </summary>
|
||||
public event EventHandler<UserReceivedEventArgs> UserReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Notifies when Preset received
|
||||
/// </summary>
|
||||
public event EventHandler<PresetReceivedEventArgs> PresetReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for this instance
|
||||
/// </summary>
|
||||
public string Key { get; private set; }
|
||||
|
||||
//string JsonMasterKey;
|
||||
|
||||
/// <summary>
|
||||
/// An embedded JsonToSimpl master object.
|
||||
/// </summary>
|
||||
JsonToSimplGenericMaster J2SMaster;
|
||||
|
||||
string UrlBase;
|
||||
|
||||
string DefaultPresetJsonFilePath;
|
||||
|
||||
User CurrentUser;
|
||||
|
||||
Preset CurrentPreset;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// SIMPL+ can only execute the default constructor. If you have variables that require initialization, please
|
||||
/// use an Initialize method
|
||||
/// </summary>
|
||||
public WebApiPasscodeClient()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the instance
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="jsonMasterKey"></param>
|
||||
/// <param name="urlBase"></param>
|
||||
/// <param name="defaultPresetJsonFilePath"></param>
|
||||
public void Initialize(string key, string jsonMasterKey, string urlBase, string defaultPresetJsonFilePath)
|
||||
{
|
||||
Key = key;
|
||||
//JsonMasterKey = jsonMasterKey;
|
||||
UrlBase = urlBase;
|
||||
DefaultPresetJsonFilePath = defaultPresetJsonFilePath;
|
||||
|
||||
J2SMaster = new JsonToSimplGenericMaster();
|
||||
J2SMaster.SaveCallback = this.SaveCallback;
|
||||
J2SMaster.Initialize(jsonMasterKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user for a passcode
|
||||
/// </summary>
|
||||
/// <param name="passcode"></param>
|
||||
public void GetUserForPasscode(string passcode)
|
||||
{
|
||||
// Bullshit duplicate code here... These two cases should be the same
|
||||
// except for https/http and the certificate ignores
|
||||
if (!UrlBase.StartsWith("https"))
|
||||
return;
|
||||
var req = new HttpsClientRequest();
|
||||
req.Url = new UrlParser(UrlBase + "/api/users/dopin");
|
||||
req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post;
|
||||
req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json"));
|
||||
req.Header.AddHeader(new HttpsHeader("Accept", "application/json"));
|
||||
var jo = new JObject();
|
||||
jo.Add("pin", passcode);
|
||||
req.ContentString = jo.ToString();
|
||||
|
||||
var client = new HttpsClient();
|
||||
client.HostVerification = false;
|
||||
client.PeerVerification = false;
|
||||
var resp = client.Dispatch(req);
|
||||
var handler = UserReceived;
|
||||
if (resp.Code == 200)
|
||||
{
|
||||
//CrestronConsole.PrintLine("Received: {0}", resp.ContentString);
|
||||
var user = JsonConvert.DeserializeObject<User>(resp.ContentString);
|
||||
CurrentUser = user;
|
||||
if (handler != null)
|
||||
UserReceived(this, new UserReceivedEventArgs(user, true));
|
||||
}
|
||||
else
|
||||
if (handler != null)
|
||||
UserReceived(this, new UserReceivedEventArgs(null, false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="roomTypeId"></param>
|
||||
/// <param name="presetNumber"></param>
|
||||
public void GetPresetForThisUser(int roomTypeId, int presetNumber)
|
||||
{
|
||||
if (CurrentUser == null)
|
||||
{
|
||||
CrestronConsole.PrintLine("GetPresetForThisUser no user loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = new UserAndRoomMessage
|
||||
{
|
||||
UserId = CurrentUser.Id,
|
||||
RoomTypeId = roomTypeId,
|
||||
PresetNumber = presetNumber
|
||||
};
|
||||
|
||||
var handler = PresetReceived;
|
||||
try
|
||||
{
|
||||
if (!UrlBase.StartsWith("https"))
|
||||
return;
|
||||
var req = new HttpsClientRequest();
|
||||
req.Url = new UrlParser(UrlBase + "/api/presets/userandroom");
|
||||
req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post;
|
||||
req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json"));
|
||||
req.Header.AddHeader(new HttpsHeader("Accept", "application/json"));
|
||||
req.ContentString = JsonConvert.SerializeObject(msg);
|
||||
|
||||
var client = new HttpsClient();
|
||||
client.HostVerification = false;
|
||||
client.PeerVerification = false;
|
||||
|
||||
// ask for the preset
|
||||
var resp = client.Dispatch(req);
|
||||
if (resp.Code == 200) // got it
|
||||
{
|
||||
//Debug.Console(1, this, "Received: {0}", resp.ContentString);
|
||||
var preset = JsonConvert.DeserializeObject<Preset>(resp.ContentString);
|
||||
CurrentPreset = preset;
|
||||
|
||||
//if there's no preset data, load the template
|
||||
if (preset.Data == null || preset.Data.Trim() == string.Empty || JObject.Parse(preset.Data).Count == 0)
|
||||
{
|
||||
//Debug.Console(1, this, "Loaded preset has no data. Loading default template.");
|
||||
LoadDefaultPresetData();
|
||||
return;
|
||||
}
|
||||
|
||||
J2SMaster.LoadWithJson(preset.Data);
|
||||
if (handler != null)
|
||||
PresetReceived(this, new PresetReceivedEventArgs(preset, true));
|
||||
}
|
||||
else // no existing preset
|
||||
{
|
||||
CurrentPreset = new Preset();
|
||||
LoadDefaultPresetData();
|
||||
if (handler != null)
|
||||
PresetReceived(this, new PresetReceivedEventArgs(null, false));
|
||||
}
|
||||
}
|
||||
catch (HttpException e)
|
||||
{
|
||||
var resp = e.Response;
|
||||
Debug.Console(1, this, "No preset received (code {0}). Loading default template", resp.Code);
|
||||
LoadDefaultPresetData();
|
||||
if (handler != null)
|
||||
PresetReceived(this, new PresetReceivedEventArgs(null, false));
|
||||
}
|
||||
}
|
||||
|
||||
void LoadDefaultPresetData()
|
||||
{
|
||||
CurrentPreset = null;
|
||||
if (!File.Exists(DefaultPresetJsonFilePath))
|
||||
{
|
||||
Debug.Console(0, this, "Cannot load default preset file. Saving will not work");
|
||||
return;
|
||||
}
|
||||
using (StreamReader sr = new StreamReader(DefaultPresetJsonFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = sr.ReadToEnd();
|
||||
J2SMaster.SetJsonWithoutEvaluating(data);
|
||||
CurrentPreset = new Preset() { Data = data, UserId = CurrentUser.Id };
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Console(0, this, "Error reading default preset JSON: \r{0}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="roomTypeId"></param>
|
||||
/// <param name="presetNumber"></param>
|
||||
public void SavePresetForThisUser(int roomTypeId, int presetNumber)
|
||||
{
|
||||
if (CurrentPreset == null)
|
||||
LoadDefaultPresetData();
|
||||
//return;
|
||||
|
||||
//// A new preset needs to have its numbers set
|
||||
//if (CurrentPreset.IsNewPreset)
|
||||
//{
|
||||
CurrentPreset.UserId = CurrentUser.Id;
|
||||
CurrentPreset.RoomTypeId = roomTypeId;
|
||||
CurrentPreset.PresetNumber = presetNumber;
|
||||
//}
|
||||
J2SMaster.Save(); // Will trigger callback when ready
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// After save operation on JSON master happens, send it to server
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
void SaveCallback(string json)
|
||||
{
|
||||
CurrentPreset.Data = json;
|
||||
|
||||
if (!UrlBase.StartsWith("https"))
|
||||
return;
|
||||
var req = new HttpsClientRequest();
|
||||
req.RequestType = Crestron.SimplSharp.Net.Https.RequestType.Post;
|
||||
req.Url = new UrlParser(string.Format("{0}/api/presets/addorchange", UrlBase));
|
||||
req.Header.AddHeader(new HttpsHeader("Content-Type", "application/json"));
|
||||
req.Header.AddHeader(new HttpsHeader("Accept", "application/json"));
|
||||
req.ContentString = JsonConvert.SerializeObject(CurrentPreset);
|
||||
|
||||
var client = new HttpsClient();
|
||||
client.HostVerification = false;
|
||||
client.PeerVerification = false;
|
||||
try
|
||||
{
|
||||
var resp = client.Dispatch(req);
|
||||
|
||||
// 201=created
|
||||
// 204=empty content
|
||||
if (resp.Code == 201)
|
||||
CrestronConsole.PrintLine("Preset added");
|
||||
else if (resp.Code == 204)
|
||||
CrestronConsole.PrintLine("Preset updated");
|
||||
else if (resp.Code == 209)
|
||||
CrestronConsole.PrintLine("Preset already exists. Cannot save as new.");
|
||||
else
|
||||
CrestronConsole.PrintLine("Preset save failed: {0}\r", resp.Code, resp.ContentString);
|
||||
}
|
||||
catch (HttpException e)
|
||||
{
|
||||
|
||||
CrestronConsole.PrintLine("Preset save exception {0}", e.Response.Code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Crestron.SimplSharpPro;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adapter that wraps actual Crestron Control System for production use
|
||||
/// </summary>
|
||||
public class CrestronControlSystemAdapter : ICrestronControlSystem
|
||||
{
|
||||
private readonly CrestronControlSystem _controlSystem;
|
||||
private readonly Dictionary<uint, IRelayPort> _relayPorts;
|
||||
|
||||
public CrestronControlSystemAdapter(CrestronControlSystem controlSystem)
|
||||
{
|
||||
_controlSystem = controlSystem ?? throw new ArgumentNullException(nameof(controlSystem));
|
||||
_relayPorts = new Dictionary<uint, IRelayPort>();
|
||||
|
||||
if (_controlSystem.SupportsRelay)
|
||||
{
|
||||
for (uint i = 1; i <= (uint)_controlSystem.NumberOfRelayPorts; i++)
|
||||
{
|
||||
_relayPorts[i] = new RelayPortAdapter(_controlSystem.RelayPorts[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsRelay => _controlSystem.SupportsRelay;
|
||||
public uint NumberOfRelayPorts => (uint)_controlSystem.NumberOfRelayPorts;
|
||||
public Dictionary<uint, IRelayPort> RelayPorts => _relayPorts;
|
||||
public string ProgramIdTag => "TestProgram"; // Simplified for now
|
||||
public string ControllerPrompt => _controlSystem.ControllerPrompt;
|
||||
public bool SupportsEthernet => _controlSystem.SupportsEthernet;
|
||||
public bool SupportsDigitalInput => _controlSystem.SupportsDigitalInput;
|
||||
public uint NumberOfDigitalInputPorts => (uint)_controlSystem.NumberOfDigitalInputPorts;
|
||||
public bool SupportsVersiPort => _controlSystem.SupportsVersiport;
|
||||
public uint NumberOfVersiPorts => (uint)_controlSystem.NumberOfVersiPorts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapter for Crestron relay port
|
||||
/// </summary>
|
||||
public class RelayPortAdapter : IRelayPort
|
||||
{
|
||||
private readonly Crestron.SimplSharpPro.Relay _relay;
|
||||
|
||||
public RelayPortAdapter(Crestron.SimplSharpPro.Relay relay)
|
||||
{
|
||||
_relay = relay ?? throw new ArgumentNullException(nameof(relay));
|
||||
}
|
||||
|
||||
public void Open() => _relay.Open();
|
||||
public void Close() => _relay.Close();
|
||||
public void Pulse(int delayMs)
|
||||
{
|
||||
// Crestron Relay.Pulse() doesn't take parameters
|
||||
// We'll just call the basic Pulse method
|
||||
_relay.Close();
|
||||
System.Threading.Thread.Sleep(delayMs);
|
||||
_relay.Open();
|
||||
}
|
||||
public bool State => _relay.State;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapter for Crestron digital input
|
||||
/// </summary>
|
||||
public class DigitalInputAdapter : IDigitalInput
|
||||
{
|
||||
private readonly Crestron.SimplSharpPro.DigitalInput _digitalInput;
|
||||
|
||||
public DigitalInputAdapter(Crestron.SimplSharpPro.DigitalInput digitalInput)
|
||||
{
|
||||
_digitalInput = digitalInput ?? throw new ArgumentNullException(nameof(digitalInput));
|
||||
_digitalInput.StateChange += OnStateChange;
|
||||
}
|
||||
|
||||
public bool State => _digitalInput.State;
|
||||
public event EventHandler<DigitalInputEventArgs> StateChange;
|
||||
|
||||
private void OnStateChange(DigitalInput digitalInput, Crestron.SimplSharpPro.DigitalInputEventArgs args)
|
||||
{
|
||||
StateChange?.Invoke(this, new Abstractions.DigitalInputEventArgs(args.State));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapter for Crestron VersiPort
|
||||
/// </summary>
|
||||
public class VersiPortAdapter : IVersiPort
|
||||
{
|
||||
private readonly Crestron.SimplSharpPro.Versiport _versiPort;
|
||||
|
||||
public VersiPortAdapter(Crestron.SimplSharpPro.Versiport versiPort)
|
||||
{
|
||||
_versiPort = versiPort ?? throw new ArgumentNullException(nameof(versiPort));
|
||||
_versiPort.VersiportChange += OnVersiportChange;
|
||||
}
|
||||
|
||||
public bool DigitalIn => _versiPort.DigitalIn;
|
||||
public void SetDigitalOut(bool value) => _versiPort.DigitalOut = value;
|
||||
public ushort AnalogIn => _versiPort.AnalogIn;
|
||||
public event EventHandler<VersiPortEventArgs> VersiportChange;
|
||||
|
||||
private void OnVersiportChange(Versiport port, VersiportEventArgs args)
|
||||
{
|
||||
var eventType = args.Event == eVersiportEvent.DigitalInChange
|
||||
? VersiPortEventType.DigitalInChange
|
||||
: VersiPortEventType.AnalogInChange;
|
||||
|
||||
VersiportChange?.Invoke(this, new Abstractions.VersiPortEventArgs
|
||||
{
|
||||
EventType = eventType,
|
||||
Value = args.Event == eVersiportEvent.DigitalInChange ? (object)port.DigitalIn : port.AnalogIn
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstraction for Crestron Control System to enable unit testing
|
||||
/// </summary>
|
||||
public interface ICrestronControlSystem
|
||||
{
|
||||
bool SupportsRelay { get; }
|
||||
uint NumberOfRelayPorts { get; }
|
||||
Dictionary<uint, IRelayPort> RelayPorts { get; }
|
||||
string ProgramIdTag { get; }
|
||||
string ControllerPrompt { get; }
|
||||
bool SupportsEthernet { get; }
|
||||
bool SupportsDigitalInput { get; }
|
||||
uint NumberOfDigitalInputPorts { get; }
|
||||
bool SupportsVersiPort { get; }
|
||||
uint NumberOfVersiPorts { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction for relay port
|
||||
/// </summary>
|
||||
public interface IRelayPort
|
||||
{
|
||||
void Open();
|
||||
void Close();
|
||||
void Pulse(int delayMs);
|
||||
bool State { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction for digital input
|
||||
/// </summary>
|
||||
public interface IDigitalInput
|
||||
{
|
||||
bool State { get; }
|
||||
event EventHandler<DigitalInputEventArgs> StateChange;
|
||||
}
|
||||
|
||||
public class DigitalInputEventArgs : EventArgs
|
||||
{
|
||||
public bool State { get; set; }
|
||||
public DigitalInputEventArgs(bool state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction for VersiPort
|
||||
/// </summary>
|
||||
public interface IVersiPort
|
||||
{
|
||||
bool DigitalIn { get; }
|
||||
void SetDigitalOut(bool value);
|
||||
ushort AnalogIn { get; }
|
||||
event EventHandler<VersiPortEventArgs> VersiportChange;
|
||||
}
|
||||
|
||||
public class VersiPortEventArgs : EventArgs
|
||||
{
|
||||
public VersiPortEventType EventType { get; set; }
|
||||
public object Value { get; set; }
|
||||
}
|
||||
|
||||
public enum VersiPortEventType
|
||||
{
|
||||
DigitalInChange,
|
||||
AnalogInChange
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Crestron.SimplSharp;
|
||||
using System.Reflection;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using Crestron.SimplSharpPro.EthernetCommunication;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using Serilog.Events;
|
||||
|
||||
//using PepperDash.Essentials.Devices.Common.Cameras;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Bridges;
|
||||
|
||||
|
||||
@@ -4,9 +4,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
|
||||
public sealed class GenericIrControllerJoinMap : JoinMapBaseAdvanced
|
||||
{
|
||||
/// <summary>
|
||||
/// PLAY
|
||||
/// </summary>
|
||||
[JoinName("PLAY")]
|
||||
public JoinDataComplete Play = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -20,10 +17,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// STOP
|
||||
/// </summary>
|
||||
|
||||
[JoinName("STOP")]
|
||||
public JoinDataComplete Stop = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -37,10 +31,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// PAUSE
|
||||
/// </summary>
|
||||
|
||||
[JoinName("PAUSE")]
|
||||
public JoinDataComplete Pause = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -55,9 +46,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// FSCAN
|
||||
/// </summary>
|
||||
[JoinName("FSCAN")]
|
||||
public JoinDataComplete ForwardScan = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -72,9 +60,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// RSCAN
|
||||
/// </summary>
|
||||
[JoinName("RSCAN")]
|
||||
public JoinDataComplete ReverseScan = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -88,10 +73,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// F_SKIP
|
||||
/// </summary>
|
||||
|
||||
[JoinName("F_SKIP")]
|
||||
public JoinDataComplete ForwardSkip = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -106,9 +88,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// R_SKIP
|
||||
/// </summary>
|
||||
[JoinName("R_SKIP")]
|
||||
public JoinDataComplete ReverseSkip = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -122,10 +101,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// RECORD
|
||||
/// </summary>
|
||||
|
||||
[JoinName("RECORD")]
|
||||
public JoinDataComplete Record = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -140,9 +116,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// POWER
|
||||
/// </summary>
|
||||
[JoinName("POWER")]
|
||||
public JoinDataComplete Power = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -157,9 +130,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 0
|
||||
/// </summary>
|
||||
[JoinName("0")]
|
||||
public JoinDataComplete Kp0 = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -173,10 +143,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 1
|
||||
/// </summary>
|
||||
|
||||
[JoinName("1")]
|
||||
public JoinDataComplete Kp1 = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -190,10 +157,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 2
|
||||
/// </summary>
|
||||
|
||||
[JoinName("2")]
|
||||
public JoinDataComplete Kp2 = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -207,10 +171,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 3
|
||||
/// </summary>
|
||||
|
||||
[JoinName("3")]
|
||||
public JoinDataComplete Kp3 = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -224,10 +185,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 4
|
||||
/// </summary>
|
||||
|
||||
[JoinName("4")]
|
||||
public JoinDataComplete Kp4 = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -241,10 +199,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 5
|
||||
/// </summary>
|
||||
|
||||
[JoinName("5")]
|
||||
public JoinDataComplete Kp5 = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -258,10 +213,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 6
|
||||
/// </summary>
|
||||
|
||||
[JoinName("6")]
|
||||
public JoinDataComplete Kp6 = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -275,10 +227,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 7
|
||||
/// </summary>
|
||||
|
||||
[JoinName("7")]
|
||||
public JoinDataComplete Kp7 = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -292,10 +241,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 8
|
||||
/// </summary>
|
||||
|
||||
[JoinName("8")]
|
||||
public JoinDataComplete Kp8 = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -309,10 +255,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 9
|
||||
/// </summary>
|
||||
|
||||
[JoinName("9")]
|
||||
public JoinDataComplete Kp9 = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -340,10 +283,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
// JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
// JoinType = eJoinType.Digital
|
||||
// });
|
||||
|
||||
/// <summary>
|
||||
/// ENTER
|
||||
/// </summary>
|
||||
|
||||
[JoinName("ENTER")]
|
||||
public JoinDataComplete Enter = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -357,10 +297,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// CH+
|
||||
/// </summary>
|
||||
|
||||
[JoinName("CH+")]
|
||||
public JoinDataComplete ChannelUp = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -374,10 +311,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// CH-
|
||||
/// </summary>
|
||||
|
||||
[JoinName("CH-")]
|
||||
public JoinDataComplete ChannelDown = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -391,10 +325,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// *
|
||||
/// </summary>
|
||||
|
||||
[JoinName("*")]
|
||||
public JoinDataComplete KpStar = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -408,10 +339,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// #
|
||||
/// </summary>
|
||||
|
||||
[JoinName("#")]
|
||||
public JoinDataComplete KpPound = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -440,9 +368,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
// JoinType = eJoinType.Digital
|
||||
// });
|
||||
|
||||
/// <summary>
|
||||
/// POWER_ON
|
||||
/// </summary>
|
||||
[JoinName("POWER_ON")]
|
||||
public JoinDataComplete PowerOn = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -456,10 +381,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// POWER_OFF
|
||||
/// </summary>
|
||||
|
||||
[JoinName("POWER_OFF")]
|
||||
public JoinDataComplete PowerOff = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -473,10 +395,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// PLAY_PAUSE
|
||||
/// </summary>
|
||||
|
||||
[JoinName("PLAY_PAUSE")]
|
||||
public JoinDataComplete PlayPause = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -490,10 +409,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// LAST
|
||||
/// </summary>
|
||||
|
||||
[JoinName("LAST")]
|
||||
public JoinDataComplete Last = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -508,9 +424,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// HOME
|
||||
/// </summary>
|
||||
[JoinName("HOME")]
|
||||
public JoinDataComplete Home = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -525,9 +438,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// BACK
|
||||
/// </summary>
|
||||
[JoinName("BACK")]
|
||||
public JoinDataComplete Back = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -542,9 +452,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// GUIDE
|
||||
/// </summary>
|
||||
|
||||
[JoinName("GUIDE")]
|
||||
public JoinDataComplete Guide = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -558,10 +466,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// INFO
|
||||
/// </summary>
|
||||
|
||||
[JoinName("INFO")]
|
||||
public JoinDataComplete Info = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -575,10 +480,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// MENU
|
||||
/// </summary>
|
||||
|
||||
[JoinName("MENU")]
|
||||
public JoinDataComplete Menu = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -592,10 +494,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// UP_ARROW
|
||||
/// </summary>
|
||||
|
||||
[JoinName("UP_ARROW")]
|
||||
public JoinDataComplete DpadUp = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -609,10 +508,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// DN_ARROW
|
||||
/// </summary>
|
||||
|
||||
[JoinName("DN_ARROW")]
|
||||
public JoinDataComplete DpadDown = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -626,10 +522,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// LEFT_ARROW
|
||||
/// </summary>
|
||||
|
||||
[JoinName("LEFT_ARROW")]
|
||||
public JoinDataComplete DpadLeft = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -643,10 +536,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// RIGHT_ARROW
|
||||
/// </summary>
|
||||
|
||||
[JoinName("RIGHT_ARROW")]
|
||||
public JoinDataComplete DpadRight = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -660,10 +550,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// SELECT
|
||||
/// </summary>
|
||||
|
||||
[JoinName("SELECT")]
|
||||
public JoinDataComplete DpadSelect = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -677,10 +564,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// OPTIONS
|
||||
/// </summary>
|
||||
|
||||
[JoinName("OPTIONS")]
|
||||
public JoinDataComplete Options = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -694,10 +578,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// RETURN
|
||||
/// </summary>
|
||||
|
||||
[JoinName("RETURN")]
|
||||
public JoinDataComplete Return = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -712,9 +593,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// DVR
|
||||
/// </summary>
|
||||
[JoinName("DVR")]
|
||||
public JoinDataComplete Dvr = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -729,9 +607,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// ON_DEMAND
|
||||
/// </summary>
|
||||
|
||||
[JoinName("ON_DEMAND")]
|
||||
public JoinDataComplete OnDemand = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -746,9 +622,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// PAGE_UP
|
||||
/// </summary>
|
||||
|
||||
[JoinName("PAGE_UP")]
|
||||
public JoinDataComplete PageUp = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -762,10 +636,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// PAGE_DOWN
|
||||
/// </summary>
|
||||
|
||||
[JoinName("PAGE_DOWN")]
|
||||
public JoinDataComplete PageDown = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -779,10 +650,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// F_SRCH
|
||||
/// </summary>
|
||||
|
||||
[JoinName("F_SRCH")]
|
||||
public JoinDataComplete ForwardSearch = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -796,10 +664,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// R_SRCH
|
||||
/// </summary>
|
||||
|
||||
[JoinName("R_SRCH")]
|
||||
public JoinDataComplete ReverseSearch = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -813,10 +678,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// TRACK+
|
||||
/// </summary>
|
||||
|
||||
[JoinName("TRACK+")]
|
||||
public JoinDataComplete TrackPlus = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -830,10 +692,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// TRACK-
|
||||
/// </summary>
|
||||
|
||||
[JoinName("TRACK-")]
|
||||
public JoinDataComplete TrackMinus = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -848,9 +707,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// A
|
||||
/// </summary>
|
||||
[JoinName("A")]
|
||||
public JoinDataComplete KpA = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -864,10 +720,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// B
|
||||
/// </summary>
|
||||
|
||||
[JoinName("B")]
|
||||
public JoinDataComplete KpB = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -881,10 +734,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// C
|
||||
/// </summary>
|
||||
|
||||
[JoinName("C")]
|
||||
public JoinDataComplete KpC = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -898,10 +748,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// D
|
||||
/// </summary>
|
||||
|
||||
[JoinName("D")]
|
||||
public JoinDataComplete KpD = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -915,10 +762,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// RED
|
||||
/// </summary>
|
||||
|
||||
[JoinName("RED")]
|
||||
public JoinDataComplete KpRed = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -932,10 +776,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// GREEN
|
||||
/// </summary>
|
||||
|
||||
[JoinName("GREEN")]
|
||||
public JoinDataComplete KpGreen = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -949,10 +790,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// YELLOW
|
||||
/// </summary>
|
||||
|
||||
[JoinName("YELLOW")]
|
||||
public JoinDataComplete KpYellow = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -966,10 +804,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.FromSIMPL,
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// BLUE
|
||||
/// </summary>
|
||||
|
||||
[JoinName("BLUE")]
|
||||
public JoinDataComplete KpBlue = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -984,10 +819,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="joinStart">Join this join map will start at</param>
|
||||
public GenericIrControllerJoinMap(uint joinStart)
|
||||
: base(joinStart, typeof(GenericIrControllerJoinMap))
|
||||
{
|
||||
|
||||
@@ -8,9 +8,6 @@ namespace PepperDash.Essentials.Core.Bridges;
|
||||
|
||||
#region Digital
|
||||
|
||||
/// <summary>
|
||||
/// Enable Automatic Routing on Xx1 Switchers
|
||||
/// </summary>
|
||||
[JoinName("EnableAutoRoute")]
|
||||
public JoinDataComplete EnableAutoRoute = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -25,9 +22,7 @@ namespace PepperDash.Essentials.Core.Bridges;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Device Input Sync
|
||||
/// </summary>
|
||||
|
||||
[JoinName("InputSync")]
|
||||
public JoinDataComplete InputSync = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -42,9 +37,7 @@ namespace PepperDash.Essentials.Core.Bridges;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Device Enable Input Hdcp
|
||||
/// </summary>
|
||||
|
||||
[JoinName("EnableInputHdcp")]
|
||||
public JoinDataComplete EnableInputHdcp = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -59,9 +52,7 @@ namespace PepperDash.Essentials.Core.Bridges;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Device Disnable Input Hdcp
|
||||
/// </summary>
|
||||
|
||||
[JoinName("DisableInputHdcp")]
|
||||
public JoinDataComplete DisableInputHdcp = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -76,9 +67,7 @@ namespace PepperDash.Essentials.Core.Bridges;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Device Onlne
|
||||
/// </summary>
|
||||
|
||||
[JoinName("IsOnline")]
|
||||
public JoinDataComplete IsOnline = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -98,9 +87,6 @@ namespace PepperDash.Essentials.Core.Bridges;
|
||||
|
||||
#region Analog
|
||||
|
||||
/// <summary>
|
||||
/// Device Input Route Set/Get
|
||||
/// </summary>
|
||||
[JoinName("OutputRoute")]
|
||||
public JoinDataComplete OutputRoute = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -120,9 +106,6 @@ namespace PepperDash.Essentials.Core.Bridges;
|
||||
|
||||
#region Serial
|
||||
|
||||
/// <summary>
|
||||
/// Device Name
|
||||
/// </summary>
|
||||
[JoinName("Name")]
|
||||
public JoinDataComplete Name = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -137,9 +120,7 @@ namespace PepperDash.Essentials.Core.Bridges;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Device Input Name
|
||||
/// </summary>
|
||||
|
||||
[JoinName("InputName")]
|
||||
public JoinDataComplete InputName = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -154,9 +135,7 @@ namespace PepperDash.Essentials.Core.Bridges;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Device Output Name
|
||||
/// </summary>
|
||||
|
||||
[JoinName("OutputName")]
|
||||
public JoinDataComplete OutputName = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -171,9 +150,7 @@ namespace PepperDash.Essentials.Core.Bridges;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Device Output Route Name
|
||||
/// </summary>
|
||||
|
||||
[JoinName("OutputRoutedName")]
|
||||
public JoinDataComplete OutputRoutedName = new JoinDataComplete(
|
||||
new JoinData
|
||||
|
||||
@@ -132,30 +132,18 @@ public class SystemMonitorJoinMap : JoinMapBaseAdvanced
|
||||
public JoinDataComplete DhcpStatus = new JoinDataComplete(new JoinData { JoinNumber = 86, JoinSpan = 1 },
|
||||
new JoinMetadata { Description = "Processor Ethernet Dhcp Status", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Serial });
|
||||
|
||||
/// <summary>
|
||||
/// Processor Reboot
|
||||
/// </summary>
|
||||
[JoinName("ProcessorRebot")]
|
||||
public JoinDataComplete ProcessorReboot = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata { Description = "Reboot processor", JoinCapabilities = eJoinCapabilities.FromSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
/// <summary>
|
||||
/// Is Appliance Fb
|
||||
/// </summary>
|
||||
[JoinName("IsAppliance")]
|
||||
public JoinDataComplete IsAppliance = new JoinDataComplete(new JoinData { JoinNumber = 1, JoinSpan = 1 },
|
||||
new JoinMetadata { Description = "Is appliance Fb", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
/// <summary>
|
||||
/// Is Server Fb
|
||||
/// </summary>
|
||||
[JoinName("IsServer")]
|
||||
public JoinDataComplete IsServer = new JoinDataComplete(new JoinData { JoinNumber = 2, JoinSpan = 1 },
|
||||
new JoinMetadata { Description = "Is server Fb", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
/// <summary>
|
||||
/// Program Reset
|
||||
/// </summary>
|
||||
[JoinName("ProgramReset")]
|
||||
public JoinDataComplete ProgramReset = new JoinDataComplete(new JoinData { JoinNumber = 15, JoinSpan = 1 },
|
||||
new JoinMetadata { Description = "Resets the program", JoinCapabilities = eJoinCapabilities.ToSIMPL, JoinType = eJoinType.Digital });
|
||||
|
||||
@@ -6,9 +6,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
{
|
||||
#region Digital
|
||||
|
||||
/// <summary>
|
||||
/// Device is Online
|
||||
/// </summary>
|
||||
[JoinName("IsOnline")]
|
||||
public JoinDataComplete IsOnline = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -219,9 +216,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Current Hook State
|
||||
/// </summary>
|
||||
[JoinName("HookState")]
|
||||
public JoinDataComplete HookState = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -236,9 +230,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Speed Dial
|
||||
/// </summary>
|
||||
[JoinName("SpeedDialStart")]
|
||||
public JoinDataComplete SpeedDialStart = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -253,9 +244,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Incoming Call
|
||||
/// </summary>
|
||||
[JoinName("IncomingCall")]
|
||||
public JoinDataComplete IncomingCall = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -270,9 +258,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Answer Incoming Call
|
||||
/// </summary>
|
||||
[JoinName("IncomingAnswer")]
|
||||
public JoinDataComplete IncomingAnswer = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -287,9 +272,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Reject Incoming Call
|
||||
/// </summary>
|
||||
[JoinName("IncomingReject")]
|
||||
public JoinDataComplete IncomingReject = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -304,9 +286,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Manual Dial
|
||||
/// </summary>
|
||||
[JoinName("ManualDial")]
|
||||
public JoinDataComplete ManualDial = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -335,9 +314,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Phone Hook State
|
||||
/// </summary>
|
||||
[JoinName("PhoneHookState")]
|
||||
public JoinDataComplete PhoneHookState = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -408,9 +384,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Search Busy
|
||||
/// </summary>
|
||||
[JoinName("DirectorySearchBusy")]
|
||||
public JoinDataComplete DirectorySearchBusy = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -425,9 +398,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Selected Entry Is Contact
|
||||
/// </summary>
|
||||
|
||||
[JoinName("DirectoryEntryIsContact")]
|
||||
public JoinDataComplete DirectoryEntryIsContact = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -442,9 +413,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Line Selected
|
||||
/// </summary>
|
||||
[JoinName("DirectoryLineSelected")]
|
||||
public JoinDataComplete DirectoryLineSelected = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -459,9 +427,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Is Root
|
||||
/// </summary>
|
||||
[JoinName("DirectoryIsRoot")]
|
||||
public JoinDataComplete DirectoryIsRoot = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -476,9 +441,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Has Changed
|
||||
/// </summary>
|
||||
[JoinName("DirectoryHasChanged")]
|
||||
public JoinDataComplete DirectoryHasChanged = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -493,9 +455,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Go to Root
|
||||
/// </summary>
|
||||
[JoinName("DirectoryRoot")]
|
||||
public JoinDataComplete DirectoryRoot = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -510,9 +469,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Go Back One Level
|
||||
/// </summary>
|
||||
[JoinName("DirectoryFolderBack")]
|
||||
public JoinDataComplete DirectoryFolderBack = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -527,9 +483,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Dial Selected Line
|
||||
/// </summary>
|
||||
[JoinName("DirectoryDialSelectedLine")]
|
||||
public JoinDataComplete DirectoryDialSelectedLine = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -586,9 +539,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Tilt Up
|
||||
/// </summary>
|
||||
|
||||
[JoinName("CameraTiltUp")]
|
||||
public JoinDataComplete CameraTiltUp = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -603,9 +554,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Tilt Down
|
||||
/// </summary>
|
||||
[JoinName("CameraTiltDown")]
|
||||
public JoinDataComplete CameraTiltDown = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -620,9 +568,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Pan Left
|
||||
/// </summary>
|
||||
[JoinName("CameraPanLeft")]
|
||||
public JoinDataComplete CameraPanLeft = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -637,9 +582,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Pan Right
|
||||
/// </summary>
|
||||
[JoinName("CameraPanRight")]
|
||||
public JoinDataComplete CameraPanRight = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -654,9 +596,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Zoom In
|
||||
/// </summary>
|
||||
[JoinName("CameraZoomIn")]
|
||||
public JoinDataComplete CameraZoomIn = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -671,9 +610,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Zoom Out
|
||||
/// </summary>
|
||||
[JoinName("CameraZoomOut")]
|
||||
public JoinDataComplete CameraZoomOut = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -730,9 +666,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Preset Save
|
||||
/// </summary>
|
||||
[JoinName("CameraPresetSave")]
|
||||
public JoinDataComplete CameraPresetSave = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -747,9 +680,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Preset Recall
|
||||
/// </summary>
|
||||
[JoinName("CameraModeAuto")]
|
||||
public JoinDataComplete CameraModeAuto = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -764,9 +694,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Mode Manual
|
||||
/// </summary>
|
||||
[JoinName("CameraModeManual")]
|
||||
public JoinDataComplete CameraModeManual = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -781,9 +708,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Mode Off
|
||||
/// </summary>
|
||||
[JoinName("CameraModeOff")]
|
||||
public JoinDataComplete CameraModeOff = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -798,9 +722,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Self View
|
||||
/// </summary>
|
||||
[JoinName("CameraSelfView")]
|
||||
public JoinDataComplete CameraSelfView = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -815,9 +736,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Layout
|
||||
/// </summary>
|
||||
[JoinName("CameraLayout")]
|
||||
public JoinDataComplete CameraLayout = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -832,9 +750,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Supports Auto Mode
|
||||
/// </summary>
|
||||
[JoinName("CameraSupportsAutoMode")]
|
||||
public JoinDataComplete CameraSupportsAutoMode = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -849,9 +764,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Supports Off Mode
|
||||
/// </summary>
|
||||
[JoinName("CameraSupportsOffMode")]
|
||||
public JoinDataComplete CameraSupportsOffMode = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -866,9 +778,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Update Meetings
|
||||
/// </summary>
|
||||
[JoinName("UpdateMeetings")]
|
||||
public JoinDataComplete UpdateMeetings = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -897,9 +806,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Mic Mute On
|
||||
/// </summary>
|
||||
[JoinName("MicMuteOn")]
|
||||
public JoinDataComplete MicMuteOn = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -914,9 +820,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Mic Mute Off
|
||||
/// </summary>
|
||||
[JoinName("MicMuteOff")]
|
||||
public JoinDataComplete MicMuteOff = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -931,9 +834,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Mic Mute Toggle
|
||||
/// </summary>
|
||||
[JoinName("MicMuteToggle")]
|
||||
public JoinDataComplete MicMuteToggle = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -948,9 +848,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Volume Up
|
||||
/// </summary>
|
||||
[JoinName("VolumeUp")]
|
||||
public JoinDataComplete VolumeUp = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -965,9 +862,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Volume Down
|
||||
/// </summary>
|
||||
[JoinName("VolumeDown")]
|
||||
public JoinDataComplete VolumeDown = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -982,9 +876,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Volume Mute On
|
||||
/// </summary>
|
||||
[JoinName("VolumeMuteOn")]
|
||||
public JoinDataComplete VolumeMuteOn = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -999,9 +890,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Volume Mute Off
|
||||
/// </summary>
|
||||
[JoinName("VolumeMuteOff")]
|
||||
public JoinDataComplete VolumeMuteOff = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1016,9 +904,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Volume Mute Toggle
|
||||
/// </summary>
|
||||
[JoinName("VolumeMuteToggle")]
|
||||
public JoinDataComplete VolumeMuteToggle = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1061,9 +946,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Source Share Start
|
||||
/// </summary>
|
||||
[JoinName("SourceShareStart")]
|
||||
public JoinDataComplete SourceShareStart = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1078,9 +960,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Source Share End
|
||||
/// </summary>
|
||||
[JoinName("SourceShareEnd")]
|
||||
public JoinDataComplete SourceShareEnd = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1095,9 +974,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Source Share Auto Start
|
||||
/// </summary>
|
||||
[JoinName("AutoShareWhileInCall")]
|
||||
public JoinDataComplete SourceShareAutoStart = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1112,9 +988,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Digital
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Recieving Content
|
||||
/// </summary>
|
||||
[JoinName("RecievingContent")]
|
||||
public JoinDataComplete RecievingContent = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1129,9 +1002,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinCapabilities = eJoinCapabilities.ToSIMPL
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Selfview Position
|
||||
/// </summary>
|
||||
[JoinName("SelfviewPosition")]
|
||||
public JoinDataComplete SelfviewPosition = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1309,9 +1179,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Analog
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Minutes Before Meeting Start
|
||||
/// </summary>
|
||||
[JoinName("MinutesBeforeMeetingStart")]
|
||||
public JoinDataComplete MinutesBeforeMeetingStart = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1326,9 +1193,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Analog
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Number Select
|
||||
/// </summary>
|
||||
[JoinName("CameraNumberSelect")]
|
||||
public JoinDataComplete CameraNumberSelect = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1357,9 +1221,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Analog
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Row Count
|
||||
/// </summary>
|
||||
[JoinName("DirectoryRowCount")]
|
||||
public JoinDataComplete DirectoryRowCount = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1374,9 +1235,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Analog
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Select Row
|
||||
/// </summary>
|
||||
[JoinName("DirectorySelectRow")]
|
||||
public JoinDataComplete DirectorySelectRow = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1435,9 +1293,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
});
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Camera Preset Select
|
||||
/// </summary>
|
||||
|
||||
[JoinName("CameraPresetSelect")]
|
||||
public JoinDataComplete CameraPresetSelect = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1466,9 +1322,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Analog
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Participant Count
|
||||
/// </summary>
|
||||
[JoinName("ParticipantCount")]
|
||||
public JoinDataComplete ParticipantCount = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1483,9 +1336,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Analog
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Meeting Count
|
||||
/// </summary>
|
||||
[JoinName("Meeting Count Fb")]
|
||||
public JoinDataComplete MeetingCount = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1500,9 +1350,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Analog
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Volume Level
|
||||
/// </summary>
|
||||
[JoinName("VolumeLevel")]
|
||||
public JoinDataComplete VolumeLevel = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1565,9 +1412,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
|
||||
#region Serials
|
||||
|
||||
/// <summary>
|
||||
/// Current Dial String
|
||||
/// </summary>
|
||||
[JoinName("CurrentDialString")]
|
||||
public JoinDataComplete CurrentDialString = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1610,9 +1454,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Call Direction
|
||||
/// </summary>
|
||||
[JoinName("CallDirection")]
|
||||
public JoinDataComplete CallDirection = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1627,9 +1468,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Incoming Call Name
|
||||
/// </summary>
|
||||
[JoinName("IncomingCallName")]
|
||||
public JoinDataComplete IncomingCallName = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1644,9 +1482,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Incoming Call Number
|
||||
/// </summary>
|
||||
[JoinName("IncomingCallNumber")]
|
||||
public JoinDataComplete IncomingCallNumber = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1661,9 +1496,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Search String
|
||||
/// </summary>
|
||||
|
||||
[JoinName("DirectorySearchString")]
|
||||
public JoinDataComplete DirectorySearchString = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1678,9 +1511,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Directory Entries
|
||||
/// </summary>
|
||||
[JoinName("DirectoryEntries")]
|
||||
public JoinDataComplete DirectoryEntries = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1695,9 +1525,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Schedule Data
|
||||
/// </summary>
|
||||
[JoinName("Schedule")]
|
||||
public JoinDataComplete Schedule = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1726,9 +1553,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Camera Preset Names
|
||||
/// </summary>
|
||||
[JoinName("CameraPresetNames")]
|
||||
public JoinDataComplete CameraPresetNames = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1786,9 +1610,7 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
});
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Current Participants XSig
|
||||
/// </summary>
|
||||
|
||||
[JoinName("CurrentParticipants")]
|
||||
public JoinDataComplete CurrentParticipants = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1873,9 +1695,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Current Source
|
||||
/// </summary>
|
||||
[JoinName("CurrentSource")]
|
||||
public JoinDataComplete CurrentSource = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1890,9 +1709,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Selfview Position Feedback
|
||||
/// </summary>
|
||||
[JoinName("SelfviewPositionFb")]
|
||||
public JoinDataComplete SelfviewPositionFb = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1977,9 +1793,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Selected Directory Entry Name
|
||||
/// </summary>
|
||||
[JoinName("DirectoryEntrySelectedName")]
|
||||
public JoinDataComplete DirectoryEntrySelectedName = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -1994,9 +1807,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Selected Directory Entry Number
|
||||
/// </summary>
|
||||
[JoinName("DirectoryEntrySelectedNumber")]
|
||||
public JoinDataComplete DirectoryEntrySelectedNumber = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -2011,9 +1821,6 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
JoinType = eJoinType.Serial
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Selected Directory Folder Name
|
||||
/// </summary>
|
||||
[JoinName("DirectorySelectedFolderName")]
|
||||
public JoinDataComplete DirectorySelectedFolderName = new JoinDataComplete(
|
||||
new JoinData
|
||||
@@ -2031,20 +1838,11 @@ namespace PepperDash.Essentials.Core.Bridges.JoinMaps;
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the VideoCodecControllerJoinMap class.
|
||||
/// </summary>
|
||||
/// <param name="joinStart">Join this join map will start at</param>
|
||||
public VideoCodecControllerJoinMap(uint joinStart)
|
||||
: base(joinStart, typeof(VideoCodecControllerJoinMap))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="joinStart">Join this join map will start at</param>
|
||||
/// <param name="type">Type of the child join map</param>
|
||||
public VideoCodecControllerJoinMap(uint joinStart, Type type)
|
||||
: base(joinStart, type)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.GeneralIO;
|
||||
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using Serilog.Events;
|
||||
|
||||
|
||||
@@ -15,19 +16,9 @@ namespace PepperDash.Essentials.Core;
|
||||
{
|
||||
public CommunicationStreamDebugging StreamDebugging { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when bytes are received
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when text is received
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the IsConnected
|
||||
/// </summary>
|
||||
public bool IsConnected { get { return true; } }
|
||||
|
||||
ComPort Port;
|
||||
@@ -38,7 +29,7 @@ namespace PepperDash.Essentials.Core;
|
||||
{
|
||||
StreamDebugging = new CommunicationStreamDebugging(key);
|
||||
|
||||
Spec = spec;
|
||||
Spec = spec;
|
||||
|
||||
AddPostActivationAction(() =>
|
||||
{
|
||||
@@ -48,18 +39,12 @@ namespace PepperDash.Essentials.Core;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="key">Device key</param>
|
||||
/// <param name="port">COM port instance</param>
|
||||
/// <param name="spec">COM port specification</param>
|
||||
public ComPortController(string key, ComPort port, ComPort.ComPortSpec spec)
|
||||
: base(key)
|
||||
{
|
||||
if (port == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function");
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "ERROR: Invalid com port, continuing but comms will not function");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -87,33 +72,16 @@ namespace PepperDash.Essentials.Core;
|
||||
}
|
||||
}
|
||||
|
||||
var specResult = Port.SetComPortSpec(Spec);
|
||||
if (specResult != 0)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, this, "WARNING: Cannot set comspec");
|
||||
return;
|
||||
}
|
||||
Port.SerialDataReceived += Port_SerialDataReceived;
|
||||
}
|
||||
|
||||
if (Port.Parent is CrestronControlSystem || Port.Parent is CenIoCom102)
|
||||
{
|
||||
var result = Port.Register();
|
||||
if (result != eDeviceRegistrationUnRegistrationResponse.Success)
|
||||
{
|
||||
this.LogError($"Cannot register {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
|
||||
return;
|
||||
}
|
||||
this.LogInformation($"Successfully registered {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {result})");
|
||||
}
|
||||
|
||||
var specResult = Port.SetComPortSpec(Spec);
|
||||
if (specResult != 0)
|
||||
{
|
||||
this.LogError($"Cannot set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
|
||||
return;
|
||||
}
|
||||
this.LogInformation($"Successfully set comspec for {Key} using {Port.Parent.GetType().Name}-comport-{Port.ID} (result == {specResult})");
|
||||
|
||||
Port.SerialDataReceived += Port_SerialDataReceived;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructor
|
||||
/// </summary>
|
||||
~ComPortController()
|
||||
~ComPortController()
|
||||
{
|
||||
Port.SerialDataReceived -= Port_SerialDataReceived;
|
||||
}
|
||||
@@ -148,10 +116,6 @@ namespace PepperDash.Essentials.Core;
|
||||
if(!eventSubscribed) Debug.LogMessage(LogEventLevel.Warning, this, "Received data but no handler is registered");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivate method
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override bool Deactivate()
|
||||
{
|
||||
return Port.UnRegister() == eDeviceRegistrationUnRegistrationResponse.Success;
|
||||
@@ -159,9 +123,6 @@ namespace PepperDash.Essentials.Core;
|
||||
|
||||
#region IBasicCommunication Members
|
||||
|
||||
/// <summary>
|
||||
/// SendText method
|
||||
/// </summary>
|
||||
public void SendText(string text)
|
||||
{
|
||||
if (Port == null)
|
||||
@@ -172,9 +133,6 @@ namespace PepperDash.Essentials.Core;
|
||||
Port.Send(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SendBytes method
|
||||
/// </summary>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
if (Port == null)
|
||||
@@ -186,16 +144,10 @@ namespace PepperDash.Essentials.Core;
|
||||
Port.Send(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect method
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect method
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
}
|
||||
@@ -221,4 +173,4 @@ namespace PepperDash.Essentials.Core;
|
||||
|
||||
OnDataReceived(b.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Crestron.SimplSharp.CrestronSockets;
|
||||
using Crestron.SimplSharpPro.DeviceSupport;
|
||||
using Newtonsoft.Json;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Core.Logging;
|
||||
using PepperDash.Essentials.Core.Bridges;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using PepperDash.Essentials.Core.Devices;
|
||||
|
||||
|
||||
namespace PepperDash.Essentials.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements IBasicCommunication and sends all communication through an EISC
|
||||
/// </summary>
|
||||
[Description("Generic communication wrapper class for any IBasicCommunication type")]
|
||||
public class CommBridge : EssentialsBridgeableDevice, IBasicCommunication
|
||||
{
|
||||
private EiscApiAdvanced eisc;
|
||||
|
||||
private IBasicCommunicationJoinMap joinMap;
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when text is received through the communication bridge.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveTextArgs> TextReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when bytes are received through the communication bridge.
|
||||
/// </summary>
|
||||
public event EventHandler<GenericCommMethodReceiveBytesArgs> BytesReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the communication bridge is currently connected.
|
||||
/// </summary>
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommBridge"/> class.
|
||||
/// </summary>
|
||||
/// <param name="key">The unique key for the communication bridge.</param>
|
||||
/// <param name="name">The display name for the communication bridge.</param>
|
||||
public CommBridge(string key, string name)
|
||||
: base(key, name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a byte array through the communication bridge.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The byte array to send.</param>
|
||||
public void SendBytes(byte[] bytes)
|
||||
{
|
||||
if (eisc == null)
|
||||
{
|
||||
this.LogWarning("EISC is null, cannot send bytes.");
|
||||
return;
|
||||
}
|
||||
eisc.Eisc.SetString(joinMap.SendText.JoinNumber, Encoding.ASCII.GetString(bytes, 0, bytes.Length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a text string through the communication bridge.
|
||||
/// </summary>
|
||||
/// <param name="text">The text string to send.</param>
|
||||
public void SendText(string text)
|
||||
{
|
||||
if (eisc == null)
|
||||
{
|
||||
this.LogWarning("EISC is null, cannot send text.");
|
||||
return;
|
||||
}
|
||||
eisc.Eisc.SetString(joinMap.SendText.JoinNumber, text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates a connection through the communication bridge.
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
if (eisc == null)
|
||||
{
|
||||
this.LogWarning("EISC is null, cannot connect.");
|
||||
return;
|
||||
}
|
||||
eisc.Eisc.SetBool(joinMap.Connect.JoinNumber, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Terminates the connection through the communication bridge.
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (eisc == null)
|
||||
{
|
||||
this.LogWarning("EISC is null, cannot disconnect.");
|
||||
return;
|
||||
}
|
||||
eisc.Eisc.SetBool(joinMap.Connect.JoinNumber, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void LinkToApi(BasicTriList trilist, uint joinStart, string joinMapKey, EiscApiAdvanced bridge)
|
||||
{
|
||||
joinMap = new IBasicCommunicationJoinMap(joinStart);
|
||||
|
||||
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey);
|
||||
|
||||
if (!string.IsNullOrEmpty(joinMapSerialized))
|
||||
joinMap = JsonConvert.DeserializeObject<IBasicCommunicationJoinMap>(joinMapSerialized);
|
||||
|
||||
if (bridge != null)
|
||||
{
|
||||
bridge.AddJoinMap(Key, joinMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LogWarning("Please update config to use 'eiscapiadvanced' to get all join map features for this device.");
|
||||
}
|
||||
|
||||
this.LogDebug("Linking to Trilist '{0}'", trilist.ID.ToString("X"));
|
||||
|
||||
eisc = bridge;
|
||||
|
||||
trilist.SetBoolSigAction(joinMap.Connected.JoinNumber, (b) => IsConnected = b);
|
||||
|
||||
trilist.SetStringSigAction(joinMap.TextReceived.JoinNumber, (s) =>
|
||||
{
|
||||
TextReceived?.Invoke(this, new GenericCommMethodReceiveTextArgs(s));
|
||||
BytesReceived?.Invoke(this, new GenericCommMethodReceiveBytesArgs(Encoding.ASCII.GetBytes(s)));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
|
||||
|
||||
using System;
|
||||
using Crestron.SimplSharp;
|
||||
using Crestron.SimplSharpPro;
|
||||
using Crestron.SimplSharpPro.DM;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PepperDash.Core;
|
||||
using PepperDash.Essentials.Core.Config;
|
||||
using Serilog.Events;
|
||||
@@ -15,11 +17,6 @@ namespace PepperDash.Essentials.Core;
|
||||
/// </summary>
|
||||
public class CommFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// GetControlPropertiesConfig method
|
||||
/// </summary>
|
||||
/// <param name="deviceConfig">The Device config object</param>
|
||||
/// <returns>EssentialsControlPropertiesConfig object</returns>
|
||||
public static EssentialsControlPropertiesConfig GetControlPropertiesConfig(DeviceConfig deviceConfig)
|
||||
{
|
||||
try
|
||||
@@ -41,9 +38,6 @@ namespace PepperDash.Essentials.Core;
|
||||
/// Returns a comm method of either com port, TCP, SSH, and puts this into the DeviceManager
|
||||
/// </summary>
|
||||
/// <param name="deviceConfig">The Device config object</param>
|
||||
/// <summary>
|
||||
/// CreateCommForDevice method
|
||||
/// </summary>
|
||||
public static IBasicCommunication CreateCommForDevice(DeviceConfig deviceConfig)
|
||||
{
|
||||
EssentialsControlPropertiesConfig controlConfig = GetControlPropertiesConfig(deviceConfig);
|
||||
@@ -92,10 +86,8 @@ namespace PepperDash.Essentials.Core;
|
||||
break;
|
||||
case eControlMethod.SecureTcpIp:
|
||||
{
|
||||
var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize)
|
||||
{
|
||||
AutoReconnect = c.AutoReconnect
|
||||
};
|
||||
var secureTcp = new GenericSecureTcpIpClient(deviceConfig.Key + "-secureTcp", c.Address, c.Port, c.BufferSize);
|
||||
secureTcp.AutoReconnect = c.AutoReconnect;
|
||||
if (secureTcp.AutoReconnect)
|
||||
secureTcp.AutoReconnectIntervalMs = c.AutoReconnectIntervalMs;
|
||||
comm = secureTcp;
|
||||
@@ -103,7 +95,7 @@ namespace PepperDash.Essentials.Core;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -112,14 +104,12 @@ namespace PepperDash.Essentials.Core;
|
||||
}
|
||||
|
||||
// put it in the device manager if it's the right flavor
|
||||
if (comm is Device comDev)
|
||||
var comDev = comm as Device;
|
||||
if (comDev != null)
|
||||
DeviceManager.AddDevice(comDev);
|
||||
return comm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetComPort method
|
||||
/// </summary>
|
||||
public static ComPort GetComPort(EssentialsControlPropertiesConfig config)
|
||||
{
|
||||
var comPar = config.ComParams;
|
||||
@@ -141,45 +131,45 @@ namespace PepperDash.Essentials.Core;
|
||||
{
|
||||
var dev = DeviceManager.GetDeviceForKey(config.ControlPortDevKey);
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: device '{0}' {1}", config.ControlPortDevKey, dev == null
|
||||
? "is not valid, failed to get cec port"
|
||||
: "found in device manager, attempting to get cec port");
|
||||
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: device '{0}' {1}", config.ControlPortDevKey, dev == null
|
||||
? "is not valid, failed to get cec port"
|
||||
: "found in device manager, attempting to get cec port");
|
||||
|
||||
if (dev == null)
|
||||
return null;
|
||||
if (dev == null)
|
||||
return null;
|
||||
|
||||
if (String.IsNullOrEmpty(config.ControlPortName))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: '{0}' - Configuration missing 'ControlPortName'", config.ControlPortDevKey);
|
||||
return null;
|
||||
}
|
||||
if (String.IsNullOrEmpty(config.ControlPortName))
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: '{0}' - Configuration missing 'ControlPortName'", config.ControlPortDevKey);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var inputsOutputs = dev as IRoutingInputsOutputs;
|
||||
if (inputsOutputs == null)
|
||||
{
|
||||
var inputsOutputs = dev as IRoutingInputsOutputs;
|
||||
if (inputsOutputs == null)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not support IRoutingInputsOutputs, failed to get CEC port called '{1}'",
|
||||
config.ControlPortDevKey, config.ControlPortName);
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var inputPort = inputsOutputs.InputPorts[config.ControlPortName];
|
||||
if (inputPort != null && inputPort.Port is ICec)
|
||||
if (inputPort != null && inputPort.Port is ICec)
|
||||
return inputPort.Port as ICec;
|
||||
|
||||
|
||||
|
||||
var outputPort = inputsOutputs.OutputPorts[config.ControlPortName];
|
||||
if (outputPort != null && outputPort.Port is ICec)
|
||||
return outputPort.Port as ICec;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "GetCecPort Exception Message: {0}", ex.Message);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "GetCecPort Exception StackTrace: {0}", ex.StackTrace);
|
||||
if (ex.InnerException != null)
|
||||
Debug.LogMessage(LogEventLevel.Information, "GetCecPort Exception InnerException: {0}", ex.InnerException);
|
||||
}
|
||||
var outputPort = inputsOutputs.OutputPorts[config.ControlPortName];
|
||||
if (outputPort != null && outputPort.Port is ICec)
|
||||
return outputPort.Port as ICec;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogMessage(LogEventLevel.Debug, "GetCecPort Exception Message: {0}", ex.Message);
|
||||
Debug.LogMessage(LogEventLevel.Verbose, "GetCecPort Exception StackTrace: {0}", ex.StackTrace);
|
||||
if (ex.InnerException != null)
|
||||
Debug.LogMessage(LogEventLevel.Information, "GetCecPort Exception InnerException: {0}", ex.InnerException);
|
||||
}
|
||||
|
||||
Debug.LogMessage(LogEventLevel.Information, "GetCecPort: Device '{0}' does not have a CEC port called '{1}'",
|
||||
config.ControlPortDevKey, config.ControlPortName);
|
||||
@@ -192,9 +182,6 @@ namespace PepperDash.Essentials.Core;
|
||||
/// return the ControlSystem object from the Global class.
|
||||
/// </summary>
|
||||
/// <returns>IComPorts device or null if the device is not found or does not implement IComPorts</returns>
|
||||
/// <summary>
|
||||
/// GetIComPortsDeviceFromManagedDevice method
|
||||
/// </summary>
|
||||
public static IComPorts GetIComPortsDeviceFromManagedDevice(string ComPortDevKey)
|
||||
{
|
||||
if ((ComPortDevKey.Equals("controlSystem", System.StringComparison.OrdinalIgnoreCase)
|
||||
|
||||
@@ -20,8 +20,5 @@ namespace PepperDash.Essentials.Core;
|
||||
/// </summary>
|
||||
public interface IComPortsDevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Device
|
||||
/// </summary>
|
||||
IComPorts Device { get; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user