mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-02-21 07:35:04 +00:00
Compare commits
3 Commits
copilot/fi
...
RegisterFo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68301503bb | ||
|
|
639c74489d | ||
|
|
ab97e8140c |
36
.github/workflows/unit-tests.yml
vendored
36
.github/workflows/unit-tests.yml
vendored
@@ -1,36 +0,0 @@
|
|||||||
name: Unit Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main, develop ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main, develop ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup .NET
|
|
||||||
uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
dotnet-version: '8.0.x'
|
|
||||||
|
|
||||||
- name: Restore dependencies
|
|
||||||
run: dotnet restore tests/PepperDash.Essentials.Core.Tests/PepperDash.Essentials.Core.Tests.csproj
|
|
||||||
|
|
||||||
- name: Build tests
|
|
||||||
run: dotnet build tests/PepperDash.Essentials.Core.Tests/PepperDash.Essentials.Core.Tests.csproj --no-restore
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: dotnet test tests/PepperDash.Essentials.Core.Tests/PepperDash.Essentials.Core.Tests.csproj --no-build --verbosity normal --collect:"XPlat Code Coverage"
|
|
||||||
|
|
||||||
- name: Upload coverage reports to Codecov
|
|
||||||
uses: codecov/codecov-action@v3
|
|
||||||
with:
|
|
||||||
files: ./tests/PepperDash.Essentials.Core.Tests/TestResults/*/coverage.cobertura.xml
|
|
||||||
flags: unittests
|
|
||||||
name: codecov-umbrella
|
|
||||||
fail_ci_if_error: false
|
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -60,15 +60,6 @@ bld/
|
|||||||
[Ll]og/
|
[Ll]og/
|
||||||
[Ll]ogs/
|
[Ll]ogs/
|
||||||
|
|
||||||
# Test results and coverage
|
|
||||||
TestResults/
|
|
||||||
coverage/
|
|
||||||
*.trx
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
coverage.cobertura.xml
|
|
||||||
coverage-report/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
.vs/
|
.vs/
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
@@ -403,4 +394,3 @@ essentials-framework/Essentials Interfaces/PepperDash_Essentials_Interfaces/Pepp
|
|||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
_site/
|
_site/
|
||||||
api/
|
api/
|
||||||
*.DS_Store
|
|
||||||
|
|||||||
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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -36,10 +36,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core", "src\PepperDash.Core\PepperDash.Core.csproj", "{E5336563-1194-501E-BC4A-79AD9283EF90}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PepperDash.Core", "src\PepperDash.Core\PepperDash.Core.csproj", "{E5336563-1194-501E-BC4A-79AD9283EF90}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C8EAB8E7-4F14-4E5C-8D23-1A3956D46DE8}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PepperDash.Essentials.Core.Tests", "tests\PepperDash.Essentials.Core.Tests\PepperDash.Essentials.Core.Tests.csproj", "{A1234567-8901-2345-6789-ABCDEF012345}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug 4.7.2|Any CPU = Debug 4.7.2|Any CPU
|
Debug 4.7.2|Any CPU = Debug 4.7.2|Any CPU
|
||||||
@@ -83,12 +79,6 @@ Global
|
|||||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E5336563-1194-501E-BC4A-79AD9283EF90}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E5336563-1194-501E-BC4A-79AD9283EF90}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{A1234567-8901-2345-6789-ABCDEF012345}.Debug 4.7.2|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A1234567-8901-2345-6789-ABCDEF012345}.Debug 4.7.2|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A1234567-8901-2345-6789-ABCDEF012345}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A1234567-8901-2345-6789-ABCDEF012345}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A1234567-8901-2345-6789-ABCDEF012345}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A1234567-8901-2345-6789-ABCDEF012345}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -100,7 +90,6 @@ Global
|
|||||||
{F6D362DE-2256-44B1-927A-8CE4705D839A} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
{F6D362DE-2256-44B1-927A-8CE4705D839A} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
||||||
{B438694F-8FF7-464A-9EC8-10427374471F} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
{B438694F-8FF7-464A-9EC8-10427374471F} = {B24989D7-32B5-48D5-9AE1-5F3B17D25206}
|
||||||
{E5336563-1194-501E-BC4A-79AD9283EF90} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{E5336563-1194-501E-BC4A-79AD9283EF90} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
{A1234567-8901-2345-6789-ABCDEF012345} = {C8EAB8E7-4F14-4E5C-8D23-1A3956D46DE8}
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {6907A4BF-7201-47CF-AAB1-3597F3B8E1C3}
|
SolutionGuid = {6907A4BF-7201-47CF-AAB1-3597F3B8E1C3}
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
# Quick Start Guide: Unit Testing
|
|
||||||
|
|
||||||
This guide helps you get started with writing unit tests for PepperDash Essentials components.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- .NET 8 SDK
|
|
||||||
- Visual Studio 2022 or VS Code with C# extension
|
|
||||||
|
|
||||||
## Running Existing Tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Navigate to the test project
|
|
||||||
cd tests/PepperDash.Essentials.Core.Tests
|
|
||||||
|
|
||||||
# Run all tests
|
|
||||||
dotnet test
|
|
||||||
|
|
||||||
# Run with coverage
|
|
||||||
dotnet test --collect:"XPlat Code Coverage"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Creating Your First Test
|
|
||||||
|
|
||||||
### 1. Identify the Component
|
|
||||||
|
|
||||||
Choose a component that has business logic you want to test. Look for classes that:
|
|
||||||
- Have clear inputs and outputs
|
|
||||||
- Contain conditional logic or algorithms
|
|
||||||
- Are used by multiple parts of the system
|
|
||||||
|
|
||||||
### 2. Create Test File
|
|
||||||
|
|
||||||
Create a new test file in `tests/PepperDash.Essentials.Core.Tests/Unit/`:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using PepperDash.Essentials.Core.Tests.Abstractions;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Tests.Unit
|
|
||||||
{
|
|
||||||
public class YourComponentTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void YourMethod_WithValidInput_ReturnsExpectedResult()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var component = new YourComponent();
|
|
||||||
var input = "test data";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = component.YourMethod(input);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("expected result", result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Abstract Dependencies
|
|
||||||
|
|
||||||
If your component uses Crestron SDK or other external dependencies:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Create interface in Abstractions/
|
|
||||||
public interface IYourDependency
|
|
||||||
{
|
|
||||||
string DoSomething(string input);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify your component to use the interface
|
|
||||||
public class YourComponent
|
|
||||||
{
|
|
||||||
private readonly IYourDependency _dependency;
|
|
||||||
|
|
||||||
public YourComponent(IYourDependency dependency)
|
|
||||||
{
|
|
||||||
_dependency = dependency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string YourMethod(string input)
|
|
||||||
{
|
|
||||||
return _dependency.DoSomething(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with mocks
|
|
||||||
[Fact]
|
|
||||||
public void YourMethod_CallsDependency_ReturnsResult()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var mockDependency = new Mock<IYourDependency>();
|
|
||||||
mockDependency.Setup(x => x.DoSomething("input")).Returns("output");
|
|
||||||
var component = new YourComponent(mockDependency.Object);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = component.YourMethod("input");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal("output", result);
|
|
||||||
mockDependency.Verify(x => x.DoSomething("input"), Times.Once);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Patterns
|
|
||||||
|
|
||||||
### Testing Exceptions
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
[Fact]
|
|
||||||
public void Constructor_WithNullDependency_ThrowsArgumentNullException()
|
|
||||||
{
|
|
||||||
// Act & Assert
|
|
||||||
Assert.Throws<ArgumentNullException>(() => new YourComponent(null));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing Async Methods
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
[Fact]
|
|
||||||
public async Task YourAsyncMethod_WithValidInput_ReturnsExpectedResult()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var component = new YourComponent();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = await component.YourAsyncMethod();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(result);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing Collections
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
[Fact]
|
|
||||||
public void GetItems_ReturnsExpectedCount()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var component = new YourComponent();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var items = component.GetItems();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(3, items.Count());
|
|
||||||
Assert.Contains(items, x => x.Name == "Expected Item");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Mistakes to Avoid
|
|
||||||
|
|
||||||
1. **Testing Implementation Details** - Test behavior, not internal implementation
|
|
||||||
2. **Overly Complex Tests** - Keep tests simple and focused on one thing
|
|
||||||
3. **Not Testing Edge Cases** - Include null values, empty collections, boundary conditions
|
|
||||||
4. **Ignoring Test Performance** - Tests should run quickly to enable fast feedback
|
|
||||||
|
|
||||||
## Debugging Tests
|
|
||||||
|
|
||||||
### In Visual Studio
|
|
||||||
- Set breakpoints in test methods
|
|
||||||
- Use Test Explorer to run individual tests
|
|
||||||
- Check test output window for detailed information
|
|
||||||
|
|
||||||
### From Command Line
|
|
||||||
```bash
|
|
||||||
# Run specific test
|
|
||||||
dotnet test --filter "YourComponentTests.YourMethod_WithValidInput_ReturnsExpectedResult"
|
|
||||||
|
|
||||||
# Run with detailed output
|
|
||||||
dotnet test --verbosity diagnostic
|
|
||||||
```
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. **Review Examples** - Look at `TestableActionSequenceTests.cs` for comprehensive examples
|
|
||||||
2. **Start Small** - Begin with simple components and build up complexity
|
|
||||||
3. **Read Documentation** - Check `docs/testing/README.md` for detailed patterns
|
|
||||||
4. **Get Feedback** - Have your tests reviewed by team members
|
|
||||||
|
|
||||||
## Need Help?
|
|
||||||
|
|
||||||
- Check existing test examples in the codebase
|
|
||||||
- Review the full testing documentation
|
|
||||||
- Ask team members for guidance on complex scenarios
|
|
||||||
- Consider pair programming for your first few tests
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
# Unit Testing Infrastructure
|
|
||||||
|
|
||||||
This document outlines the unit testing infrastructure and patterns for PepperDash Essentials.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The testing infrastructure is designed to enable comprehensive testing of business logic while abstracting away Crestron SDK dependencies. This allows for:
|
|
||||||
|
|
||||||
- Fast, reliable unit tests that don't require hardware
|
|
||||||
- Isolated testing of business logic
|
|
||||||
- Better code quality through testability
|
|
||||||
- Easier debugging and maintenance
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
tests/
|
|
||||||
├── PepperDash.Essentials.Core.Tests/
|
|
||||||
│ ├── Abstractions/ # Interface abstractions for SDK dependencies
|
|
||||||
│ ├── TestableComponents/ # Demonstration components showing abstraction patterns
|
|
||||||
│ ├── Unit/ # Unit test files
|
|
||||||
│ └── GlobalUsings.cs # Global using statements for tests
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test Project Configuration
|
|
||||||
|
|
||||||
The test projects use:
|
|
||||||
- **.NET 8** - Modern testing framework with better performance
|
|
||||||
- **xUnit** - Primary testing framework
|
|
||||||
- **Moq** - Mocking framework for dependencies
|
|
||||||
- **Coverlet** - Code coverage analysis
|
|
||||||
|
|
||||||
## Abstraction Patterns
|
|
||||||
|
|
||||||
### Interface Segregation
|
|
||||||
|
|
||||||
Create focused interfaces that abstract SDK functionality:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public interface IThreadService
|
|
||||||
{
|
|
||||||
void Sleep(int milliseconds);
|
|
||||||
object CreateAndStartThread(Func<object, object> threadFunction, object parameter);
|
|
||||||
void AbortThread(object thread);
|
|
||||||
bool IsThreadRunning(object thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ILogger
|
|
||||||
{
|
|
||||||
void LogDebug(object source, string message, params object[] args);
|
|
||||||
void LogVerbose(object source, string message, params object[] args);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dependency Injection
|
|
||||||
|
|
||||||
Components should accept dependencies via constructor injection:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class TestableActionSequence
|
|
||||||
{
|
|
||||||
private readonly IQueue<TestableSequencedAction> _actionQueue;
|
|
||||||
private readonly IThreadService _threadService;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
public TestableActionSequence(
|
|
||||||
IQueue<TestableSequencedAction> actionQueue,
|
|
||||||
IThreadService threadService,
|
|
||||||
ILogger logger,
|
|
||||||
List<TestableSequencedAction> actions)
|
|
||||||
{
|
|
||||||
_actionQueue = actionQueue ?? throw new ArgumentNullException(nameof(actionQueue));
|
|
||||||
_threadService = threadService ?? throw new ArgumentNullException(nameof(threadService));
|
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Writing Tests
|
|
||||||
|
|
||||||
### Test Organization
|
|
||||||
|
|
||||||
- **One test class per component**
|
|
||||||
- **Descriptive test method names** that describe the scenario and expected outcome
|
|
||||||
- **Arrange-Act-Assert pattern** for clear test structure
|
|
||||||
|
|
||||||
### Example Test Structure
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class TestableActionSequenceTests
|
|
||||||
{
|
|
||||||
private readonly Mock<IQueue<TestableSequencedAction>> _mockQueue;
|
|
||||||
private readonly Mock<IThreadService> _mockThreadService;
|
|
||||||
private readonly Mock<ILogger> _mockLogger;
|
|
||||||
private readonly TestableActionSequence _actionSequence;
|
|
||||||
|
|
||||||
public TestableActionSequenceTests()
|
|
||||||
{
|
|
||||||
// Arrange - Set up mocks and dependencies
|
|
||||||
_mockQueue = new Mock<IQueue<TestableSequencedAction>>();
|
|
||||||
_mockThreadService = new Mock<IThreadService>();
|
|
||||||
_mockLogger = new Mock<ILogger>();
|
|
||||||
|
|
||||||
_actionSequence = new TestableActionSequence(
|
|
||||||
_mockQueue.Object,
|
|
||||||
_mockThreadService.Object,
|
|
||||||
_mockLogger.Object,
|
|
||||||
new List<TestableSequencedAction>());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void StartSequence_WhenNoThreadRunning_StartsNewThread()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
_mockThreadService.Setup(x => x.IsThreadRunning(It.IsAny<object>())).Returns(false);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
_actionSequence.StartSequence();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
_mockLogger.Verify(x => x.LogDebug(_actionSequence, "Starting Action Sequence"), Times.Once);
|
|
||||||
_mockThreadService.Verify(x => x.CreateAndStartThread(It.IsAny<Func<object, object>>(), null), Times.Once);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Naming Convention
|
|
||||||
|
|
||||||
Use descriptive names that follow the pattern:
|
|
||||||
`MethodName_Scenario_ExpectedBehavior`
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
- `StartSequence_WhenNoThreadRunning_StartsNewThread`
|
|
||||||
- `Constructor_WithNullQueue_ThrowsArgumentNullException`
|
|
||||||
- `StopSequence_SetsAllowActionsToFalseAndAbortsThread`
|
|
||||||
|
|
||||||
## Running Tests
|
|
||||||
|
|
||||||
### From Command Line
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run all tests
|
|
||||||
dotnet test
|
|
||||||
|
|
||||||
# Run tests with coverage
|
|
||||||
dotnet test --collect:"XPlat Code Coverage"
|
|
||||||
|
|
||||||
# Run specific test class
|
|
||||||
dotnet test --filter "TestableActionSequenceTests"
|
|
||||||
|
|
||||||
# Run with verbose output
|
|
||||||
dotnet test --verbosity normal
|
|
||||||
```
|
|
||||||
|
|
||||||
### From Visual Studio
|
|
||||||
|
|
||||||
- Use Test Explorer to run and debug tests
|
|
||||||
- Right-click on test methods to run individually
|
|
||||||
- Use "Run Tests in Parallel" for faster execution
|
|
||||||
|
|
||||||
## Code Coverage
|
|
||||||
|
|
||||||
The test projects include Coverlet for code coverage analysis. After running tests with coverage:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Generate coverage report
|
|
||||||
dotnet tool install -g reportgenerator
|
|
||||||
reportgenerator -reports:"coverage.cobertura.xml" -targetdir:"coverage-report" -reporttypes:Html
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### 1. Test Independence
|
|
||||||
- Each test should be independent and able to run in isolation
|
|
||||||
- Use fresh mock objects for each test
|
|
||||||
- Don't rely on test execution order
|
|
||||||
|
|
||||||
### 2. Mocking Guidelines
|
|
||||||
- Mock external dependencies (SDK calls, file system, network)
|
|
||||||
- Don't mock the component under test
|
|
||||||
- Use strict mocks when behavior verification is important
|
|
||||||
|
|
||||||
### 3. Assertions
|
|
||||||
- Make assertions specific and meaningful
|
|
||||||
- Test both positive and negative scenarios
|
|
||||||
- Verify state changes and behavior calls
|
|
||||||
|
|
||||||
### 4. Test Data
|
|
||||||
- Use meaningful test data that represents real scenarios
|
|
||||||
- Consider edge cases and boundary conditions
|
|
||||||
- Use constants or factory methods for complex test data
|
|
||||||
|
|
||||||
## Migrating Existing Code
|
|
||||||
|
|
||||||
To make existing components testable:
|
|
||||||
|
|
||||||
1. **Identify SDK Dependencies** - Look for direct Crestron SDK usage
|
|
||||||
2. **Extract Interfaces** - Create abstractions for SDK functionality
|
|
||||||
3. **Inject Dependencies** - Modify constructors to accept abstractions
|
|
||||||
4. **Create Tests** - Write comprehensive unit tests for business logic
|
|
||||||
5. **Implement Wrappers** - Create concrete implementations for production use
|
|
||||||
|
|
||||||
## Example Implementation Wrapper
|
|
||||||
|
|
||||||
When implementing the abstractions for production use:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public class CrestronThreadService : IThreadService
|
|
||||||
{
|
|
||||||
public void Sleep(int milliseconds)
|
|
||||||
{
|
|
||||||
Thread.Sleep(milliseconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object CreateAndStartThread(Func<object, object> threadFunction, object parameter)
|
|
||||||
{
|
|
||||||
var thread = new Thread(threadFunction, parameter, Thread.eThreadStartOptions.Running);
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AbortThread(object thread)
|
|
||||||
{
|
|
||||||
if (thread is Thread crestronThread)
|
|
||||||
{
|
|
||||||
crestronThread.Abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsThreadRunning(object thread)
|
|
||||||
{
|
|
||||||
return thread is Thread crestronThread &&
|
|
||||||
crestronThread.ThreadState == Thread.eThreadStates.ThreadRunning;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
The testing infrastructure can be extended with:
|
|
||||||
|
|
||||||
- **Integration test support** for end-to-end scenarios
|
|
||||||
- **Performance testing** utilities
|
|
||||||
- **Test data builders** for complex object creation
|
|
||||||
- **Custom assertions** for domain-specific validations
|
|
||||||
- **Automated test generation** for common patterns
|
|
||||||
|
|
||||||
## Getting Help
|
|
||||||
|
|
||||||
For questions about testing patterns or infrastructure:
|
|
||||||
|
|
||||||
1. Review existing test examples in the `tests/` directory
|
|
||||||
2. Check this documentation for guidance
|
|
||||||
3. Consult the team for complex abstraction scenarios
|
|
||||||
4. Consider the impact on existing plugin interfaces before major changes
|
|
||||||
@@ -11,35 +11,35 @@ namespace PepperDash.Core
|
|||||||
public class Device : IKeyName
|
public class Device : IKeyName
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique Key
|
/// Unique Key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Key { get; protected set; }
|
public string Key { get; protected set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Name of the devie
|
/// Name of the devie
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; protected set; }
|
public string Name { get; protected set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Enabled { get; protected set; }
|
public bool Enabled { get; protected set; }
|
||||||
|
|
||||||
///// <summary>
|
///// <summary>
|
||||||
///// A place to store reference to the original config object, if any. These values should
|
///// 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.
|
///// NOT be used as properties on the device as they are all publicly-settable values.
|
||||||
///// </summary>
|
///// </summary>
|
||||||
//public DeviceConfig Config { get; private set; }
|
//public DeviceConfig Config { get; private set; }
|
||||||
///// <summary>
|
///// <summary>
|
||||||
///// Helper method to check if Config exists
|
///// Helper method to check if Config exists
|
||||||
///// </summary>
|
///// </summary>
|
||||||
//public bool HasConfig { get { return Config != null; } }
|
//public bool HasConfig { get { return Config != null; } }
|
||||||
|
|
||||||
List<Action> _PreActivationActions;
|
List<Action> _PreActivationActions;
|
||||||
List<Action> _PostActivationActions;
|
List<Action> _PostActivationActions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Device DefaultDevice { get { return _DefaultDevice; } }
|
public static Device DefaultDevice { get { return _DefaultDevice; } }
|
||||||
static Device _DefaultDevice = new Device("Default", "Default");
|
static Device _DefaultDevice = new Device("Default", "Default");
|
||||||
|
|
||||||
@@ -54,27 +54,27 @@ namespace PepperDash.Core
|
|||||||
Name = "";
|
Name = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor with key and name
|
/// Constructor with key and name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <param name="name"></param>
|
/// <param name="name"></param>
|
||||||
public Device(string key, string name) : this(key)
|
public Device(string key, string name) : this(key)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//public Device(DeviceConfig config)
|
//public Device(DeviceConfig config)
|
||||||
// : this(config.Key, config.Name)
|
// : this(config.Key, config.Name)
|
||||||
//{
|
//{
|
||||||
// Config = config;
|
// Config = config;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a pre activation action
|
/// Adds a pre activation action
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="act"></param>
|
/// <param name="act"></param>
|
||||||
public void AddPreActivationAction(Action act)
|
public void AddPreActivationAction(Action act)
|
||||||
{
|
{
|
||||||
if (_PreActivationActions == null)
|
if (_PreActivationActions == null)
|
||||||
@@ -82,10 +82,10 @@ namespace PepperDash.Core
|
|||||||
_PreActivationActions.Add(act);
|
_PreActivationActions.Add(act);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a post activation action
|
/// Adds a post activation action
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="act"></param>
|
/// <param name="act"></param>
|
||||||
public void AddPostActivationAction(Action act)
|
public void AddPostActivationAction(Action act)
|
||||||
{
|
{
|
||||||
if (_PostActivationActions == null)
|
if (_PostActivationActions == null)
|
||||||
@@ -93,24 +93,22 @@ namespace PepperDash.Core
|
|||||||
_PostActivationActions.Add(act);
|
_PostActivationActions.Add(act);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the preactivation actions
|
/// Executes the preactivation actions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PreActivate()
|
public void PreActivate()
|
||||||
{
|
{
|
||||||
if (_PreActivationActions != null)
|
if (_PreActivationActions != null)
|
||||||
_PreActivationActions.ForEach(a =>
|
_PreActivationActions.ForEach(a => {
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
a.Invoke();
|
a.Invoke();
|
||||||
}
|
} catch (Exception e)
|
||||||
catch (Exception e)
|
|
||||||
{
|
{
|
||||||
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
|
Debug.LogMessage(e, "Error in PreActivationAction: " + e.Message, this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets this device ready to be used in the system. Runs any added pre-activation items, and
|
/// Gets this device ready to be used in the system. Runs any added pre-activation items, and
|
||||||
@@ -119,32 +117,31 @@ namespace PepperDash.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Activate()
|
public bool Activate()
|
||||||
{
|
{
|
||||||
//if (_PreActivationActions != null)
|
//if (_PreActivationActions != null)
|
||||||
// _PreActivationActions.ForEach(a => a.Invoke());
|
// _PreActivationActions.ForEach(a => a.Invoke());
|
||||||
var result = CustomActivate();
|
var result = CustomActivate();
|
||||||
//if(result && _PostActivationActions != null)
|
//if(result && _PostActivationActions != null)
|
||||||
// _PostActivationActions.ForEach(a => a.Invoke());
|
// _PostActivationActions.ForEach(a => a.Invoke());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the postactivation actions
|
/// Executes the postactivation actions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PostActivate()
|
public void PostActivate()
|
||||||
{
|
{
|
||||||
if (_PostActivationActions != null)
|
if (_PostActivationActions != null)
|
||||||
_PostActivationActions.ForEach(a =>
|
_PostActivationActions.ForEach(a => {
|
||||||
{
|
try
|
||||||
try
|
{
|
||||||
{
|
a.Invoke();
|
||||||
a.Invoke();
|
}
|
||||||
}
|
catch (Exception e)
|
||||||
catch (Exception e)
|
{
|
||||||
{
|
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
|
||||||
Debug.LogMessage(e, "Error in PostActivationAction: " + e.Message, this);
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called in between Pre and PostActivationActions when Activate() is called.
|
/// Called in between Pre and PostActivationActions when Activate() is called.
|
||||||
@@ -161,14 +158,14 @@ namespace PepperDash.Core
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual bool Deactivate() { return true; }
|
public virtual bool Deactivate() { return true; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
|
/// Call this method to start communications with a device. Overriding classes do not need to call base.Initialize()
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void Initialize()
|
public virtual void Initialize()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper method to check object for bool value false and fire an Action method
|
/// Helper method to check object for bool value false and fire an Action method
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="o">Should be of type bool, others will be ignored</param>
|
/// <param name="o">Should be of type bool, others will be ignored</param>
|
||||||
@@ -178,15 +175,5 @@ namespace PepperDash.Core
|
|||||||
if (o is bool && !(bool)o) 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,7 @@
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
||||||
{
|
{
|
||||||
/// <summary>
|
public interface IDisplay: IHasFeedback, IRoutingSinkWithSwitching, IHasPowerControl, IWarmingCooling, IUsageTracking, IKeyName
|
||||||
/// Interface for display devices that can be controlled and monitored.
|
|
||||||
/// This interface combines functionality for feedback, routing, power control,
|
|
||||||
/// warming/cooling, usage tracking, and key name management.
|
|
||||||
/// It is designed to be implemented by devices that require these capabilities,
|
|
||||||
/// such as projectors, displays, and other visual output devices.
|
|
||||||
/// </summary>
|
|
||||||
public interface IDisplay : IHasFeedback, IRoutingSinkWithSwitching, IHasPowerControl, IWarmingCooling, IUsageTracking, IKeyName
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This defines a device that has screens with layouts
|
|
||||||
/// Simply decorative
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasScreensWithLayouts
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A dictionary of screens, keyed by screen ID, that contains information about each screen and its layouts.
|
|
||||||
/// </summary>
|
|
||||||
Dictionary<uint, ScreenInfo> Screens { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies a specific layout to a screen based on the provided screen ID and layout index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="screenId"></param>
|
|
||||||
/// <param name="layoutIndex"></param>
|
|
||||||
void ApplyLayout(uint screenId, uint layoutIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents information about a screen and its layouts.
|
|
||||||
/// </summary>
|
|
||||||
public class ScreenInfo
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether the screen is enabled or not.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("enabled")]
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the screen.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("name")]
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The index of the screen.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("screenIndex")]
|
|
||||||
public int ScreenIndex { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A dictionary of layout information for the screen, keyed by layout ID.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("layouts")]
|
|
||||||
public Dictionary<uint, LayoutInfo> Layouts { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents information about a layout on a screen.
|
|
||||||
/// </summary>
|
|
||||||
public class LayoutInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the layout.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("layoutName")]
|
|
||||||
public string LayoutName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The index of the layout.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("layoutIndex")]
|
|
||||||
public int LayoutIndex { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The type of the layout, which can be "single", "double", "triple", or "quad".
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("layoutType")]
|
|
||||||
public string LayoutType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A dictionary of window configurations for the layout, keyed by window ID.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("windows")]
|
|
||||||
public Dictionary<uint, WindowConfig> Windows { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the configuration of a window within a layout on a screen.
|
|
||||||
/// </summary>
|
|
||||||
public class WindowConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The display label for the window
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("label")]
|
|
||||||
public string Label { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The input for the window
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("input")]
|
|
||||||
public string Input { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for devices that provide audio meter feedback.
|
|
||||||
/// This interface is used to standardize access to meter feedback across different devices.
|
|
||||||
/// </summary>
|
|
||||||
public interface IMeterFeedback
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the meter feedback for the device.
|
|
||||||
/// This property provides an IntFeedback that represents the current audio level or meter value.
|
|
||||||
/// </summary>
|
|
||||||
IntFeedback MeterFeedback { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using Crestron.SimplSharpPro;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
@@ -35,11 +33,11 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|||||||
|
|
||||||
string SystemUuid { get; }
|
string SystemUuid { get; }
|
||||||
|
|
||||||
BoolFeedback ApiOnlineAndAuthorized { get; }
|
BoolFeedback ApiOnlineAndAuthorized { get;}
|
||||||
|
|
||||||
void SendMessageObject(IMobileControlMessage o);
|
void SendMessageObject(IMobileControlMessage o);
|
||||||
|
|
||||||
void AddAction<T>(T messenger, Action<string, string, JToken> action) where T : IMobileControlMessenger;
|
void AddAction<T>(T messenger, Action<string, string, JToken> action) where T:IMobileControlMessenger;
|
||||||
|
|
||||||
void RemoveAction(string key);
|
void RemoveAction(string key);
|
||||||
|
|
||||||
@@ -47,14 +45,14 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|||||||
|
|
||||||
bool CheckForDeviceMessenger(string key);
|
bool CheckForDeviceMessenger(string key);
|
||||||
|
|
||||||
IMobileControlRoomMessenger GetRoomMessenger(string key);
|
IMobileControlRoomMessenger GetRoomMessenger(string key);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Describes a mobile control messenger
|
/// Describes a mobile control messenger
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IMobileControlMessenger : IKeyed
|
public interface IMobileControlMessenger: IKeyed
|
||||||
{
|
{
|
||||||
IMobileControl AppServerController { get; }
|
IMobileControl AppServerController { get; }
|
||||||
string MessagePath { get; }
|
string MessagePath { get; }
|
||||||
@@ -106,47 +104,16 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|||||||
|
|
||||||
public interface IMobileControlAction
|
public interface IMobileControlAction
|
||||||
{
|
{
|
||||||
IMobileControlMessenger Messenger { get; }
|
IMobileControlMessenger Messenger { get; }
|
||||||
|
|
||||||
Action<string, string, JToken> Action { get; }
|
Action<string,string, JToken> Action { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Describes a MobileControl Touchpanel Controller
|
|
||||||
/// </summary>
|
|
||||||
public interface IMobileControlTouchpanelController : IKeyed
|
public interface IMobileControlTouchpanelController : IKeyed
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The default room key for the controller
|
|
||||||
/// </summary>
|
|
||||||
string DefaultRoomKey { get; }
|
string DefaultRoomKey { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the application URL for the controller
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="url">The application URL</param>
|
|
||||||
void SetAppUrl(string url);
|
void SetAppUrl(string url);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether the controller uses a direct server connection
|
|
||||||
/// </summary>
|
|
||||||
bool UseDirectServer { get; }
|
bool UseDirectServer { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether the controller is a Zoom Room controller
|
|
||||||
/// </summary>
|
|
||||||
bool ZoomRoomController { get; }
|
bool ZoomRoomController { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Describes a MobileControl Crestron Touchpanel Controller
|
|
||||||
/// This interface extends the IMobileControlTouchpanelController to include connected IP information
|
|
||||||
/// </summary>
|
|
||||||
public interface IMobileControlCrestronTouchpanelController : IMobileControlTouchpanelController
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a collection of connected IP information for the touchpanel controller
|
|
||||||
/// </summary>
|
|
||||||
ReadOnlyCollection<ConnectedIpInformation> ConnectedIps { get; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
using Crestron.SimplSharpPro.DM.Streaming;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a collection of network port information and provides notifications when the information changes.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This interface is designed to provide access to a list of network port details and to notify
|
|
||||||
/// subscribers when the port information is updated. Implementations of this interface should ensure that the <see
|
|
||||||
/// cref="PortInformationChanged"/> event is raised whenever the <see cref="NetworkPorts"/> collection
|
|
||||||
/// changes.</remarks>
|
|
||||||
public interface INvxNetworkPortInformation
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the port information changes.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This event is triggered whenever there is a change in the port information, such as
|
|
||||||
/// updates to port settings or status. Subscribers can handle this event to respond to such changes.</remarks>
|
|
||||||
event EventHandler PortInformationChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the collection of network port information associated with the current instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>The collection provides information about the network ports, such as their status,
|
|
||||||
/// configuration, or other relevant details. The returned list is read-only and cannot be modified
|
|
||||||
/// directly.</remarks>
|
|
||||||
List<NvxNetworkPortInformation> NetworkPorts { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents information about a network port, including its configuration and associated system details.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This class provides properties to describe various attributes of a network port, such as its
|
|
||||||
/// name, description, VLAN configuration, and management IP address. It is typically used to store and retrieve
|
|
||||||
/// metadata about network ports in a managed environment.</remarks>
|
|
||||||
public class NvxNetworkPortInformation
|
|
||||||
{
|
|
||||||
private readonly DmNvxBaseClass.DmNvx35xNetwork.DmNvxNetworkLldpPort port;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the index of the device port.
|
|
||||||
/// </summary>
|
|
||||||
public uint DevicePortIndex { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name of the port used for communication.
|
|
||||||
/// </summary>
|
|
||||||
public string PortName => port.PortNameFeedback.StringValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the description of the port.
|
|
||||||
/// </summary>
|
|
||||||
public string PortDescription => port.PortNameDescriptionFeedback.StringValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name of the VLAN (Virtual Local Area Network).
|
|
||||||
/// </summary>
|
|
||||||
public string VlanName => port.VlanNameFeedback.StringValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the IP management address associated with the port.
|
|
||||||
/// </summary>
|
|
||||||
public string IpManagementAddress => port.IpManagementAddressFeedback.StringValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the system as reported by the associated port.
|
|
||||||
/// </summary>
|
|
||||||
public string SystemName => port.SystemNameFeedback.StringValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the description of the system name.
|
|
||||||
/// </summary>
|
|
||||||
public string SystemNameDescription => port.SystemNameDescriptionFeedback.StringValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="NvxNetworkPortInformation"/> class with the specified network port
|
|
||||||
/// and device port index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="port">The network port associated with the device. Cannot be <see langword="null"/>.</param>
|
|
||||||
/// <param name="devicePortIndex">The index of the device port.</param>
|
|
||||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="port"/> is <see langword="null"/>.</exception>
|
|
||||||
public NvxNetworkPortInformation(DmNvxBaseClass.DmNvx35xNetwork.DmNvxNetworkLldpPort port, uint devicePortIndex)
|
|
||||||
{
|
|
||||||
this.port = port ?? throw new ArgumentNullException(nameof(port), "Port cannot be null");
|
|
||||||
DevicePortIndex = devicePortIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for devices that provide state feedback.
|
|
||||||
/// This interface is used to standardize access to state feedback across different devices.
|
|
||||||
/// </summary>
|
|
||||||
public interface IStateFeedback
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the state feedback for the device.
|
|
||||||
/// This property provides a BoolFeedback that represents the current state (on/off) of the device.
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback StateFeedback { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,34 +5,19 @@ using PepperDash.Essentials.Core;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents a destination item in a routing system that can receive audio/video signals.
|
|
||||||
/// Contains information about the destination device, its properties, and location settings.
|
|
||||||
/// </summary>
|
|
||||||
public class DestinationListItem
|
public class DestinationListItem
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the key identifier for the sink device that this destination represents.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("sinkKey")]
|
[JsonProperty("sinkKey")]
|
||||||
public string SinkKey { get; set; }
|
public string SinkKey { get; set; }
|
||||||
|
|
||||||
private EssentialsDevice _sinkDevice;
|
private EssentialsDevice _sinkDevice;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the actual device instance for this destination.
|
|
||||||
/// Lazily loads the device from the DeviceManager using the SinkKey.
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public EssentialsDevice SinkDevice
|
public EssentialsDevice SinkDevice
|
||||||
{
|
{
|
||||||
get { return _sinkDevice ?? (_sinkDevice = DeviceManager.GetDeviceForKey(SinkKey) as EssentialsDevice); }
|
get { return _sinkDevice ?? (_sinkDevice = DeviceManager.GetDeviceForKey(SinkKey) as EssentialsDevice); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the preferred display name for this destination.
|
|
||||||
/// Returns the custom Name if set, otherwise returns the SinkDevice name, or "---" if no device is found.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("preferredName")]
|
[JsonProperty("preferredName")]
|
||||||
public string PreferredName
|
public string PreferredName
|
||||||
{
|
{
|
||||||
@@ -47,78 +32,31 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the custom name for this destination.
|
|
||||||
/// If set, this name will be used as the PreferredName instead of the device name.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this destination should be included in destination lists.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("includeInDestinationList")]
|
[JsonProperty("includeInDestinationList")]
|
||||||
public bool IncludeInDestinationList { get; set; }
|
public bool IncludeInDestinationList { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the display order for this destination in lists.
|
|
||||||
/// Lower values appear first in sorted lists.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("order")]
|
[JsonProperty("order")]
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the surface location identifier for this destination.
|
|
||||||
/// Used to specify which surface or screen this destination is located on.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("surfaceLocation")]
|
[JsonProperty("surfaceLocation")]
|
||||||
public int SurfaceLocation { get; set; }
|
public int SurfaceLocation { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the vertical location position for this destination.
|
|
||||||
/// Used for spatial positioning in multi-display configurations.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("verticalLocation")]
|
[JsonProperty("verticalLocation")]
|
||||||
public int VerticalLocation { get; set; }
|
public int VerticalLocation { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the horizontal location position for this destination.
|
|
||||||
/// Used for spatial positioning in multi-display configurations.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("horizontalLocation")]
|
[JsonProperty("horizontalLocation")]
|
||||||
public int HorizontalLocation { get; set; }
|
public int HorizontalLocation { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the signal type that this destination can receive (Audio, Video, AudioVideo, etc.).
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("sinkType")]
|
[JsonProperty("sinkType")]
|
||||||
public eRoutingSignalType SinkType { get; set; }
|
public eRoutingSignalType SinkType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this destination is used for codec content sharing.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("isCodecContentDestination")]
|
[JsonProperty("isCodecContentDestination")]
|
||||||
public bool isCodecContentDestination { get; set; }
|
public bool isCodecContentDestination { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this destination is used for program audio output.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("isProgramAudioDestination")]
|
[JsonProperty("isProgramAudioDestination")]
|
||||||
public bool isProgramAudioDestination { get; set; }
|
public bool isProgramAudioDestination { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this destination supports USB connections.
|
|
||||||
/// Indicates if the destination can handle USB functionality, such as USB signal routing or device connections.
|
|
||||||
/// This property is used to determine compatibility with USB-based devices or systems.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("supportsUsb")]
|
|
||||||
public bool SupportsUsb { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the destination port associated with this destination item
|
|
||||||
/// This is used to identify the specific port on the destination device that this item refers to for advanced routing
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("destinationPortKey")]
|
|
||||||
public string DestinationPortKey { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,7 +248,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
|
|
||||||
foreach (var dev in Devices.Values.OfType<ICommunicationMonitor>())
|
foreach (var dev in Devices.Values.OfType<ICommunicationMonitor>())
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}\r\n");
|
CrestronConsole.ConsoleCommandResponse($"{dev}: {dev.CommunicationMonitor.Status}{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
public event EventHandler Initialized;
|
public event EventHandler Initialized;
|
||||||
|
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
public bool IsInitialized
|
public bool IsInitialized {
|
||||||
{
|
|
||||||
get { return _isInitialized; }
|
get { return _isInitialized; }
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
@@ -81,8 +80,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Override this method to build and create custom Mobile Control Messengers during the Activation phase
|
/// Override this method to build and create custom Mobile Control Messengers during the Activation phase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void CreateMobileControlMessengers()
|
protected virtual void CreateMobileControlMessengers() {
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,10 @@ using PepperDash.Essentials.Core.Devices;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents a level control item in a list, which can be used to control volume or mute functionality.
|
|
||||||
/// </summary>
|
|
||||||
public class LevelControlListItem : AudioControlListItemBase
|
public class LevelControlListItem : AudioControlListItemBase
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A reference to the IBasicVolumeWithFeedback device for control.
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public IBasicVolumeWithFeedback LevelControl
|
public IBasicVolumeWithFeedback LevelControl
|
||||||
{
|
{
|
||||||
@@ -60,7 +55,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(ItemKey)) return ParentDeviceKey;
|
if(string.IsNullOrEmpty(ItemKey)) return ParentDeviceKey;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return DeviceManager.AllDevices.
|
return DeviceManager.AllDevices.
|
||||||
@@ -75,39 +70,13 @@ namespace PepperDash.Essentials.Core
|
|||||||
[JsonProperty("type")]
|
[JsonProperty("type")]
|
||||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||||
public eLevelControlType Type { get; set; }
|
public eLevelControlType Type { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates if the item is a mic or not.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("isMic", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public bool? IsMic { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates if the item should show the raw level in the UI.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("showRawLevel", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public bool? ShowRawLevel { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates the type of level control item.
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum eLevelControlType
|
public enum eLevelControlType
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the item is a level control only
|
|
||||||
/// </summary>
|
|
||||||
Level = 1,
|
Level = 1,
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the item is a mute control only
|
|
||||||
/// </summary>
|
|
||||||
Mute = 2,
|
Mute = 2,
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the item is both a level and mute control
|
|
||||||
/// </summary>
|
|
||||||
LevelAndMute = Level | Mute,
|
LevelAndMute = Level | Mute,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,16 @@
|
|||||||
using System.Collections.Generic;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the type of source list item, which can be a route, off, or other.
|
///
|
||||||
/// This is used to categorize the source list items in a room.
|
|
||||||
/// The type is serialized to JSON and can be used to determine how the item should be displayed or handled in the UI.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum eSourceListItemType
|
public enum eSourceListItemType
|
||||||
{
|
{
|
||||||
/// <summary>
|
Route, Off, Other, SomethingAwesomerThanThese
|
||||||
/// Represents a typical route.
|
|
||||||
/// </summary>
|
|
||||||
Route,
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an off route.
|
|
||||||
/// </summary>
|
|
||||||
Off,
|
|
||||||
/// <summary>
|
|
||||||
/// Represents some other type of route
|
|
||||||
/// </summary>
|
|
||||||
Other,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,9 +18,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SourceListItem
|
public class SourceListItem
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The key of the source item, which is used to identify it in the DeviceManager
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("sourceKey")]
|
[JsonProperty("sourceKey")]
|
||||||
public string SourceKey { get; set; }
|
public string SourceKey { get; set; }
|
||||||
|
|
||||||
@@ -133,9 +117,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
[JsonProperty("disableRoutedSharing")]
|
[JsonProperty("disableRoutedSharing")]
|
||||||
public bool DisableRoutedSharing { get; set; }
|
public bool DisableRoutedSharing { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("destinations")]
|
[JsonProperty("destinations")]
|
||||||
public List<eSourceListItemDestinationTypes> Destinations { get; set; }
|
public List<eSourceListItemDestinationTypes> Destinations { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -168,76 +149,31 @@ namespace PepperDash.Essentials.Core
|
|||||||
[JsonProperty("disableSimpleRouting")]
|
[JsonProperty("disableSimpleRouting")]
|
||||||
public bool DisableSimpleRouting { get; set; }
|
public bool DisableSimpleRouting { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the device that provides video sync for this source item
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("syncProviderDeviceKey")]
|
|
||||||
public string SyncProviderDeviceKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates if the source supports USB connections
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("supportsUsb")]
|
|
||||||
public bool SupportsUsb { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the source port associated with this source item
|
|
||||||
/// This is used to identify the specific port on the source device that this item refers to for advanced routing
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("sourcePortKey")]
|
|
||||||
public string SourcePortKey { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default constructor for SourceListItem, initializes the Icon to "Blank"
|
|
||||||
/// </summary>
|
|
||||||
public SourceListItem()
|
public SourceListItem()
|
||||||
{
|
{
|
||||||
Icon = "Blank";
|
Icon = "Blank";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string representation of the SourceListItem, including the SourceKey and Name
|
|
||||||
/// </summary>
|
|
||||||
/// <returns> A string representation of the SourceListItem</returns>
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{SourceKey}:{Name}";
|
return $"{SourceKey}:{Name}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a route in a source list item, which defines the source and destination keys and the type of signal being routed
|
|
||||||
/// </summary>
|
|
||||||
public class SourceRouteListItem
|
public class SourceRouteListItem
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The key of the source device to route from
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("sourceKey")]
|
[JsonProperty("sourceKey")]
|
||||||
public string SourceKey { get; set; }
|
public string SourceKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the source port to route from
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("sourcePortKey")]
|
[JsonProperty("sourcePortKey")]
|
||||||
public string SourcePortKey { get; set; }
|
public string SourcePortKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the destination device to route to
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("destinationKey")]
|
[JsonProperty("destinationKey")]
|
||||||
public string DestinationKey { get; set; }
|
public string DestinationKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the destination port to route to
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("destinationPortKey")]
|
[JsonProperty("destinationPortKey")]
|
||||||
public string DestinationPortKey { get; set; }
|
public string DestinationPortKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The type of signal being routed, such as audio or video
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("type")]
|
[JsonProperty("type")]
|
||||||
public eRoutingSignalType Type { get; set; }
|
public eRoutingSignalType Type { get; set; }
|
||||||
}
|
}
|
||||||
@@ -247,85 +183,15 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum eSourceListItemDestinationTypes
|
public enum eSourceListItemDestinationTypes
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Default display, used for the main video output in a room
|
|
||||||
/// </summary>
|
|
||||||
defaultDisplay,
|
defaultDisplay,
|
||||||
/// <summary>
|
|
||||||
/// Left display
|
|
||||||
/// </summary>
|
|
||||||
leftDisplay,
|
leftDisplay,
|
||||||
/// <summary>
|
|
||||||
/// Right display
|
|
||||||
/// </summary>
|
|
||||||
rightDisplay,
|
rightDisplay,
|
||||||
/// <summary>
|
|
||||||
/// Center display
|
|
||||||
/// </summary>
|
|
||||||
centerDisplay,
|
centerDisplay,
|
||||||
/// <summary>
|
|
||||||
/// Program audio, used for the main audio output in a room
|
|
||||||
/// </summary>
|
|
||||||
programAudio,
|
programAudio,
|
||||||
/// <summary>
|
|
||||||
/// Codec content, used for sharing content to the far end in a video call
|
|
||||||
/// </summary>
|
|
||||||
codecContent,
|
codecContent,
|
||||||
/// <summary>
|
|
||||||
/// Front left display, used for rooms with multiple displays
|
|
||||||
/// </summary>
|
|
||||||
frontLeftDisplay,
|
frontLeftDisplay,
|
||||||
/// <summary>
|
|
||||||
/// Front right display, used for rooms with multiple displays
|
|
||||||
/// </summary>
|
|
||||||
frontRightDisplay,
|
frontRightDisplay,
|
||||||
/// <summary>
|
|
||||||
/// Rear left display, used for rooms with multiple displays
|
|
||||||
/// </summary>
|
|
||||||
rearLeftDisplay,
|
rearLeftDisplay,
|
||||||
/// <summary>
|
|
||||||
/// Rear right display, used for rooms with multiple displays
|
|
||||||
/// </summary>
|
|
||||||
rearRightDisplay,
|
rearRightDisplay,
|
||||||
/// <summary>
|
|
||||||
/// Auxiliary display 1, used for additional displays in a room
|
|
||||||
/// </summary>
|
|
||||||
auxDisplay1,
|
|
||||||
/// <summary>
|
|
||||||
/// Auxiliary display 2, used for additional displays in a room
|
|
||||||
/// </summary>
|
|
||||||
auxDisplay2,
|
|
||||||
/// <summary>
|
|
||||||
/// Auxiliary display 3, used for additional displays in a room
|
|
||||||
/// </summary>
|
|
||||||
auxDisplay3,
|
|
||||||
/// <summary>
|
|
||||||
/// Auxiliary display 4, used for additional displays in a room
|
|
||||||
/// </summary>
|
|
||||||
auxDisplay4,
|
|
||||||
/// <summary>
|
|
||||||
/// Auxiliary display 5, used for additional displays in a room
|
|
||||||
/// </summary>
|
|
||||||
auxDisplay5,
|
|
||||||
/// <summary>
|
|
||||||
/// Auxiliary display 6, used for additional displays in a room
|
|
||||||
/// </summary>
|
|
||||||
auxDisplay6,
|
|
||||||
/// <summary>
|
|
||||||
/// Auxiliary display 7, used for additional displays in a room
|
|
||||||
/// </summary>
|
|
||||||
auxDisplay7,
|
|
||||||
/// <summary>
|
|
||||||
/// Auxiliary display 8, used for additional displays in a room
|
|
||||||
/// </summary>
|
|
||||||
auxDisplay8,
|
|
||||||
/// <summary>
|
|
||||||
/// Auxiliary display 9, used for additional displays in a room
|
|
||||||
/// </summary>
|
|
||||||
auxDisplay9,
|
|
||||||
/// <summary>
|
|
||||||
/// Auxiliary display 10, used for additional displays in a room
|
|
||||||
/// </summary>
|
|
||||||
auxDisplay10,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Extensions for IPAddress to provide additional functionality such as getting broadcast address, network address, and checking if two addresses are in the same subnet.
|
|
||||||
/// </summary>
|
|
||||||
public static class IPAddressExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Get the broadcast address for a given IP address and subnet mask.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Address to check</param>
|
|
||||||
/// <param name="subnetMask">Subnet mask in a.b.c.d format</param>
|
|
||||||
/// <returns>Broadcast address</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// If the input IP address is 192.168.1.100 and the subnet mask is 255.255.255.0, the broadcast address will be 192.168.1.255
|
|
||||||
/// </remarks>
|
|
||||||
/// <exception cref="ArgumentException"></exception>
|
|
||||||
public static IPAddress GetBroadcastAddress(this IPAddress address, IPAddress subnetMask)
|
|
||||||
{
|
|
||||||
byte[] ipAdressBytes = address.GetAddressBytes();
|
|
||||||
byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
|
|
||||||
|
|
||||||
if (ipAdressBytes.Length != subnetMaskBytes.Length)
|
|
||||||
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
|
|
||||||
|
|
||||||
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
|
|
||||||
for (int i = 0; i < broadcastAddress.Length; i++)
|
|
||||||
{
|
|
||||||
broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
|
|
||||||
}
|
|
||||||
return new IPAddress(broadcastAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the network address for a given IP address and subnet mask.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Address to check</param>
|
|
||||||
/// <param name="subnetMask">Subnet mask in a.b.c.d</param>
|
|
||||||
/// <returns>Network Address</returns>
|
|
||||||
/// /// <remarks>
|
|
||||||
/// If the input IP address is 192.168.1.100 and the subnet mask is 255.255.255.0, the network address will be 192.168.1.0
|
|
||||||
/// </remarks>
|
|
||||||
/// <exception cref="ArgumentException"></exception>
|
|
||||||
public static IPAddress GetNetworkAddress(this IPAddress address, IPAddress subnetMask)
|
|
||||||
{
|
|
||||||
byte[] ipAdressBytes = address.GetAddressBytes();
|
|
||||||
byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
|
|
||||||
|
|
||||||
if (ipAdressBytes.Length != subnetMaskBytes.Length)
|
|
||||||
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
|
|
||||||
|
|
||||||
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
|
|
||||||
for (int i = 0; i < broadcastAddress.Length; i++)
|
|
||||||
{
|
|
||||||
broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
|
|
||||||
}
|
|
||||||
return new IPAddress(broadcastAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determine if two IP addresses are in the same subnet.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address2">Address to check</param>
|
|
||||||
/// <param name="address">Second address to check</param>
|
|
||||||
/// <param name="subnetMask">Subnet mask to use to compare the 2 IP Address</param>
|
|
||||||
/// <returns>True if addresses are in the same subnet</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// If the input IP addresses are 192.168.1.100 and 192.168.1.200, and the subnet mask is 255.255.255.0, this will return true.
|
|
||||||
/// If the input IP addresses are 10.1.1.100 and 192.168.1.100, and the subnet mask is 255.255.255.0, this will return false.
|
|
||||||
/// </remarks>
|
|
||||||
public static bool IsInSameSubnet(this IPAddress address2, IPAddress address, IPAddress subnetMask)
|
|
||||||
{
|
|
||||||
IPAddress network1 = address.GetNetworkAddress(subnetMask);
|
|
||||||
IPAddress network2 = address2.GetNetworkAddress(subnetMask);
|
|
||||||
|
|
||||||
return network1.Equals(network2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,13 +10,6 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents a device that manages room combinations by controlling partitions and scenarios.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>The <see cref="EssentialsRoomCombiner"/> allows for dynamic configuration of room
|
|
||||||
/// combinations based on partition states and predefined scenarios. It supports both automatic and manual modes
|
|
||||||
/// for managing room combinations. In automatic mode, the device determines the current room combination scenario
|
|
||||||
/// based on partition sensor states. In manual mode, scenarios can be set explicitly by the user.</remarks>
|
|
||||||
public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner
|
public class EssentialsRoomCombiner : EssentialsDevice, IEssentialsRoomCombiner
|
||||||
{
|
{
|
||||||
private EssentialsRoomCombinerPropertiesConfig _propertiesConfig;
|
private EssentialsRoomCombinerPropertiesConfig _propertiesConfig;
|
||||||
@@ -25,9 +18,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
|
|
||||||
private List<IEssentialsRoom> _rooms;
|
private List<IEssentialsRoom> _rooms;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of rooms represented as key-name pairs.
|
|
||||||
/// </summary>
|
|
||||||
public List<IKeyName> Rooms
|
public List<IKeyName> Rooms
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -38,12 +28,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
|
|
||||||
private bool _isInAutoMode;
|
private bool _isInAutoMode;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the system is operating in automatic mode.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Changing this property triggers an update event via
|
|
||||||
/// <c>IsInAutoModeFeedback.FireUpdate()</c>. Ensure that any event listeners are properly configured to handle
|
|
||||||
/// this update.</remarks>
|
|
||||||
public bool IsInAutoMode
|
public bool IsInAutoMode
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -62,36 +46,12 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether automatic mode is disabled.
|
|
||||||
/// </summary>
|
|
||||||
public bool DisableAutoMode
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _propertiesConfig.DisableAutoMode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CTimer _scenarioChangeDebounceTimer;
|
private CTimer _scenarioChangeDebounceTimer;
|
||||||
|
|
||||||
private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s
|
private int _scenarioChangeDebounceTimeSeconds = 10; // default to 10s
|
||||||
|
|
||||||
private Mutex _scenarioChange = new Mutex();
|
private Mutex _scenarioChange = new Mutex();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="EssentialsRoomCombiner"/> class, which manages room combination
|
|
||||||
/// scenarios and partition states.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>The <see cref="EssentialsRoomCombiner"/> class is designed to handle dynamic room
|
|
||||||
/// combination scenarios based on partition states. It supports both automatic and manual modes for managing
|
|
||||||
/// room combinations. By default, the instance starts in automatic mode unless the <paramref name="props"/>
|
|
||||||
/// specifies otherwise. After activation, the room combiner initializes partition state providers and sets up
|
|
||||||
/// the initial room configuration. Additionally, it subscribes to the <see
|
|
||||||
/// cref="DeviceManager.AllDevicesInitialized"/> event to ensure proper initialization of dependent devices
|
|
||||||
/// before determining or setting the room combination scenario.</remarks>
|
|
||||||
/// <param name="key">The unique identifier for the room combiner instance.</param>
|
|
||||||
/// <param name="props">The configuration properties for the room combiner, including default settings and debounce times.</param>
|
|
||||||
public EssentialsRoomCombiner(string key, EssentialsRoomCombinerPropertiesConfig props)
|
public EssentialsRoomCombiner(string key, EssentialsRoomCombinerPropertiesConfig props)
|
||||||
: base(key)
|
: base(key)
|
||||||
{
|
{
|
||||||
@@ -286,16 +246,8 @@ namespace PepperDash.Essentials.Core
|
|||||||
|
|
||||||
#region IEssentialsRoomCombiner Members
|
#region IEssentialsRoomCombiner Members
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when the room combination scenario changes.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This event is triggered whenever the configuration or state of the room combination
|
|
||||||
/// changes. Subscribers can use this event to update their logic or UI based on the new scenario.</remarks>
|
|
||||||
public event EventHandler<EventArgs> RoomCombinationScenarioChanged;
|
public event EventHandler<EventArgs> RoomCombinationScenarioChanged;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current room combination scenario.
|
|
||||||
/// </summary>
|
|
||||||
public IRoomCombinationScenario CurrentScenario
|
public IRoomCombinationScenario CurrentScenario
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -304,25 +256,10 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the feedback indicating whether the system is currently in auto mode.
|
|
||||||
/// </summary>
|
|
||||||
public BoolFeedback IsInAutoModeFeedback { get; private set; }
|
public BoolFeedback IsInAutoModeFeedback { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enables auto mode for the room combiner and its partitions, allowing automatic room combination scenarios to
|
|
||||||
/// be determined.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Auto mode allows the room combiner to automatically adjust its configuration based on
|
|
||||||
/// the state of its partitions. If auto mode is disabled in the configuration, this method logs a warning and
|
|
||||||
/// does not enable auto mode.</remarks>
|
|
||||||
public void SetAutoMode()
|
public void SetAutoMode()
|
||||||
{
|
{
|
||||||
if(_propertiesConfig.DisableAutoMode)
|
|
||||||
{
|
|
||||||
this.LogWarning("Auto mode is disabled for this room combiner. Cannot set to auto mode.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
IsInAutoMode = true;
|
IsInAutoMode = true;
|
||||||
|
|
||||||
foreach (var partition in Partitions)
|
foreach (var partition in Partitions)
|
||||||
@@ -333,12 +270,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
DetermineRoomCombinationScenario();
|
DetermineRoomCombinationScenario();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Switches the system to manual mode, disabling automatic operations.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This method sets the system to manual mode by updating the mode state and propagates
|
|
||||||
/// the change to all partitions. Once in manual mode, automatic operations are disabled for the system and its
|
|
||||||
/// partitions.</remarks>
|
|
||||||
public void SetManualMode()
|
public void SetManualMode()
|
||||||
{
|
{
|
||||||
IsInAutoMode = false;
|
IsInAutoMode = false;
|
||||||
@@ -349,11 +280,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toggles the current mode between automatic and manual.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>If the current mode is automatic, this method switches to manual mode. If the
|
|
||||||
/// current mode is manual, it switches to automatic mode.</remarks>
|
|
||||||
public void ToggleMode()
|
public void ToggleMode()
|
||||||
{
|
{
|
||||||
if (IsInAutoMode)
|
if (IsInAutoMode)
|
||||||
@@ -366,22 +292,10 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the collection of room combination scenarios.
|
|
||||||
/// </summary>
|
|
||||||
public List<IRoomCombinationScenario> RoomCombinationScenarios { get; private set; }
|
public List<IRoomCombinationScenario> RoomCombinationScenarios { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the collection of partition controllers managed by this instance.
|
|
||||||
/// </summary>
|
|
||||||
public List<IPartitionController> Partitions { get; private set; }
|
public List<IPartitionController> Partitions { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toggles the state of the partition identified by the specified partition key.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>If no partition with the specified key exists, the method performs no
|
|
||||||
/// action.</remarks>
|
|
||||||
/// <param name="partitionKey">The key of the partition whose state is to be toggled. This value cannot be null or empty.</param>
|
|
||||||
public void TogglePartitionState(string partitionKey)
|
public void TogglePartitionState(string partitionKey)
|
||||||
{
|
{
|
||||||
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionKey));
|
var partition = Partitions.FirstOrDefault((p) => p.Key.Equals(partitionKey));
|
||||||
@@ -392,17 +306,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the room combination scenario based on the specified scenario key.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This method manually adjusts the partition states according to the specified
|
|
||||||
/// scenario. If the application is in auto mode, the operation will not proceed, and a log message will be
|
|
||||||
/// generated indicating that the mode must be set to manual first. If the specified scenario key does not
|
|
||||||
/// match any existing scenario, a debug log message will be generated. For each partition state in the
|
|
||||||
/// scenario, the corresponding partition will be updated to either "Present" or "Not Present" based on the
|
|
||||||
/// scenario's configuration. If a partition key in the scenario cannot be found, a debug log message will be
|
|
||||||
/// generated.</remarks>
|
|
||||||
/// <param name="scenarioKey">The key identifying the room combination scenario to apply. This must match the key of an existing scenario.</param>
|
|
||||||
public void SetRoomCombinationScenario(string scenarioKey)
|
public void SetRoomCombinationScenario(string scenarioKey)
|
||||||
{
|
{
|
||||||
if (IsInAutoMode)
|
if (IsInAutoMode)
|
||||||
@@ -451,32 +354,13 @@ namespace PepperDash.Essentials.Core
|
|||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a factory for creating instances of <see cref="EssentialsRoomCombiner"/> devices.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This factory is responsible for constructing <see cref="EssentialsRoomCombiner"/> devices
|
|
||||||
/// based on the provided configuration. It supports the type name "essentialsroomcombiner" for device
|
|
||||||
/// creation.</remarks>
|
|
||||||
public class EssentialsRoomCombinerFactory : EssentialsDeviceFactory<EssentialsRoomCombiner>
|
public class EssentialsRoomCombinerFactory : EssentialsDeviceFactory<EssentialsRoomCombiner>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="EssentialsRoomCombinerFactory"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This factory is used to create instances of room combiners with the specified type
|
|
||||||
/// names. By default, the factory includes the type name "essentialsroomcombiner".</remarks>
|
|
||||||
public EssentialsRoomCombinerFactory()
|
public EssentialsRoomCombinerFactory()
|
||||||
{
|
{
|
||||||
TypeNames = new List<string> { "essentialsroomcombiner" };
|
TypeNames = new List<string> { "essentialsroomcombiner" };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates and initializes a new instance of the <see cref="EssentialsRoomCombiner"/> device.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This method uses the provided device configuration to extract the properties and
|
|
||||||
/// create an <see cref="EssentialsRoomCombiner"/> device. Ensure that the configuration contains valid
|
|
||||||
/// properties for the device to be created successfully.</remarks>
|
|
||||||
/// <param name="dc">The device configuration containing the key and properties required to build the device.</param>
|
|
||||||
/// <returns>A new instance of <see cref="EssentialsRoomCombiner"/> initialized with the specified configuration.</returns>
|
|
||||||
public override EssentialsDevice BuildDevice(PepperDash.Essentials.Core.Config.DeviceConfig dc)
|
public override EssentialsDevice BuildDevice(PepperDash.Essentials.Core.Config.DeviceConfig dc)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new EssentialsRoomCombiner Device");
|
Debug.LogMessage(LogEventLevel.Debug, "Factory Attempting to create new EssentialsRoomCombiner Device");
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Crestron.SimplSharp;
|
||||||
|
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
|
|
||||||
@@ -11,14 +17,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class EssentialsRoomCombinerPropertiesConfig
|
public class EssentialsRoomCombinerPropertiesConfig
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the system operates in automatic mode.
|
|
||||||
/// <remarks>Some systems don't have partitions sensors, and show shouldn't allow auto mode to be turned on. When this is true in the configuration,
|
|
||||||
/// auto mode won't be allowed to be turned on.</remarks>
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("disableAutoMode")]
|
|
||||||
public bool DisableAutoMode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of partitions that device the rooms
|
/// The list of partitions that device the rooms
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -49,9 +47,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
[JsonProperty("defaultScenarioKey")]
|
[JsonProperty("defaultScenarioKey")]
|
||||||
public string defaultScenarioKey { get; set; }
|
public string defaultScenarioKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the debounce time, in seconds, for scenario changes.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("scenarioChangeDebounceTimeSeconds")]
|
[JsonProperty("scenarioChangeDebounceTimeSeconds")]
|
||||||
public int ScenarioChangeDebounceTimeSeconds { get; set; }
|
public int ScenarioChangeDebounceTimeSeconds { get; set; }
|
||||||
}
|
}
|
||||||
@@ -61,15 +56,9 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PartitionConfig : IKeyName
|
public class PartitionConfig : IKeyName
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the unique key associated with the object.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("key")]
|
[JsonProperty("key")]
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name associated with the object.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
@@ -91,21 +80,12 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class RoomCombinationScenarioConfig : IKeyName
|
public class RoomCombinationScenarioConfig : IKeyName
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the key associated with the object.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("key")]
|
[JsonProperty("key")]
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name associated with the object.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the collection of partition states.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("partitionStates")]
|
[JsonProperty("partitionStates")]
|
||||||
public List<PartitionState> PartitionStates { get; set; }
|
public List<PartitionState> PartitionStates { get; set; }
|
||||||
|
|
||||||
@@ -115,15 +95,9 @@ namespace PepperDash.Essentials.Core
|
|||||||
[JsonProperty("uiMap")]
|
[JsonProperty("uiMap")]
|
||||||
public Dictionary<string, string> UiMap { get; set; }
|
public Dictionary<string, string> UiMap { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list of actions to be performed during device activation.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("activationActions")]
|
[JsonProperty("activationActions")]
|
||||||
public List<DeviceActionWrapper> ActivationActions { get; set; }
|
public List<DeviceActionWrapper> ActivationActions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list of actions to be performed when a device is deactivated.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("deactivationActions")]
|
[JsonProperty("deactivationActions")]
|
||||||
public List<DeviceActionWrapper> DeactivationActions { get; set; }
|
public List<DeviceActionWrapper> DeactivationActions { get; set; }
|
||||||
}
|
}
|
||||||
@@ -133,15 +107,9 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PartitionState
|
public class PartitionState
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the partition key used to group and organize data within a storage system.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("partitionKey")]
|
[JsonProperty("partitionKey")]
|
||||||
public string PartitionKey { get; set; }
|
public string PartitionKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether a partition is currently present.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("partitionSensedState")]
|
[JsonProperty("partitionSensedState")]
|
||||||
public bool PartitionPresent { get; set; }
|
public bool PartitionPresent { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,20 +28,9 @@ namespace PepperDash.Essentials.Core
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
BoolFeedback IsInAutoModeFeedback {get;}
|
BoolFeedback IsInAutoModeFeedback {get;}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the automatic mode is disabled.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("disableAutoMode")]
|
|
||||||
bool DisableAutoMode { get; }
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the system is operating in automatic mode.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("isInAutoMode")]
|
[JsonProperty("isInAutoMode")]
|
||||||
bool IsInAutoMode { get; }
|
bool IsInAutoMode { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the collection of rooms associated with the current object.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("rooms")]
|
[JsonProperty("rooms")]
|
||||||
List<IKeyName> Rooms { get; }
|
List<IKeyName> Rooms { get; }
|
||||||
|
|
||||||
@@ -85,13 +74,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
void SetRoomCombinationScenario(string scenarioKey);
|
void SetRoomCombinationScenario(string scenarioKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a scenario for combining rooms, including activation, deactivation, and associated state.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This interface defines the behavior for managing room combination scenarios, including
|
|
||||||
/// activation and deactivation, tracking the active state, and managing related partition states and UI mappings.
|
|
||||||
/// Implementations of this interface are expected to handle the logic for room combinations based on the provided
|
|
||||||
/// partition states and UI mappings.</remarks>
|
|
||||||
public interface IRoomCombinationScenario : IKeyName
|
public interface IRoomCombinationScenario : IKeyName
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -100,9 +82,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
BoolFeedback IsActiveFeedback { get; }
|
BoolFeedback IsActiveFeedback { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the entity is active.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("isActive")]
|
[JsonProperty("isActive")]
|
||||||
bool IsActive { get; }
|
bool IsActive { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -18,15 +18,8 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Stores pending route requests, keyed by the destination device key.
|
|
||||||
/// Used primarily to handle routing requests while a device is cooling down.
|
|
||||||
/// </summary>
|
|
||||||
private static readonly Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
|
private static readonly Dictionary<string, RouteRequest> RouteRequests = new Dictionary<string, RouteRequest>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A queue to process route requests and releases sequentially.
|
|
||||||
/// </summary>
|
|
||||||
private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue");
|
private static readonly GenericQueue routeRequestQueue = new GenericQueue("routingQueue");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -45,49 +38,16 @@ namespace PepperDash.Essentials.Core
|
|||||||
|
|
||||||
ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort);
|
ReleaseAndMakeRoute(destination, source, signalType, inputPort, outputPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Will release the existing route to the destination, if a route is found. This does not CLEAR the route, only stop counting usage time on any output ports that have a usage tracker set
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="destination">destination to clear</param>
|
|
||||||
public static void ReleaseRoute(this IRoutingInputs destination)
|
public static void ReleaseRoute(this IRoutingInputs destination)
|
||||||
{
|
{
|
||||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty, false));
|
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Will release the existing route to the destination, if a route is found. This does not CLEAR the route, only stop counting usage time on any output ports that have a usage tracker set
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="destination">destination to clear</param>
|
|
||||||
/// <param name="inputPortKey">Input to use to find existing route</param>
|
|
||||||
public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey)
|
public static void ReleaseRoute(this IRoutingInputs destination, string inputPortKey)
|
||||||
{
|
{
|
||||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey, false));
|
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the route on the destination. This will remove any routes that are currently in use
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="destination">Destination</param>
|
|
||||||
public static void ClearRoute(this IRoutingInputs destination)
|
|
||||||
{
|
|
||||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, string.Empty, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the route on the destination. This will remove any routes that are currently in use
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="destination">destination</param>
|
|
||||||
/// <param name="inputPortKey">input to use to find existing route</param>
|
|
||||||
public static void ClearRoute(this IRoutingInputs destination, string inputPortKey)
|
|
||||||
{
|
|
||||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination, inputPortKey, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes the route request for the destination. This will remove any routes that are currently in use
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="destinationKey">destination device key</param>
|
|
||||||
public static void RemoveRouteRequestForDestination(string destinationKey)
|
public static void RemoveRouteRequestForDestination(string destinationKey)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Removing route request for {destination}", null, destinationKey);
|
Debug.LogMessage(LogEventLevel.Information, "Removing route request for {destination}", null, destinationKey);
|
||||||
@@ -170,15 +130,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
return (audioRouteDescriptor, videoRouteDescriptor);
|
return (audioRouteDescriptor, videoRouteDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Internal method to handle the logic for releasing an existing route and making a new one.
|
|
||||||
/// Handles devices with cooling states by queueing the request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="destination">The destination device.</param>
|
|
||||||
/// <param name="source">The source device.</param>
|
|
||||||
/// <param name="signalType">The type of signal to route.</param>
|
|
||||||
/// <param name="destinationPort">The specific destination input port (optional).</param>
|
|
||||||
/// <param name="sourcePort">The specific source output port (optional).</param>
|
|
||||||
private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null)
|
private static void ReleaseAndMakeRoute(IRoutingInputs destination, IRoutingOutputs source, eRoutingSignalType signalType, RoutingInputPort destinationPort = null, RoutingOutputPort sourcePort = null)
|
||||||
{
|
{
|
||||||
if (destination == null) throw new ArgumentNullException(nameof(destination));
|
if (destination == null) throw new ArgumentNullException(nameof(destination));
|
||||||
@@ -233,16 +184,11 @@ namespace PepperDash.Essentials.Core
|
|||||||
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
|
Debug.LogMessage(LogEventLevel.Information, "Device: {destination} is NOT cooling down. Removing stored route request and routing to source key: {sourceKey}", null, destination.Key, routeRequest.Source.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty, false));
|
routeRequestQueue.Enqueue(new ReleaseRouteQueueItem(ReleaseRouteInternal, destination,destinationPort?.Key ?? string.Empty));
|
||||||
|
|
||||||
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
|
routeRequestQueue.Enqueue(new RouteRequestQueueItem(RunRouteRequest, routeRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Executes the actual routing based on a <see cref="RouteRequest"/>.
|
|
||||||
/// Finds the route path, adds it to the collection, and executes the switches.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The route request details.</param>
|
|
||||||
private static void RunRouteRequest(RouteRequest request)
|
private static void RunRouteRequest(RouteRequest request)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -273,12 +219,11 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Will release the existing route on the destination, if it is found in RouteDescriptorCollection.DefaultCollection
|
/// Will release the existing route on the destination, if it is found in
|
||||||
|
/// RouteDescriptorCollection.DefaultCollection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="destination"></param>
|
/// <param name="destination"></param>
|
||||||
/// <param name="inputPortKey"> The input port key to use to find the route. If empty, will use the first available input port</param>
|
private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey)
|
||||||
/// <param name="clearRoute"> If true, will clear the route on the destination. This will remove any routes that are currently in use</param>
|
|
||||||
private static void ReleaseRouteInternal(IRoutingInputs destination, string inputPortKey, bool clearRoute)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -297,7 +242,7 @@ namespace PepperDash.Essentials.Core
|
|||||||
if (current != null)
|
if (current != null)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
|
Debug.LogMessage(LogEventLevel.Information, "Releasing current route: {0}", destination, current.Source.Key);
|
||||||
current.ReleaseRoutes(clearRoute);
|
current.ReleaseRoutes();
|
||||||
}
|
}
|
||||||
} catch (Exception ex)
|
} catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using PepperDash.Essentials.Core;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Routing
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The current sources for the room, keyed by eRoutingSignalType.
|
|
||||||
/// This allows for multiple sources to be tracked, such as audio and video.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This interface is used to provide access to the current sources in a room,
|
|
||||||
/// allowing for more complex routing scenarios where multiple signal types are involved.
|
|
||||||
/// </remarks>
|
|
||||||
public interface ICurrentSources
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current sources for the room, keyed by eRoutingSignalType.
|
|
||||||
/// This dictionary contains the current source for each signal type, such as audio, video, and control signals.
|
|
||||||
/// </summary>
|
|
||||||
Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current source keys for the room, keyed by eRoutingSignalType.
|
|
||||||
/// This dictionary contains the keys for the current source for each signal type, such as audio, video, and control signals.
|
|
||||||
/// </summary>
|
|
||||||
Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,6 @@ using PepperDash.Essentials.Core.Routing;
|
|||||||
using PepperDash.Essentials.Core.Routing;
|
using PepperDash.Essentials.Core.Routing;
|
||||||
using PepperDash.Essentials.Core.Routing.Interfaces
|
using PepperDash.Essentials.Core.Routing.Interfaces
|
||||||
*/
|
*/
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -23,24 +21,10 @@ namespace PepperDash.Essentials.Core
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// For rooms with a single presentation source, change event
|
/// For rooms with a single presentation source, change event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Obsolete("Use ICurrentSources instead")]
|
|
||||||
public interface IHasCurrentSourceInfoChange
|
public interface IHasCurrentSourceInfoChange
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The key for the current source info, used to look up the source in the SourceList
|
|
||||||
/// </summary>
|
|
||||||
string CurrentSourceInfoKey { get; set; }
|
string CurrentSourceInfoKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current source info for the room, used to look up the source in the SourceList
|
|
||||||
/// </summary>
|
|
||||||
SourceListItem CurrentSourceInfo { get; set; }
|
SourceListItem CurrentSourceInfo { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that is raised when the current source info changes.
|
|
||||||
/// This is used to notify the system of changes to the current source info.
|
|
||||||
/// The event handler receives the new source info and the type of change that occurred.
|
|
||||||
/// </summary>
|
|
||||||
event SourceInfoChangeHandler CurrentSourceChange;
|
event SourceInfoChangeHandler CurrentSourceChange;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using PepperDash.Essentials.Core.Routing;
|
namespace PepperDash.Essentials.Core
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For fixed-source endpoint devices
|
/// For fixed-source endpoint devices
|
||||||
@@ -9,21 +7,17 @@ namespace PepperDash.Essentials.Core
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public interface IRoutingSinkWithInputPort :IRoutingSink
|
||||||
/// For fixed-source endpoint devices with an input port
|
|
||||||
/// </summary>
|
|
||||||
public interface IRoutingSinkWithInputPort : IRoutingSink
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets the current input port for this routing sink.
|
|
||||||
/// </summary>
|
|
||||||
RoutingInputPort CurrentInputPort { get; }
|
RoutingInputPort CurrentInputPort { get; }
|
||||||
}
|
}
|
||||||
|
/*/// <summary>
|
||||||
/// <summary>
|
/// For fixed-source endpoint devices
|
||||||
/// Interface for routing sinks that have access to the current source information.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRoutingSinkWithCurrentSources : IRoutingSink, ICurrentSources
|
public interface IRoutingSink<TSelector> : IRoutingInputs<TSelector>, IHasCurrentSourceInfoChange
|
||||||
{
|
{
|
||||||
}
|
void UpdateRouteRequest<TOutputSelector>(RouteRequest<TSelector, TOutputSelector> request);
|
||||||
|
|
||||||
|
RouteRequest<TSelector, TOutputSelector> GetRouteRequest<TOutputSelector>();
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marker interface to identify a device that acts as the origin of a signal path (<see cref="IRoutingOutputs"/>).
|
/// Defines an IRoutingOutputs devices as being a source - the start of the chain
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRoutingSource : IRoutingOutputs
|
public interface IRoutingSource : IRoutingOutputs
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Defines a routing device (<see cref="IRouting"/>) that supports explicitly clearing a route on an output.
|
|
||||||
/// </summary>
|
|
||||||
public interface IRoutingWithClear : IRouting
|
public interface IRoutingWithClear : IRouting
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -3,25 +3,14 @@ using System;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Delegate for handling route change events on devices implementing <see cref="IRoutingWithFeedback"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="midpoint">The routing device where the change occurred.</param>
|
|
||||||
/// <param name="newRoute">A descriptor of the new route that was established.</param>
|
|
||||||
public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute);
|
public delegate void RouteChangedEventHandler(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a routing device (<see cref="IRouting"/>) that provides feedback about its current routes.
|
/// Defines an IRouting with a feedback event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRoutingWithFeedback : IRouting
|
public interface IRoutingWithFeedback : IRouting
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets a list describing the currently active routes on this device.
|
|
||||||
/// </summary>
|
|
||||||
List<RouteSwitchDescriptor> CurrentRoutes { get; }
|
List<RouteSwitchDescriptor> CurrentRoutes { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event triggered when a route changes on this device.
|
|
||||||
/// </summary>
|
|
||||||
event RouteChangedEventHandler RouteChanged;
|
event RouteChangedEventHandler RouteChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,8 @@
|
|||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents a routing device (typically a transmitter or source) that provides numeric feedback for its current route.
|
|
||||||
/// Extends <see cref="IRoutingNumeric"/>.
|
|
||||||
/// </summary>
|
|
||||||
public interface ITxRouting : IRoutingNumeric
|
public interface ITxRouting : IRoutingNumeric
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Feedback indicating the currently routed video source by its numeric identifier.
|
|
||||||
/// </summary>
|
|
||||||
IntFeedback VideoSourceNumericFeedback { get; }
|
IntFeedback VideoSourceNumericFeedback { get; }
|
||||||
/// <summary>
|
|
||||||
/// Feedback indicating the currently routed audio source by its numeric identifier.
|
|
||||||
/// </summary>
|
|
||||||
IntFeedback AudioSourceNumericFeedback { get; }
|
IntFeedback AudioSourceNumericFeedback { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Crestron.SimplSharpPro;
|
using Crestron.SimplSharpPro;
|
||||||
|
|
||||||
@@ -10,63 +9,35 @@ using Serilog.Events;
|
|||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a collection of individual route steps between a Source and a Destination device for a specific signal type.
|
/// Represents an collection of individual route steps between Source and Destination
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RouteDescriptor
|
public class RouteDescriptor
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The destination device (sink or midpoint) for the route.
|
|
||||||
/// </summary>
|
|
||||||
public IRoutingInputs Destination { get; private set; }
|
public IRoutingInputs Destination { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The specific input port on the destination device used for this route. Can be null if not specified or applicable.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingInputPort InputPort { get; private set; }
|
public RoutingInputPort InputPort { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The source device for the route.
|
|
||||||
/// </summary>
|
|
||||||
public IRoutingOutputs Source { get; private set; }
|
public IRoutingOutputs Source { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The type of signal being routed (e.g., Audio, Video). This descriptor represents a single signal type.
|
|
||||||
/// </summary>
|
|
||||||
public eRoutingSignalType SignalType { get; private set; }
|
public eRoutingSignalType SignalType { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of individual switching steps required to establish the route.
|
|
||||||
/// </summary>
|
|
||||||
public List<RouteSwitchDescriptor> Routes { get; private set; }
|
public List<RouteSwitchDescriptor> Routes { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RouteDescriptor"/> class for a route without a specific destination input port.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The source device.</param>
|
|
||||||
/// <param name="destination">The destination device.</param>
|
|
||||||
/// <param name="signalType">The type of signal being routed.</param>
|
|
||||||
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) : this(source, destination, null, signalType)
|
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, eRoutingSignalType signalType) : this(source, destination, null, signalType)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RouteDescriptor"/> class for a route with a specific destination input port.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The source device.</param>
|
|
||||||
/// <param name="destination">The destination device.</param>
|
|
||||||
/// <param name="inputPort">The destination input port (optional).</param>
|
|
||||||
/// <param name="signalType">The signal type for this route.</param>
|
|
||||||
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType)
|
public RouteDescriptor(IRoutingOutputs source, IRoutingInputs destination, RoutingInputPort inputPort, eRoutingSignalType signalType)
|
||||||
{
|
{
|
||||||
Destination = destination;
|
Destination = destination;
|
||||||
InputPort = inputPort;
|
|
||||||
Source = source;
|
Source = source;
|
||||||
SignalType = signalType;
|
SignalType = signalType;
|
||||||
|
InputPort = inputPort;
|
||||||
Routes = new List<RouteSwitchDescriptor>();
|
Routes = new List<RouteSwitchDescriptor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes all the switching steps defined in the <see cref="Routes"/> list.
|
/// Executes all routes described in this collection. Typically called via
|
||||||
|
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ExecuteRoutes()
|
public void ExecuteRoutes()
|
||||||
{
|
{
|
||||||
@@ -92,27 +63,15 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases the usage tracking for the route and optionally clears the route on the switching devices.
|
/// Releases all routes in this collection. Typically called via
|
||||||
|
/// extension method IRoutingInputs.ReleaseAndMakeRoute()
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clearRoute">If true, attempts to clear the route on the switching devices (e.g., set input to null/0).</param>
|
public void ReleaseRoutes()
|
||||||
public void ReleaseRoutes(bool clearRoute = false)
|
|
||||||
{
|
{
|
||||||
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting))
|
foreach (var route in Routes.Where(r => r.SwitchingDevice is IRouting))
|
||||||
{
|
{
|
||||||
if (route.SwitchingDevice is IRouting switchingDevice)
|
if (route.SwitchingDevice is IRouting switchingDevice)
|
||||||
{
|
{
|
||||||
if(clearRoute)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
switchingDevice.ExecuteSwitch(null, route.OutputPort.Selector, SignalType);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Debug.LogError("Error executing switch: {exception}", e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.OutputPort == null)
|
if (route.OutputPort == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -131,10 +90,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string representation of the route descriptor, including source, destination, and individual route steps.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string describing the route.</returns>
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var routesText = Routes.Select(r => r.ToString()).ToArray();
|
var routesText = Routes.Select(r => r.ToString()).ToArray();
|
||||||
|
|||||||
@@ -4,42 +4,15 @@ using System;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents a request to establish a route between a source and a destination device.
|
|
||||||
/// </summary>
|
|
||||||
public class RouteRequest
|
public class RouteRequest
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The specific input port on the destination device to use for the route. Can be null if the port should be automatically determined or is not applicable.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingInputPort DestinationPort { get; set; }
|
public RoutingInputPort DestinationPort { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The specific output port on the source device to use for the route. Can be null if the port should be automatically determined or is not applicable.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingOutputPort SourcePort { get; set; }
|
public RoutingOutputPort SourcePort { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The destination device (sink or midpoint) for the route.
|
|
||||||
/// </summary>
|
|
||||||
public IRoutingInputs Destination { get; set; }
|
public IRoutingInputs Destination { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The source device for the route.
|
|
||||||
/// </summary>
|
|
||||||
public IRoutingOutputs Source { get; set; }
|
public IRoutingOutputs Source { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The type of signal being routed (e.g., Audio, Video, AudioVideo).
|
|
||||||
/// </summary>
|
|
||||||
public eRoutingSignalType SignalType { get; set; }
|
public eRoutingSignalType SignalType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the route request after a device's cooldown period has finished.
|
|
||||||
/// This method is typically subscribed to the IsCoolingDownFeedback.OutputChange event.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">The object that triggered the event (usually the cooling device).</param>
|
|
||||||
/// <param name="args">Event arguments indicating the cooldown state change.</param>
|
|
||||||
public void HandleCooldown(object sender, FeedbackEventArgs args)
|
public void HandleCooldown(object sender, FeedbackEventArgs args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -66,10 +39,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string representation of the route request.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string describing the source and destination of the route request.</returns>
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"Route {Source?.Key ?? "No Source Device"}:{SourcePort?.Key ?? "auto"} to {Destination?.Key ?? "No Destination Device"}:{DestinationPort?.Key ?? "auto"}";
|
return $"Route {Source?.Key ?? "No Source Device"}:{SourcePort?.Key ?? "auto"} to {Destination?.Key ?? "No Destination Device"}:{DestinationPort?.Key ?? "auto"}";
|
||||||
|
|||||||
@@ -5,34 +5,17 @@ using Serilog.Events;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Routing
|
namespace PepperDash.Essentials.Core.Routing
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents an item in the route request queue.
|
|
||||||
/// </summary>
|
|
||||||
public class RouteRequestQueueItem : IQueueMessage
|
public class RouteRequestQueueItem : IQueueMessage
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The action to perform for the route request.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Action<RouteRequest> action;
|
private readonly Action<RouteRequest> action;
|
||||||
/// <summary>
|
|
||||||
/// The route request data.
|
|
||||||
/// </summary>
|
|
||||||
private readonly RouteRequest routeRequest;
|
private readonly RouteRequest routeRequest;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RouteRequestQueueItem"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="routeAction">The action to perform.</param>
|
|
||||||
/// <param name="request">The route request data.</param>
|
|
||||||
public RouteRequestQueueItem(Action<RouteRequest> routeAction, RouteRequest request)
|
public RouteRequestQueueItem(Action<RouteRequest> routeAction, RouteRequest request)
|
||||||
{
|
{
|
||||||
action = routeAction;
|
action = routeAction;
|
||||||
routeRequest = request;
|
routeRequest = request;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispatches the route request action.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispatch()
|
public void Dispatch()
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Dispatching route request {routeRequest}", null, routeRequest);
|
Debug.LogMessage(LogEventLevel.Information, "Dispatching route request {routeRequest}", null, routeRequest);
|
||||||
@@ -40,50 +23,23 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an item in the queue for releasing a route.
|
|
||||||
/// </summary>
|
|
||||||
public class ReleaseRouteQueueItem : IQueueMessage
|
public class ReleaseRouteQueueItem : IQueueMessage
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly Action<IRoutingInputs, string> action;
|
||||||
/// The action to perform for releasing the route.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Action<IRoutingInputs, string, bool> action;
|
|
||||||
/// <summary>
|
|
||||||
/// The destination device whose route is being released.
|
|
||||||
/// </summary>
|
|
||||||
private readonly IRoutingInputs destination;
|
private readonly IRoutingInputs destination;
|
||||||
/// <summary>
|
|
||||||
/// The specific input port key on the destination to release, or null/empty for any/default.
|
|
||||||
/// </summary>
|
|
||||||
private readonly string inputPortKey;
|
private readonly string inputPortKey;
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether to clear the route (send null) or just release the usage tracking.
|
|
||||||
/// </summary>
|
|
||||||
private readonly bool clearRoute;
|
|
||||||
|
|
||||||
/// <summary>
|
public ReleaseRouteQueueItem(Action<IRoutingInputs, string> action, IRoutingInputs destination, string inputPortKey)
|
||||||
/// Initializes a new instance of the <see cref="ReleaseRouteQueueItem"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="action">The action to perform.</param>
|
|
||||||
/// <param name="destination">The destination device.</param>
|
|
||||||
/// <param name="inputPortKey">The input port key.</param>
|
|
||||||
/// <param name="clearRoute">True to clear the route, false to just release.</param>
|
|
||||||
public ReleaseRouteQueueItem(Action<IRoutingInputs, string, bool> action, IRoutingInputs destination, string inputPortKey, bool clearRoute)
|
|
||||||
{
|
{
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
this.inputPortKey = inputPortKey;
|
this.inputPortKey = inputPortKey;
|
||||||
this.clearRoute = clearRoute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispatches the release route action.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispatch()
|
public void Dispatch()
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Dispatching release route request for {destination}:{inputPortKey}", null, destination?.Key ?? "no destination", string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
Debug.LogMessage(LogEventLevel.Information, "Dispatching release route request for {destination}:{inputPortKey}", null, destination?.Key ?? "no destination", string.IsNullOrEmpty(inputPortKey) ? "auto" : inputPortKey);
|
||||||
action(destination, inputPortKey, clearRoute);
|
action(destination, inputPortKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,25 @@
|
|||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a single switching step within a larger route, detailing the switching device, input port, and optionally the output port.
|
/// Represents an individual link for a route
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RouteSwitchDescriptor
|
public class RouteSwitchDescriptor
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The device performing the switch (derived from the InputPort's parent).
|
|
||||||
/// </summary>
|
|
||||||
public IRoutingInputs SwitchingDevice { get { return InputPort?.ParentDevice; } }
|
public IRoutingInputs SwitchingDevice { get { return InputPort?.ParentDevice; } }
|
||||||
/// <summary>
|
|
||||||
/// The output port being switched from (relevant for matrix switchers). Null for sink devices.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingOutputPort OutputPort { get; set; }
|
public RoutingOutputPort OutputPort { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// The input port being switched to.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingInputPort InputPort { get; set; }
|
public RoutingInputPort InputPort { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for sink devices (no output port).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="inputPort">The input port being switched to.</param>
|
|
||||||
public RouteSwitchDescriptor(RoutingInputPort inputPort)
|
public RouteSwitchDescriptor(RoutingInputPort inputPort)
|
||||||
{
|
{
|
||||||
InputPort = inputPort;
|
InputPort = inputPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RouteSwitchDescriptor"/> class for matrix switchers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputPort">The output port being switched from.</param>
|
|
||||||
/// <param name="inputPort">The input port being switched to.</param>
|
|
||||||
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
|
public RouteSwitchDescriptor(RoutingOutputPort outputPort, RoutingInputPort inputPort)
|
||||||
{
|
{
|
||||||
InputPort = inputPort;
|
InputPort = inputPort;
|
||||||
OutputPort = outputPort;
|
OutputPort = outputPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string representation of the route switch descriptor.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string describing the switch operation.</returns>
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (SwitchingDevice is IRouting)
|
if (SwitchingDevice is IRouting)
|
||||||
|
|||||||
@@ -5,17 +5,8 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Routing
|
namespace PepperDash.Essentials.Core.Routing
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Manages routing feedback by subscribing to route changes on midpoint and sink devices,
|
|
||||||
/// tracing the route back to the original source, and updating the CurrentSourceInfo on sink devices.
|
|
||||||
/// </summary>
|
|
||||||
public class RoutingFeedbackManager:EssentialsDevice
|
public class RoutingFeedbackManager:EssentialsDevice
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RoutingFeedbackManager"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">The unique key for this manager device.</param>
|
|
||||||
/// <param name="name">The name of this manager device.</param>
|
|
||||||
public RoutingFeedbackManager(string key, string name): base(key, name)
|
public RoutingFeedbackManager(string key, string name): base(key, name)
|
||||||
{
|
{
|
||||||
AddPreActivationAction(SubscribeForMidpointFeedback);
|
AddPreActivationAction(SubscribeForMidpointFeedback);
|
||||||
@@ -23,9 +14,6 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Subscribes to the RouteChanged event on all devices implementing <see cref="IRoutingWithFeedback"/>.
|
|
||||||
/// </summary>
|
|
||||||
private void SubscribeForMidpointFeedback()
|
private void SubscribeForMidpointFeedback()
|
||||||
{
|
{
|
||||||
var midpointDevices = DeviceManager.AllDevices.OfType<IRoutingWithFeedback>();
|
var midpointDevices = DeviceManager.AllDevices.OfType<IRoutingWithFeedback>();
|
||||||
@@ -36,9 +24,6 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Subscribes to the InputChanged event on all devices implementing <see cref="IRoutingSinkWithSwitchingWithInputPort"/>.
|
|
||||||
/// </summary>
|
|
||||||
private void SubscribeForSinkFeedback()
|
private void SubscribeForSinkFeedback()
|
||||||
{
|
{
|
||||||
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
var sinkDevices = DeviceManager.AllDevices.OfType<IRoutingSinkWithSwitchingWithInputPort>();
|
||||||
@@ -49,12 +34,6 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the RouteChanged event from a midpoint device.
|
|
||||||
/// Triggers an update for all sink devices.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="midpoint">The midpoint device that reported a route change.</param>
|
|
||||||
/// <param name="newRoute">The descriptor of the new route.</param>
|
|
||||||
private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute)
|
private void HandleMidpointUpdate(IRoutingWithFeedback midpoint, RouteSwitchDescriptor newRoute)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -72,12 +51,6 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the InputChanged event from a sink device.
|
|
||||||
/// Triggers an update for the specific sink device.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">The sink device that reported an input change.</param>
|
|
||||||
/// <param name="currentInputPort">The new input port selected on the sink device.</param>
|
|
||||||
private void HandleSinkUpdate(IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort)
|
private void HandleSinkUpdate(IRoutingSinkWithSwitching sender, RoutingInputPort currentInputPort)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -90,12 +63,6 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the CurrentSourceInfo and CurrentSourceInfoKey properties on a destination (sink) device
|
|
||||||
/// based on its currently selected input port by tracing the route back through tie lines.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="destination">The destination sink device to update.</param>
|
|
||||||
/// <param name="inputPort">The currently selected input port on the destination device.</param>
|
|
||||||
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
|
private void UpdateDestination(IRoutingSinkWithSwitching destination, RoutingInputPort inputPort)
|
||||||
{
|
{
|
||||||
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key);
|
// Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, "Updating destination {destination} with inputPort {inputPort}", this,destination?.Key, inputPort?.Key);
|
||||||
@@ -232,12 +199,6 @@ namespace PepperDash.Essentials.Core.Routing
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Recursively traces a route back from a given tie line to find the root source tie line.
|
|
||||||
/// It navigates through midpoint devices (<see cref="IRoutingWithFeedback"/>) by checking their current routes.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tieLine">The starting tie line (typically connected to a sink or midpoint).</param>
|
|
||||||
/// <returns>The <see cref="TieLine"/> connected to the original source device, or null if the source cannot be determined.</returns>
|
|
||||||
private TieLine GetRootTieLine(TieLine tieLine)
|
private TieLine GetRootTieLine(TieLine tieLine)
|
||||||
{
|
{
|
||||||
TieLine nextTieLine = null;
|
TieLine nextTieLine = null;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using System;
|
|||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a basic routing input port on a device.
|
/// Basic RoutingInput with no statuses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RoutingInputPort : RoutingPort
|
public class RoutingInputPort : RoutingPort
|
||||||
{
|
{
|
||||||
@@ -41,10 +41,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
ParentDevice = parent;
|
ParentDevice = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string representation of the input port.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string in the format "ParentDeviceKey|PortKey|SignalType|ConnectionType".</returns>
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}";
|
return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}";
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a routing input port that provides video status feedback (e.g., sync, resolution).
|
/// A RoutingInputPort for devices like DM-TX and DM input cards.
|
||||||
/// Suitable for devices like DM transmitters or DM input cards.
|
/// Will provide video statistics on connected signals
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RoutingInputPortWithVideoStatuses : RoutingInputPort
|
public class RoutingInputPortWithVideoStatuses : RoutingInputPort
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides feedback outputs for video statuses associated with this port.
|
/// Video statuses attached to this port
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public VideoStatusOutputs VideoStatus { get; private set; }
|
public VideoStatusOutputs VideoStatus { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RoutingInputPortWithVideoStatuses"/> class.
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The unique key for this port.</param>
|
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
|
||||||
/// <param name="type">The signal type supported by this port.</param>
|
/// May be string, number, whatever</param>
|
||||||
/// <param name="connType">The physical connection type of this port.</param>
|
/// <param name="parent">The IRoutingInputs object this lives on</param>
|
||||||
/// <param name="selector">An object used to refer to this port in the parent device's ExecuteSwitch method.</param>
|
/// <param name="funcs">A VideoStatusFuncsWrapper used to assign the callback funcs that will get
|
||||||
/// <param name="parent">The <see cref="IRoutingInputs"/> device this port belongs to.</param>
|
/// the values for the various stats</param>
|
||||||
/// <param name="funcs">A <see cref="VideoStatusFuncsWrapper"/> containing delegates to retrieve video status values.</param>
|
|
||||||
public RoutingInputPortWithVideoStatuses(string key,
|
public RoutingInputPortWithVideoStatuses(string key,
|
||||||
eRoutingSignalType type, eRoutingPortConnectionType connType, object selector,
|
eRoutingSignalType type, eRoutingPortConnectionType connType, object selector,
|
||||||
IRoutingInputs parent, VideoStatusFuncsWrapper funcs) :
|
IRoutingInputs parent, VideoStatusFuncsWrapper funcs) :
|
||||||
|
|||||||
@@ -3,72 +3,32 @@
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Provides event arguments for routing changes, potentially including numeric or port object references.
|
|
||||||
/// </summary>
|
|
||||||
public class RoutingNumericEventArgs : EventArgs
|
public class RoutingNumericEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The numeric representation of the output, if applicable.
|
|
||||||
/// </summary>
|
|
||||||
public uint? Output { get; set; }
|
public uint? Output { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// The numeric representation of the input, if applicable.
|
|
||||||
/// </summary>
|
|
||||||
public uint? Input { get; set; }
|
public uint? Input { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The type of signal involved in the routing change.
|
|
||||||
/// </summary>
|
|
||||||
public eRoutingSignalType SigType { get; set; }
|
public eRoutingSignalType SigType { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// The input port involved in the routing change, if applicable.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingInputPort InputPort { get; set; }
|
public RoutingInputPort InputPort { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// The output port involved in the routing change, if applicable.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingOutputPort OutputPort { get; set; }
|
public RoutingOutputPort OutputPort { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class using numeric identifiers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="output">The numeric output identifier.</param>
|
|
||||||
/// <param name="input">The numeric input identifier.</param>
|
|
||||||
/// <param name="sigType">The signal type.</param>
|
|
||||||
public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType)
|
public RoutingNumericEventArgs(uint output, uint input, eRoutingSignalType sigType) : this(output, input, null, null, sigType)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class using port objects.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputPort">The output port object.</param>
|
|
||||||
/// <param name="inputPort">The input port object.</param>
|
|
||||||
/// <param name="sigType">The signal type.</param>
|
|
||||||
public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort,
|
public RoutingNumericEventArgs(RoutingOutputPort outputPort, RoutingInputPort inputPort,
|
||||||
eRoutingSignalType sigType)
|
eRoutingSignalType sigType)
|
||||||
: this(null, null, outputPort, inputPort, sigType)
|
: this(null, null, outputPort, inputPort, sigType)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class with default values.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingNumericEventArgs()
|
public RoutingNumericEventArgs()
|
||||||
: this(null, null, null, null, 0)
|
: this(null, null, null, null, 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RoutingNumericEventArgs"/> class with potentially mixed identifiers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="output">The numeric output identifier (optional).</param>
|
|
||||||
/// <param name="input">The numeric input identifier (optional).</param>
|
|
||||||
/// <param name="outputPort">The output port object (optional).</param>
|
|
||||||
/// <param name="inputPort">The input port object (optional).</param>
|
|
||||||
/// <param name="sigType">The signal type.</param>
|
|
||||||
public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort,
|
public RoutingNumericEventArgs(uint? output, uint? input, RoutingOutputPort outputPort,
|
||||||
RoutingInputPort inputPort, eRoutingSignalType sigType)
|
RoutingInputPort inputPort, eRoutingSignalType sigType)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,46 +4,29 @@ using System;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents a basic routing output port on a device.
|
|
||||||
/// </summary>
|
|
||||||
public class RoutingOutputPort : RoutingPort
|
public class RoutingOutputPort : RoutingPort
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The IRoutingOutputs object this port lives on.
|
/// The IRoutingOutputs object this port lives on
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
///
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public IRoutingOutputs ParentDevice { get; private set; }
|
public IRoutingOutputs ParentDevice { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tracks which destinations are currently using this output port.
|
|
||||||
/// </summary>
|
|
||||||
public InUseTracking InUseTracker { get; private set; }
|
public InUseTracking InUseTracker { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="RoutingOutputPort"/> class.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The unique key for this port.</param>
|
/// <param name="selector">An object used to refer to this port in the IRouting device's ExecuteSwitch method.
|
||||||
/// <param name="type">The signal type supported by this port.</param>
|
/// May be string, number, whatever</param>
|
||||||
/// <param name="connType">The physical connection type of this port.</param>
|
/// <param name="parent">The IRoutingOutputs object this port lives on</param>
|
||||||
/// <param name="selector">An object used to refer to this port in the parent device's ExecuteSwitch method.</param>
|
|
||||||
/// <param name="parent">The <see cref="IRoutingOutputs"/> device this port belongs to.</param>
|
|
||||||
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||||
object selector, IRoutingOutputs parent)
|
object selector, IRoutingOutputs parent)
|
||||||
: this(key, type, connType, selector, parent, false)
|
: this(key, type, connType, selector, parent, false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RoutingOutputPort"/> class, potentially marking it as internal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">The unique key for this port.</param>
|
|
||||||
/// <param name="type">The signal type supported by this port.</param>
|
|
||||||
/// <param name="connType">The physical connection type of this port.</param>
|
|
||||||
/// <param name="selector">An object used to refer to this port in the parent device's ExecuteSwitch method.</param>
|
|
||||||
/// <param name="parent">The <see cref="IRoutingOutputs"/> device this port belongs to.</param>
|
|
||||||
/// <param name="isInternal">True if this port represents an internal connection within a device (e.g., card to backplane).</param>
|
|
||||||
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
public RoutingOutputPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType,
|
||||||
object selector, IRoutingOutputs parent, bool isInternal)
|
object selector, IRoutingOutputs parent, bool isInternal)
|
||||||
: base(key, type, connType, selector, isInternal)
|
: base(key, type, connType, selector, isInternal)
|
||||||
@@ -52,10 +35,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
InUseTracker = new InUseTracking();
|
InUseTracker = new InUseTracking();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string representation of the output port.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string in the format "ParentDeviceKey|PortKey|SignalType|ConnectionType".</returns>
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}";
|
return $"{ParentDevice.Key}|{Key}|{Type}|{ConnectionType}";
|
||||||
|
|||||||
@@ -4,47 +4,18 @@
|
|||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for <see cref="RoutingInputPort"/> and <see cref="RoutingOutputPort"/>.
|
/// Base class for RoutingInput and Output ports
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class RoutingPort : IKeyed
|
public abstract class RoutingPort : IKeyed
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The unique key identifying this port within its parent device.
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; private set; }
|
public string Key { get; private set; }
|
||||||
/// <summary>
|
|
||||||
/// The type of signal this port handles (e.g., Audio, Video, AudioVideo).
|
|
||||||
/// </summary>
|
|
||||||
public eRoutingSignalType Type { get; private set; }
|
public eRoutingSignalType Type { get; private set; }
|
||||||
/// <summary>
|
|
||||||
/// The physical connection type of this port (e.g., Hdmi, Rca, Dm).
|
|
||||||
/// </summary>
|
|
||||||
public eRoutingPortConnectionType ConnectionType { get; private set; }
|
public eRoutingPortConnectionType ConnectionType { get; private set; }
|
||||||
/// <summary>
|
|
||||||
/// An object (often a number or string) used by the parent routing device to select this port during switching.
|
|
||||||
/// </summary>
|
|
||||||
public readonly object Selector;
|
public readonly object Selector;
|
||||||
/// <summary>
|
|
||||||
/// Indicates if this port represents an internal connection within a device (e.g., card to backplane).
|
|
||||||
/// </summary>
|
|
||||||
public bool IsInternal { get; private set; }
|
public bool IsInternal { get; private set; }
|
||||||
/// <summary>
|
|
||||||
/// An object used to match feedback values to this port, if applicable.
|
|
||||||
/// </summary>
|
|
||||||
public object FeedbackMatchObject { get; set; }
|
public object FeedbackMatchObject { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// A reference to the underlying hardware port object (e.g., SimplSharpPro Port), if applicable.
|
|
||||||
/// </summary>
|
|
||||||
public object Port { get; set; }
|
public object Port { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="RoutingPort"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">The unique key for this port.</param>
|
|
||||||
/// <param name="type">The signal type supported by this port.</param>
|
|
||||||
/// <param name="connType">The physical connection type of this port.</param>
|
|
||||||
/// <param name="selector">The selector object for switching.</param>
|
|
||||||
/// <param name="isInternal">True if this port is internal.</param>
|
|
||||||
public RoutingPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, object selector, bool isInternal)
|
public RoutingPort(string key, eRoutingSignalType type, eRoutingPortConnectionType connType, object selector, bool isInternal)
|
||||||
{
|
{
|
||||||
Key = key;
|
Key = key;
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ using Crestron.SimplSharp;
|
|||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines constant string values for common routing port keys.
|
/// These should correspond directly with the portNames var in the config tool.
|
||||||
/// These should correspond directly with the portNames var in the config tool.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RoutingPortNames
|
public class RoutingPortNames
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,23 +4,14 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core
|
namespace PepperDash.Essentials.Core
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents a connection (tie line) between a <see cref="RoutingOutputPort"/> and a <see cref="RoutingInputPort"/>.
|
|
||||||
/// </summary>
|
|
||||||
public class TieLine
|
public class TieLine
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The source output port of the tie line.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingOutputPort SourcePort { get; private set; }
|
public RoutingOutputPort SourcePort { get; private set; }
|
||||||
/// <summary>
|
|
||||||
/// The destination input port of the tie line.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingInputPort DestinationPort { get; private set; }
|
public RoutingInputPort DestinationPort { get; private set; }
|
||||||
//public int InUseCount { get { return DestinationUsingThis.Count; } }
|
//public int InUseCount { get { return DestinationUsingThis.Count; } }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of this tie line. Will either be the type of the destination port
|
/// Gets the type of this tie line. Will either be the type of the desination port
|
||||||
/// or the type of OverrideType when it is set.
|
/// or the type of OverrideType when it is set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public eRoutingSignalType Type
|
public eRoutingSignalType Type
|
||||||
@@ -44,27 +35,20 @@ namespace PepperDash.Essentials.Core
|
|||||||
//List<IRoutingInputs> DestinationUsingThis = new List<IRoutingInputs>();
|
//List<IRoutingInputs> DestinationUsingThis = new List<IRoutingInputs>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this tie line represents an internal connection within a device (both source and destination ports are internal).
|
/// For tie lines that represent internal links, like from cards to the matrix in a DM.
|
||||||
|
/// This property is true if SourcePort and DestinationPort IsInternal
|
||||||
|
/// property are both true
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsInternal { get { return SourcePort.IsInternal && DestinationPort.IsInternal; } }
|
public bool IsInternal { get { return SourcePort.IsInternal && DestinationPort.IsInternal; } }
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the signal types of the source and destination ports differ.
|
|
||||||
/// </summary>
|
|
||||||
public bool TypeMismatch { get { return SourcePort.Type != DestinationPort.Type; } }
|
public bool TypeMismatch { get { return SourcePort.Type != DestinationPort.Type; } }
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the connection types of the source and destination ports differ.
|
|
||||||
/// </summary>
|
|
||||||
public bool ConnectionTypeMismatch { get { return SourcePort.ConnectionType != DestinationPort.ConnectionType; } }
|
public bool ConnectionTypeMismatch { get { return SourcePort.ConnectionType != DestinationPort.ConnectionType; } }
|
||||||
/// <summary>
|
|
||||||
/// A descriptive note about any type mismatch, if applicable.
|
|
||||||
/// </summary>
|
|
||||||
public string TypeMismatchNote { get; set; }
|
public string TypeMismatchNote { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TieLine"/> class.
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sourcePort">The source output port.</param>
|
/// <param name="sourcePort"></param>
|
||||||
/// <param name="destinationPort">The destination input port.</param>
|
/// <param name="destinationPort"></param>
|
||||||
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort)
|
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort)
|
||||||
{
|
{
|
||||||
if (sourcePort == null || destinationPort == null)
|
if (sourcePort == null || destinationPort == null)
|
||||||
@@ -74,11 +58,9 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a tie line with an overriding Type. See help for OverrideType property for info.
|
/// Creates a tie line with an overriding Type. See help for OverrideType property for info
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sourcePort">The source output port.</param>
|
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type</param>
|
||||||
/// <param name="destinationPort">The destination input port.</param>
|
|
||||||
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type for routing calculations.</param>
|
|
||||||
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType? overrideType) :
|
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType? overrideType) :
|
||||||
this(sourcePort, destinationPort)
|
this(sourcePort, destinationPort)
|
||||||
{
|
{
|
||||||
@@ -86,11 +68,9 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a tie line with an overriding Type. See help for OverrideType property for info.
|
/// Creates a tie line with an overriding Type. See help for OverrideType property for info
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sourcePort">The source output port.</param>
|
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type</param>
|
||||||
/// <param name="destinationPort">The destination input port.</param>
|
|
||||||
/// <param name="overrideType">The signal type to limit the link to. Overrides DestinationPort.Type for routing calculations.</param>
|
|
||||||
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType overrideType) :
|
public TieLine(RoutingOutputPort sourcePort, RoutingInputPort destinationPort, eRoutingSignalType overrideType) :
|
||||||
this(sourcePort, destinationPort)
|
this(sourcePort, destinationPort)
|
||||||
{
|
{
|
||||||
@@ -98,25 +78,18 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Will link up video status from supporting inputs to connected outputs.
|
/// Will link up video status from supporting inputs to connected outputs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Activate()
|
public void Activate()
|
||||||
{
|
{
|
||||||
// Now does nothing
|
// Now does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deactivates the tie line.
|
|
||||||
/// </summary>
|
|
||||||
public void Deactivate()
|
public void Deactivate()
|
||||||
{
|
{
|
||||||
// Now does nothing
|
// Now does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string representation of the tie line.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string describing the source, destination, and type of the tie line.</returns>
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("Tie line: {0}:{1} --> {2}:{3} {4}", SourcePort.ParentDevice.Key, SourcePort.Key,
|
return string.Format("Tie line: {0}:{1} --> {2}:{3} {4}", SourcePort.ParentDevice.Key, SourcePort.Key,
|
||||||
@@ -126,14 +99,8 @@ namespace PepperDash.Essentials.Core
|
|||||||
|
|
||||||
//********************************************************************************
|
//********************************************************************************
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a collection of <see cref="TieLine"/> objects.
|
|
||||||
/// </summary>
|
|
||||||
public class TieLineCollection : List<TieLine>
|
public class TieLineCollection : List<TieLine>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets the default singleton instance of the <see cref="TieLineCollection"/>.
|
|
||||||
/// </summary>
|
|
||||||
public static TieLineCollection Default
|
public static TieLineCollection Default
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -144,9 +111,6 @@ namespace PepperDash.Essentials.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Backing field for the singleton instance.
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private static TieLineCollection _Default;
|
private static TieLineCollection _Default;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using Crestron.SimplSharp.CrestronIO;
|
using Crestron.SimplSharp.CrestronIO;
|
||||||
@@ -13,44 +15,15 @@ using Serilog.Events;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Config
|
namespace PepperDash.Essentials.Core.Config
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Represents the configuration data for a single tie line between two routing ports.
|
|
||||||
/// </summary>
|
|
||||||
public class TieLineConfig
|
public class TieLineConfig
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The key of the source device.
|
|
||||||
/// </summary>
|
|
||||||
public string SourceKey { get; set; }
|
public string SourceKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the source card (if applicable, e.g., in a modular chassis).
|
|
||||||
/// </summary>
|
|
||||||
public string SourceCard { get; set; }
|
public string SourceCard { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the source output port.
|
|
||||||
/// </summary>
|
|
||||||
public string SourcePort { get; set; }
|
public string SourcePort { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the destination device.
|
|
||||||
/// </summary>
|
|
||||||
public string DestinationKey { get; set; }
|
public string DestinationKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the destination card (if applicable).
|
|
||||||
/// </summary>
|
|
||||||
public string DestinationCard { get; set; }
|
public string DestinationCard { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The key of the destination input port.
|
|
||||||
/// </summary>
|
|
||||||
public string DestinationPort { get; set; }
|
public string DestinationPort { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optional override for the signal type of the tie line. If set, this overrides the destination port's type for routing calculations.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public eRoutingSignalType? OverrideType { get; set; }
|
public eRoutingSignalType? OverrideType { get; set; }
|
||||||
@@ -100,19 +73,11 @@ namespace PepperDash.Essentials.Core.Config
|
|||||||
return new TieLine(sourceOutputPort, destinationInputPort, OverrideType);
|
return new TieLine(sourceOutputPort, destinationInputPort, OverrideType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Logs an error message related to creating this tie line configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="msg">The specific error message.</param>
|
|
||||||
void LogError(string msg)
|
void LogError(string msg)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Error, "WARNING: Cannot create tie line: {message}:\r {tieLineConfig}",null, msg, this);
|
Debug.LogMessage(LogEventLevel.Error, "WARNING: Cannot create tie line: {message}:\r {tieLineConfig}",null, msg, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a string representation of the tie line configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string describing the source and destination of the configured tie line.</returns>
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("{0}.{1}.{2} --> {3}.{4}.{5}", SourceKey, SourceCard, SourcePort,
|
return string.Format("{0}.{1}.{2} --> {3}.{4}.{5}", SourceKey, SourceCard, SourcePort,
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
|
|||||||
|
|
||||||
SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist);
|
SendCameraPresetNamesToApi(presetsCamera, joinMap, trilist);
|
||||||
|
|
||||||
for (int i = 0; i < joinMap.PresetRecallStart.JoinSpan; i++)
|
for (int i = 0; i < joinMap.NumberOfPresets.JoinSpan; i++)
|
||||||
{
|
{
|
||||||
int tempNum = i;
|
int tempNum = i;
|
||||||
|
|
||||||
|
|||||||
@@ -3,60 +3,29 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Crestron.SimplSharp;
|
using Crestron.SimplSharp;
|
||||||
using PepperDash.Core;
|
|
||||||
using PepperDash.Essentials.Core;
|
using PepperDash.Essentials.Core;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.Cameras
|
namespace PepperDash.Essentials.Devices.Common.Cameras
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Enum for camera control modes
|
|
||||||
/// </summary>
|
|
||||||
public enum eCameraControlMode
|
public enum eCameraControlMode
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Manual control mode, where the camera is controlled directly by the user or system
|
|
||||||
/// </summary>
|
|
||||||
Manual = 0,
|
Manual = 0,
|
||||||
/// <summary>
|
|
||||||
/// Off control mode, where the camera is turned off or disabled
|
|
||||||
/// </summary>
|
|
||||||
Off,
|
Off,
|
||||||
/// <summary>
|
|
||||||
/// Auto control mode, where the camera automatically adjusts settings based on the environment or conditions
|
|
||||||
/// </summary>
|
|
||||||
Auto
|
Auto
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
public interface IHasCameras
|
||||||
/// Interface for devices that have cameras
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasCameras : IKeyName
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Event that is raised when a camera is selected
|
|
||||||
/// </summary>
|
|
||||||
event EventHandler<CameraSelectedEventArgs> CameraSelected;
|
event EventHandler<CameraSelectedEventArgs> CameraSelected;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of cameras on the device. This should be a list of CameraBase objects
|
|
||||||
/// </summary>
|
|
||||||
List<CameraBase> Cameras { get; }
|
List<CameraBase> Cameras { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The currently selected camera. This should be a CameraBase object
|
|
||||||
/// </summary>
|
|
||||||
CameraBase SelectedCamera { get; }
|
CameraBase SelectedCamera { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feedback that indicates the currently selected camera
|
|
||||||
/// </summary>
|
|
||||||
StringFeedback SelectedCameraFeedback { get; }
|
StringFeedback SelectedCameraFeedback { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Selects a camera from the list of available cameras based on the provided key.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">The unique identifier or name of the camera to select.</param>
|
|
||||||
void SelectCamera(string key);
|
void SelectCamera(string key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,14 +42,7 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasCameraOff
|
public interface IHasCameraOff
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Feedback that indicates whether the camera is off
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback CameraIsOffFeedback { get; }
|
BoolFeedback CameraIsOffFeedback { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Turns the camera off, blanking the near end video
|
|
||||||
/// </summary>
|
|
||||||
void CameraOff();
|
void CameraOff();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,71 +51,31 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasCameraMute
|
public interface IHasCameraMute
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Feedback that indicates whether the camera is muted
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback CameraIsMutedFeedback { get; }
|
BoolFeedback CameraIsMutedFeedback { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mutes the camera video, preventing it from being sent to the far end
|
|
||||||
/// </summary>
|
|
||||||
void CameraMuteOn();
|
void CameraMuteOn();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unmutes the camera video, allowing it to be sent to the far end
|
|
||||||
/// </summary>
|
|
||||||
void CameraMuteOff();
|
void CameraMuteOff();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toggles the camera mute state. If the camera is muted, it will be unmuted, and vice versa.
|
|
||||||
/// </summary>
|
|
||||||
void CameraMuteToggle();
|
void CameraMuteToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for devices that can mute and unmute their camera video, with an event for unmute requests
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasCameraMuteWithUnmuteReqeust : IHasCameraMute
|
public interface IHasCameraMuteWithUnmuteReqeust : IHasCameraMute
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Event that is raised when a video unmute is requested, typically by the far end
|
|
||||||
/// </summary>
|
|
||||||
event EventHandler VideoUnmuteRequested;
|
event EventHandler VideoUnmuteRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event arguments for the CameraSelected event
|
|
||||||
/// </summary>
|
|
||||||
public class CameraSelectedEventArgs : EventArgs
|
public class CameraSelectedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The selected camera
|
|
||||||
/// </summary>
|
|
||||||
public CameraBase SelectedCamera { get; private set; }
|
public CameraBase SelectedCamera { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructor for CameraSelectedEventArgs
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="camera"></param>
|
|
||||||
public CameraSelectedEventArgs(CameraBase camera)
|
public CameraSelectedEventArgs(CameraBase camera)
|
||||||
{
|
{
|
||||||
SelectedCamera = camera;
|
SelectedCamera = camera;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for devices that have a far end camera control
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasFarEndCameraControl
|
public interface IHasFarEndCameraControl
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets the far end camera, which is typically a CameraBase object that represents the camera at the far end of a call
|
|
||||||
/// </summary>
|
|
||||||
CameraBase FarEndCamera { get; }
|
CameraBase FarEndCamera { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feedback that indicates whether the far end camera is being controlled
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback ControllingFarEndCameraFeedback { get; }
|
BoolFeedback ControllingFarEndCameraFeedback { get; }
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -166,9 +88,6 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for devices that have camera controls
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasCameraControls
|
public interface IHasCameraControls
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -189,19 +108,8 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasCameraPanControl : IHasCameraControls
|
public interface IHasCameraPanControl : IHasCameraControls
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Pans the camera left
|
|
||||||
/// </summary>
|
|
||||||
void PanLeft();
|
void PanLeft();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pans the camera right
|
|
||||||
/// </summary>
|
|
||||||
void PanRight();
|
void PanRight();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops the camera pan movement
|
|
||||||
/// </summary>
|
|
||||||
void PanStop();
|
void PanStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,19 +118,8 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasCameraTiltControl : IHasCameraControls
|
public interface IHasCameraTiltControl : IHasCameraControls
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Tilts the camera down
|
|
||||||
/// </summary>
|
|
||||||
void TiltDown();
|
void TiltDown();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tilts the camera up
|
|
||||||
/// </summary>
|
|
||||||
void TiltUp();
|
void TiltUp();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops the camera tilt movement
|
|
||||||
/// </summary>
|
|
||||||
void TiltStop();
|
void TiltStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,19 +128,8 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasCameraZoomControl : IHasCameraControls
|
public interface IHasCameraZoomControl : IHasCameraControls
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Zooms the camera in
|
|
||||||
/// </summary>
|
|
||||||
void ZoomIn();
|
void ZoomIn();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Zooms the camera out
|
|
||||||
/// </summary>
|
|
||||||
void ZoomOut();
|
void ZoomOut();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops the camera zoom movement
|
|
||||||
/// </summary>
|
|
||||||
void ZoomStop();
|
void ZoomStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,71 +138,25 @@ namespace PepperDash.Essentials.Devices.Common.Cameras
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasCameraFocusControl : IHasCameraControls
|
public interface IHasCameraFocusControl : IHasCameraControls
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Focuses the camera near
|
|
||||||
/// </summary>
|
|
||||||
void FocusNear();
|
void FocusNear();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Focuses the camera far
|
|
||||||
/// </summary>
|
|
||||||
void FocusFar();
|
void FocusFar();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops the camera focus movement
|
|
||||||
/// </summary>
|
|
||||||
void FocusStop();
|
void FocusStop();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Triggers the camera's auto focus functionality, if available.
|
|
||||||
/// </summary>
|
|
||||||
void TriggerAutoFocus();
|
void TriggerAutoFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for devices that have auto focus mode control
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasAutoFocusMode
|
public interface IHasAutoFocusMode
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Sets the focus mode to auto or manual, or toggles between them.
|
|
||||||
/// </summary>
|
|
||||||
void SetFocusModeAuto();
|
void SetFocusModeAuto();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the focus mode to manual, allowing for manual focus adjustments.
|
|
||||||
/// </summary>
|
|
||||||
void SetFocusModeManual();
|
void SetFocusModeManual();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toggles the focus mode between auto and manual.
|
|
||||||
/// </summary>
|
|
||||||
void ToggleFocusMode();
|
void ToggleFocusMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for devices that have camera auto mode control
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasCameraAutoMode : IHasCameraControls
|
public interface IHasCameraAutoMode : IHasCameraControls
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Enables or disables the camera's auto mode, which may include automatic adjustments for focus, exposure, and other settings.
|
|
||||||
/// </summary>
|
|
||||||
void CameraAutoModeOn();
|
void CameraAutoModeOn();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disables the camera's auto mode, allowing for manual control of camera settings.
|
|
||||||
/// </summary>
|
|
||||||
void CameraAutoModeOff();
|
void CameraAutoModeOff();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toggles the camera's auto mode state. If the camera is in auto mode, it will switch to manual mode, and vice versa.
|
|
||||||
/// </summary>
|
|
||||||
void CameraAutoModeToggle();
|
void CameraAutoModeToggle();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feedback that indicates whether the camera's auto mode is currently enabled.
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback CameraAutoModeIsOnFeedback { get; }
|
BoolFeedback CameraAutoModeIsOnFeedback { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
using PepperDash.Core;
|
|
||||||
using PepperDash.Essentials.Core;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.Codec.Cisco
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Describes the available tracking modes for a Cisco codec's Presenter Track feature.
|
|
||||||
/// </summary>
|
|
||||||
public enum ePresenterTrackMode
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Presenter Track is turned off.
|
|
||||||
/// </summary>
|
|
||||||
Off,
|
|
||||||
/// <summary>
|
|
||||||
/// Presenter Track follows the speaker's movements.
|
|
||||||
/// </summary>
|
|
||||||
Follow,
|
|
||||||
/// <summary>
|
|
||||||
/// Presenter Track is set to background mode, where it tracks the speaker but does not actively follow.
|
|
||||||
/// </summary>
|
|
||||||
Background,
|
|
||||||
/// <summary>
|
|
||||||
/// Presenter Track is set to persistent mode, where it maintains a fixed position or focus on the speaker.
|
|
||||||
/// </summary>
|
|
||||||
Persistent
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Describes the Presenter Track controls for a Cisco codec.
|
|
||||||
/// </summary>
|
|
||||||
public interface IPresenterTrack : IKeyed
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
bool PresenterTrackAvailability { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feedback indicating whether Presenter Track is available.
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback PresenterTrackAvailableFeedback { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feedback indicating the current status of Presenter Track is off
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback PresenterTrackStatusOffFeedback { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feedback indicating the current status of Presenter Track is follow
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback PresenterTrackStatusFollowFeedback { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feedback indicating the current status of Presenter Track is background
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback PresenterTrackStatusBackgroundFeedback { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feedback indicating the current status of Presenter Track is persistent
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback PresenterTrackStatusPersistentFeedback { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates the current status of Presenter Track.
|
|
||||||
/// </summary>
|
|
||||||
bool PresenterTrackStatus { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Turns off Presenter Track.
|
|
||||||
/// </summary>
|
|
||||||
void PresenterTrackOff();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Turns on Presenter Track in follow mode.
|
|
||||||
/// </summary>
|
|
||||||
void PresenterTrackFollow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Turns on Presenter Track in background mode.
|
|
||||||
/// </summary>
|
|
||||||
void PresenterTrackBackground();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Turns on Presenter Track in persistent mode.
|
|
||||||
/// </summary>
|
|
||||||
void PresenterTrackPersistent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
using PepperDash.Core;
|
|
||||||
using PepperDash.Essentials.Core;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.Codec.Cisco
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Describes the available tracking modes for a Cisco codec
|
|
||||||
/// </summary>
|
|
||||||
public interface ISpeakerTrack : IKeyed
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates whether Speaker Track is available on the codec.
|
|
||||||
/// </summary>
|
|
||||||
bool SpeakerTrackAvailability { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
BoolFeedback SpeakerTrackAvailableFeedback { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feedback indicating the current status of Speaker Track is off
|
|
||||||
/// </summary>
|
|
||||||
bool SpeakerTrackStatus { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Turns Speaker Track off
|
|
||||||
/// </summary>
|
|
||||||
void SpeakerTrackOff();
|
|
||||||
/// <summary>
|
|
||||||
/// Turns Speaker Track on
|
|
||||||
/// </summary>
|
|
||||||
void SpeakerTrackOn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,13 +18,9 @@ namespace PepperDash.Essentials.Devices.Common.DSP
|
|||||||
|
|
||||||
public Dictionary<string, DspControlPoint> SwitcherControlPoints { get; private set; }
|
public Dictionary<string, DspControlPoint> SwitcherControlPoints { get; private set; }
|
||||||
|
|
||||||
public DspBase(string key, string name) :
|
public DspBase(string key, string name) :
|
||||||
base(key, name)
|
base(key, name)
|
||||||
{
|
{
|
||||||
|
|
||||||
LevelControlPoints = new Dictionary<string, IBasicVolumeWithFeedback>();
|
|
||||||
DialerControlPoints = new Dictionary<string, DspControlPoint>();
|
|
||||||
SwitcherControlPoints = new Dictionary<string, DspControlPoint>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,199 +1,110 @@
|
|||||||
using System;
|
using Crestron.SimplSharp;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharpPro.DeviceSupport;
|
using Crestron.SimplSharpPro.DeviceSupport;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using PepperDash.Essentials.Core;
|
using PepperDash.Essentials.Core;
|
||||||
using PepperDash.Essentials.Core.Bridges;
|
using PepperDash.Essentials.Core.Bridges;
|
||||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||||
using PepperDash.Essentials.Core.Routing;
|
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Feedback = PepperDash.Essentials.Core.Feedback;
|
using Feedback = PepperDash.Essentials.Core.Feedback;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.Displays
|
namespace PepperDash.Essentials.Devices.Common.Displays
|
||||||
{
|
{
|
||||||
/// <summary>
|
public abstract class DisplayBase : EssentialsDevice, IDisplay
|
||||||
/// Abstract base class for display devices that provides common display functionality
|
|
||||||
/// including power control, input switching, and routing capabilities.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class DisplayBase : EssentialsDevice, IDisplay, ICurrentSources
|
|
||||||
{
|
{
|
||||||
private RoutingInputPort _currentInputPort;
|
private RoutingInputPort _currentInputPort;
|
||||||
|
public RoutingInputPort CurrentInputPort
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _currentInputPort;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected set
|
||||||
/// Gets or sets the current input port that is selected on the display.
|
{
|
||||||
/// </summary>
|
if (_currentInputPort == value) return;
|
||||||
public RoutingInputPort CurrentInputPort
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _currentInputPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected set
|
_currentInputPort = value;
|
||||||
{
|
|
||||||
if (_currentInputPort == value) return;
|
|
||||||
|
|
||||||
_currentInputPort = value;
|
InputChanged?.Invoke(this, _currentInputPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
InputChanged?.Invoke(this, _currentInputPort);
|
public event InputChangedEventHandler InputChanged;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public event SourceInfoChangeHandler CurrentSourceChange;
|
||||||
/// Event that is raised when the input changes on the display.
|
|
||||||
/// </summary>
|
|
||||||
public event InputChangedEventHandler InputChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
public string CurrentSourceInfoKey { get; set; }
|
||||||
/// Event that is raised when the current source information changes.
|
public SourceListItem CurrentSourceInfo
|
||||||
/// </summary>
|
{
|
||||||
public event SourceInfoChangeHandler CurrentSourceChange;
|
get
|
||||||
|
{
|
||||||
|
return _CurrentSourceInfo;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _CurrentSourceInfo) return;
|
||||||
|
|
||||||
/// <summary>
|
var handler = CurrentSourceChange;
|
||||||
/// Gets or sets the key of the current source information.
|
|
||||||
/// </summary>
|
|
||||||
public string CurrentSourceInfoKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
if (handler != null)
|
||||||
/// Gets or sets the current source information for the display.
|
handler(_CurrentSourceInfo, ChangeType.WillChange);
|
||||||
/// </summary>
|
|
||||||
public SourceListItem CurrentSourceInfo
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _CurrentSourceInfo;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == _CurrentSourceInfo) return;
|
|
||||||
|
|
||||||
var handler = CurrentSourceChange;
|
_CurrentSourceInfo = value;
|
||||||
|
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
handler(_CurrentSourceInfo, ChangeType.WillChange);
|
handler(_CurrentSourceInfo, ChangeType.DidChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SourceListItem _CurrentSourceInfo;
|
||||||
|
|
||||||
_CurrentSourceInfo = value;
|
|
||||||
|
|
||||||
if (handler != null)
|
|
||||||
handler(_CurrentSourceInfo, ChangeType.DidChange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SourceListItem _CurrentSourceInfo;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public Dictionary<eRoutingSignalType, SourceListItem> CurrentSources { get; private set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public Dictionary<eRoutingSignalType, string> CurrentSourceKeys { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets feedback indicating whether the display is currently cooling down after being powered off.
|
|
||||||
/// </summary>
|
|
||||||
public BoolFeedback IsCoolingDownFeedback { get; protected set; }
|
public BoolFeedback IsCoolingDownFeedback { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets feedback indicating whether the display is currently warming up after being powered on.
|
|
||||||
/// </summary>
|
|
||||||
public BoolFeedback IsWarmingUpFeedback { get; private set; }
|
public BoolFeedback IsWarmingUpFeedback { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
public UsageTracking UsageTracker { get; set; }
|
||||||
/// Gets or sets the usage tracking instance for monitoring display usage statistics.
|
|
||||||
/// </summary>
|
|
||||||
public UsageTracking UsageTracker { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the warmup time in milliseconds for the display to become ready after power on.
|
|
||||||
/// </summary>
|
|
||||||
public uint WarmupTime { get; set; }
|
public uint WarmupTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the cooldown time in milliseconds for the display to fully power down.
|
|
||||||
/// </summary>
|
|
||||||
public uint CooldownTime { get; set; }
|
public uint CooldownTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstract function that must be implemented by derived classes to provide the cooling down feedback value.
|
/// Bool Func that will provide a value for the PowerIsOn Output. Must be implemented
|
||||||
/// Must be implemented by concrete sub-classes.
|
/// by concrete sub-classes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
abstract protected Func<bool> IsCoolingDownFeedbackFunc { get; }
|
abstract protected Func<bool> IsCoolingDownFeedbackFunc { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Abstract function that must be implemented by derived classes to provide the warming up feedback value.
|
|
||||||
/// Must be implemented by concrete sub-classes.
|
|
||||||
/// </summary>
|
|
||||||
abstract protected Func<bool> IsWarmingUpFeedbackFunc { get; }
|
abstract protected Func<bool> IsWarmingUpFeedbackFunc { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Timer used for managing display warmup timing.
|
|
||||||
/// </summary>
|
|
||||||
protected CTimer WarmupTimer;
|
|
||||||
|
|
||||||
/// <summary>
|
protected CTimer WarmupTimer;
|
||||||
/// Timer used for managing display cooldown timing.
|
|
||||||
/// </summary>
|
|
||||||
protected CTimer CooldownTimer;
|
protected CTimer CooldownTimer;
|
||||||
|
|
||||||
#region IRoutingInputs Members
|
#region IRoutingInputs Members
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the collection of input ports available on this display device.
|
|
||||||
/// </summary>
|
|
||||||
public RoutingPortCollection<RoutingInputPort> InputPorts { get; private set; }
|
public RoutingPortCollection<RoutingInputPort> InputPorts { get; private set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
protected DisplayBase(string key, string name)
|
||||||
/// Initializes a new instance of the DisplayBase class.
|
: base(key, name)
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">The unique key identifier for this display device.</param>
|
|
||||||
/// <param name="name">The friendly name for this display device.</param>
|
|
||||||
protected DisplayBase(string key, string name)
|
|
||||||
: base(key, name)
|
|
||||||
{
|
{
|
||||||
IsCoolingDownFeedback = new BoolFeedback("IsCoolingDown", IsCoolingDownFeedbackFunc);
|
IsCoolingDownFeedback = new BoolFeedback("IsCoolingDown", IsCoolingDownFeedbackFunc);
|
||||||
IsWarmingUpFeedback = new BoolFeedback("IsWarmingUp", IsWarmingUpFeedbackFunc);
|
IsWarmingUpFeedback = new BoolFeedback("IsWarmingUp", IsWarmingUpFeedbackFunc);
|
||||||
|
|
||||||
InputPorts = new RoutingPortCollection<RoutingInputPort>();
|
InputPorts = new RoutingPortCollection<RoutingInputPort>();
|
||||||
|
|
||||||
CurrentSources = new Dictionary<eRoutingSignalType, SourceListItem>
|
|
||||||
{
|
|
||||||
{ eRoutingSignalType.Audio, null },
|
|
||||||
{ eRoutingSignalType.Video, null },
|
|
||||||
};
|
|
||||||
|
|
||||||
CurrentSourceKeys = new Dictionary<eRoutingSignalType, string>
|
|
||||||
{
|
|
||||||
{ eRoutingSignalType.Audio, string.Empty },
|
|
||||||
{ eRoutingSignalType.Video, string.Empty },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Powers on the display device. Must be implemented by derived classes.
|
|
||||||
/// </summary>
|
|
||||||
public abstract void PowerOn();
|
public abstract void PowerOn();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Powers off the display device. Must be implemented by derived classes.
|
|
||||||
/// </summary>
|
|
||||||
public abstract void PowerOff();
|
public abstract void PowerOff();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toggles the power state of the display device. Must be implemented by derived classes.
|
|
||||||
/// </summary>
|
|
||||||
public abstract void PowerToggle();
|
public abstract void PowerToggle();
|
||||||
|
|
||||||
/// <summary>
|
public virtual FeedbackCollection<Feedback> Feedbacks
|
||||||
/// Gets the collection of feedback objects for this display device.
|
|
||||||
/// </summary>
|
|
||||||
public virtual FeedbackCollection<Feedback> Feedbacks
|
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return new FeedbackCollection<Feedback>
|
return new FeedbackCollection<Feedback>
|
||||||
{
|
{
|
||||||
IsCoolingDownFeedback,
|
IsCoolingDownFeedback,
|
||||||
IsWarmingUpFeedback
|
IsWarmingUpFeedback
|
||||||
@@ -201,50 +112,30 @@ namespace PepperDash.Essentials.Devices.Common.Displays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public abstract void ExecuteSwitch(object selector);
|
||||||
/// Executes a switch to the specified input on the display device. Must be implemented by derived classes.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="selector">The selector object that identifies which input to switch to.</param>
|
|
||||||
public abstract void ExecuteSwitch(object selector);
|
|
||||||
|
|
||||||
/// <summary>
|
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey,
|
||||||
/// Links the display device to an API using a trilist, join start, join map key, and bridge.
|
EiscApiAdvanced bridge)
|
||||||
/// This overload uses serialized join map configuration.
|
{
|
||||||
/// </summary>
|
var joinMap = new DisplayControllerJoinMap(joinStart);
|
||||||
/// <param name="displayDevice">The display device to link.</param>
|
|
||||||
/// <param name="trilist">The BasicTriList for communication.</param>
|
|
||||||
/// <param name="joinStart">The starting join number for the device.</param>
|
|
||||||
/// <param name="joinMapKey">The key for the join map configuration.</param>
|
|
||||||
/// <param name="bridge">The EISC API bridge instance.</param>
|
|
||||||
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, uint joinStart, string joinMapKey,
|
|
||||||
EiscApiAdvanced bridge)
|
|
||||||
{
|
|
||||||
var joinMap = new DisplayControllerJoinMap(joinStart);
|
|
||||||
|
|
||||||
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey);
|
var joinMapSerialized = JoinMapHelper.GetSerializedJoinMapForDevice(joinMapKey);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(joinMapSerialized))
|
if (!string.IsNullOrEmpty(joinMapSerialized))
|
||||||
joinMap = JsonConvert.DeserializeObject<DisplayControllerJoinMap>(joinMapSerialized);
|
joinMap = JsonConvert.DeserializeObject<DisplayControllerJoinMap>(joinMapSerialized);
|
||||||
|
|
||||||
if (bridge != null)
|
if (bridge != null)
|
||||||
{
|
{
|
||||||
bridge.AddJoinMap(Key, joinMap);
|
bridge.AddJoinMap(Key, joinMap);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Information, this, "Please update config to use 'eiscapiadvanced' to get all join map features for this device.");
|
Debug.LogMessage(LogEventLevel.Information,this,"Please update config to use 'eiscapiadvanced' to get all join map features for this device.");
|
||||||
}
|
}
|
||||||
|
|
||||||
LinkDisplayToApi(displayDevice, trilist, joinMap);
|
LinkDisplayToApi(displayDevice, trilist, joinMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Links the display device to an API using a trilist and join map.
|
|
||||||
/// This overload uses a pre-configured join map instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="displayDevice">The display device to link.</param>
|
|
||||||
/// <param name="trilist">The BasicTriList for communication.</param>
|
|
||||||
/// <param name="joinMap">The join map configuration for the device.</param>
|
|
||||||
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, DisplayControllerJoinMap joinMap)
|
protected void LinkDisplayToApi(DisplayBase displayDevice, BasicTriList trilist, DisplayControllerJoinMap joinMap)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X"));
|
Debug.LogMessage(LogEventLevel.Debug, "Linking to Trilist '{0}'", trilist.ID.ToString("X"));
|
||||||
@@ -377,41 +268,21 @@ namespace PepperDash.Essentials.Devices.Common.Displays
|
|||||||
volumeDisplayWithFeedback.MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]);
|
volumeDisplayWithFeedback.MuteFeedback.LinkComplementInputSig(trilist.BooleanInput[joinMap.VolumeMuteOff.JoinNumber]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public abstract class TwoWayDisplayBase : DisplayBase, IRoutingFeedback, IHasPowerControlWithFeedback
|
||||||
/// Abstract base class for two-way display devices that provide feedback capabilities.
|
|
||||||
/// Extends DisplayBase with routing feedback and power control feedback functionality.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class TwoWayDisplayBase : DisplayBase, IRoutingFeedback, IHasPowerControlWithFeedback
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
public StringFeedback CurrentInputFeedback { get; private set; }
|
||||||
/// Gets feedback for the current input selection on the display.
|
|
||||||
/// </summary>
|
|
||||||
public StringFeedback CurrentInputFeedback { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
abstract protected Func<string> CurrentInputFeedbackFunc { get; }
|
||||||
/// Abstract function that must be implemented by derived classes to provide the current input feedback value.
|
|
||||||
/// Must be implemented by concrete sub-classes.
|
|
||||||
/// </summary>
|
|
||||||
abstract protected Func<string> CurrentInputFeedbackFunc { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
public BoolFeedback PowerIsOnFeedback { get; protected set; }
|
||||||
/// Gets feedback indicating whether the display is currently powered on.
|
|
||||||
/// </summary>
|
|
||||||
public BoolFeedback PowerIsOnFeedback { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
abstract protected Func<bool> PowerIsOnFeedbackFunc { get; }
|
||||||
/// Abstract function that must be implemented by derived classes to provide the power state feedback value.
|
|
||||||
/// Must be implemented by concrete sub-classes.
|
|
||||||
/// </summary>
|
|
||||||
abstract protected Func<bool> PowerIsOnFeedbackFunc { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the default mock display instance for testing and development purposes.
|
public static MockDisplay DefaultDisplay
|
||||||
/// </summary>
|
{
|
||||||
public static MockDisplay DefaultDisplay
|
|
||||||
{
|
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_DefaultDisplay == null)
|
if (_DefaultDisplay == null)
|
||||||
@@ -421,52 +292,44 @@ namespace PepperDash.Essentials.Devices.Common.Displays
|
|||||||
}
|
}
|
||||||
static MockDisplay _DefaultDisplay;
|
static MockDisplay _DefaultDisplay;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the TwoWayDisplayBase class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">The unique key identifier for this display device.</param>
|
|
||||||
/// <param name="name">The friendly name for this display device.</param>
|
|
||||||
public TwoWayDisplayBase(string key, string name)
|
public TwoWayDisplayBase(string key, string name)
|
||||||
: base(key, name)
|
: base(key, name)
|
||||||
{
|
{
|
||||||
CurrentInputFeedback = new StringFeedback(CurrentInputFeedbackFunc);
|
CurrentInputFeedback = new StringFeedback(CurrentInputFeedbackFunc);
|
||||||
|
|
||||||
WarmupTime = 7000;
|
WarmupTime = 7000;
|
||||||
CooldownTime = 15000;
|
CooldownTime = 15000;
|
||||||
|
|
||||||
PowerIsOnFeedback = new BoolFeedback("PowerOnFeedback", PowerIsOnFeedbackFunc);
|
PowerIsOnFeedback = new BoolFeedback("PowerOnFeedback", PowerIsOnFeedbackFunc);
|
||||||
|
|
||||||
Feedbacks.Add(CurrentInputFeedback);
|
Feedbacks.Add(CurrentInputFeedback);
|
||||||
Feedbacks.Add(PowerIsOnFeedback);
|
Feedbacks.Add(PowerIsOnFeedback);
|
||||||
|
|
||||||
PowerIsOnFeedback.OutputChange += PowerIsOnFeedback_OutputChange;
|
PowerIsOnFeedback.OutputChange += PowerIsOnFeedback_OutputChange;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PowerIsOnFeedback_OutputChange(object sender, EventArgs e)
|
void PowerIsOnFeedback_OutputChange(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (UsageTracker != null)
|
if (UsageTracker != null)
|
||||||
{
|
{
|
||||||
if (PowerIsOnFeedback.BoolValue)
|
if (PowerIsOnFeedback.BoolValue)
|
||||||
UsageTracker.StartDeviceUsage();
|
UsageTracker.StartDeviceUsage();
|
||||||
else
|
else
|
||||||
UsageTracker.EndDeviceUsage();
|
UsageTracker.EndDeviceUsage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
|
||||||
/// Event that is raised when a numeric switch change occurs on the display.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<RoutingNumericEventArgs> NumericSwitchChange;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Raise an event when the status of a switch object changes.
|
/// Raise an event when the status of a switch object changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="e">Arguments defined as IKeyName sender, output, input, and eRoutingSignalType</param>
|
/// <param name="e">Arguments defined as IKeyName sender, output, input, and eRoutingSignalType</param>
|
||||||
protected void OnSwitchChange(RoutingNumericEventArgs e)
|
protected void OnSwitchChange(RoutingNumericEventArgs e)
|
||||||
{
|
{
|
||||||
var newEvent = NumericSwitchChange;
|
var newEvent = NumericSwitchChange;
|
||||||
if (newEvent != null) newEvent(this, e);
|
if (newEvent != null) newEvent(this, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.Generic
|
namespace PepperDash.Essentials.Devices.Common.Generic
|
||||||
{
|
{
|
||||||
public class GenericSink : EssentialsDevice, IRoutingSinkWithInputPort
|
public class GenericSink : EssentialsDevice, IRoutingSink
|
||||||
{
|
{
|
||||||
public GenericSink(string key, string name) : base(key, name)
|
public GenericSink(string key, string name) : base(key, name)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.Devices.Common.SoftCodec
|
namespace PepperDash.Essentials.Devices.Common.SoftCodec
|
||||||
{
|
{
|
||||||
public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingSinkWithSwitchingWithInputPort
|
public class GenericSoftCodec : EssentialsDevice, IRoutingSource, IRoutingOutputs, IRoutingSinkWithSwitching
|
||||||
{
|
{
|
||||||
private RoutingInputPort _currentInputPort;
|
private RoutingInputPort _currentInputPort;
|
||||||
|
|
||||||
public RoutingInputPort CurrentInputPort {
|
public RoutingInputPort CurrentInputPort {
|
||||||
get => _currentInputPort;
|
get => _currentInputPort;
|
||||||
set
|
set
|
||||||
|
|||||||
@@ -12,44 +12,19 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHasCodecRoomPresets
|
public interface IHasCodecRoomPresets
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Event that is raised when the list of room presets has changed.
|
|
||||||
/// </summary>
|
|
||||||
event EventHandler<EventArgs> CodecRoomPresetsListHasChanged;
|
event EventHandler<EventArgs> CodecRoomPresetsListHasChanged;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of near end presets that can be recalled.
|
|
||||||
/// </summary>
|
|
||||||
List<CodecRoomPreset> NearEndPresets { get; }
|
List<CodecRoomPreset> NearEndPresets { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of far end presets that can be recalled.
|
|
||||||
/// </summary>
|
|
||||||
List<CodecRoomPreset> FarEndRoomPresets { get; }
|
List<CodecRoomPreset> FarEndRoomPresets { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Selects a near end preset by its ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="preset"></param>
|
|
||||||
void CodecRoomPresetSelect(int preset);
|
void CodecRoomPresetSelect(int preset);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stores a near end preset with the given ID and description.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="preset"></param>
|
|
||||||
/// <param name="description"></param>
|
|
||||||
void CodecRoomPresetStore(int preset, string description);
|
void CodecRoomPresetStore(int preset, string description);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Selects a far end preset by its ID. This is typically used to recall a preset that has been defined on the far end codec.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="preset"></param>
|
|
||||||
void SelectFarEndPreset(int preset);
|
void SelectFarEndPreset(int preset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Static class for converting non-generic RoomPresets to generic CameraPresets.
|
|
||||||
/// </summary>
|
|
||||||
public static class RoomPresets
|
public static class RoomPresets
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -72,13 +47,6 @@ namespace PepperDash.Essentials.Devices.Common.VideoCodec
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CodecRoomPreset : PresetBase
|
public class CodecRoomPreset : PresetBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id"></param>
|
|
||||||
/// <param name="description"></param>
|
|
||||||
/// <param name="def"></param>
|
|
||||||
/// <param name="isDef"></param>
|
|
||||||
public CodecRoomPreset(int id, string description, bool def, bool isDef)
|
public CodecRoomPreset(int id, string description, bool def, bool isDef)
|
||||||
: base(id, description, def, isDef)
|
: base(id, description, def, isDef)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.AppServer.Messengers
|
namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Messenger for a CameraBase device
|
|
||||||
/// </summary>
|
|
||||||
public class CameraBaseMessenger : MessengerBase
|
public class CameraBaseMessenger : MessengerBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -48,9 +45,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers the actions for this messenger. This is called by the base class
|
|
||||||
/// </summary>
|
|
||||||
protected override void RegisterActions()
|
protected override void RegisterActions()
|
||||||
{
|
{
|
||||||
base.RegisterActions();
|
base.RegisterActions();
|
||||||
|
|||||||
@@ -2,69 +2,27 @@
|
|||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using PepperDash.Essentials.Core.DeviceInfo;
|
using PepperDash.Essentials.Core.DeviceInfo;
|
||||||
using System.Timers;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.AppServer.Messengers
|
namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Facilitates communication of device information by providing mechanisms for status updates and device
|
|
||||||
/// information reporting.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>The <see cref="DeviceInfoMessenger"/> class integrates with an <see
|
|
||||||
/// cref="IDeviceInfoProvider"/> to manage device-specific information. It uses a debounce timer to limit the
|
|
||||||
/// frequency of updates, ensuring efficient communication. The timer is initialized with a 1-second interval and
|
|
||||||
/// is disabled by default. This class also subscribes to device information change events and provides actions for
|
|
||||||
/// reporting full device status and triggering updates.</remarks>
|
|
||||||
public class DeviceInfoMessenger : MessengerBase
|
public class DeviceInfoMessenger : MessengerBase
|
||||||
{
|
{
|
||||||
private readonly IDeviceInfoProvider _deviceInfoProvider;
|
private readonly IDeviceInfoProvider _deviceInfoProvider;
|
||||||
|
|
||||||
private readonly Timer debounceTimer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="DeviceInfoMessenger"/> class, which facilitates communication
|
|
||||||
/// of device information.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>The messenger uses a debounce timer to limit the frequency of certain operations. The
|
|
||||||
/// timer is initialized with a 1-second interval and is disabled by default.</remarks>
|
|
||||||
/// <param name="key">A unique identifier for the messenger instance.</param>
|
|
||||||
/// <param name="messagePath">The path used for sending and receiving messages.</param>
|
|
||||||
/// <param name="device">An implementation of <see cref="IDeviceInfoProvider"/> that provides device-specific information.</param>
|
|
||||||
public DeviceInfoMessenger(string key, string messagePath, IDeviceInfoProvider device) : base(key, messagePath, device as Device)
|
public DeviceInfoMessenger(string key, string messagePath, IDeviceInfoProvider device) : base(key, messagePath, device as Device)
|
||||||
{
|
{
|
||||||
_deviceInfoProvider = device;
|
_deviceInfoProvider = device;
|
||||||
|
|
||||||
debounceTimer = new Timer(1000)
|
|
||||||
{
|
|
||||||
Enabled = false,
|
|
||||||
AutoReset = false
|
|
||||||
};
|
|
||||||
|
|
||||||
debounceTimer.Elapsed += DebounceTimer_Elapsed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DebounceTimer_Elapsed(object sender, ElapsedEventArgs e)
|
|
||||||
{
|
|
||||||
PostStatusMessage(JToken.FromObject(new
|
|
||||||
{
|
|
||||||
deviceInfo = _deviceInfoProvider.DeviceInfo
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers actions and event handlers for device information updates and status reporting.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This method sets up actions for handling device status updates and reporting full
|
|
||||||
/// device status. It also subscribes to the <see cref="IDeviceInfoProvider.DeviceInfoChanged"/> event to
|
|
||||||
/// trigger debounced updates when the device information changes.</remarks>
|
|
||||||
protected override void RegisterActions()
|
protected override void RegisterActions()
|
||||||
{
|
{
|
||||||
base.RegisterActions();
|
base.RegisterActions();
|
||||||
|
|
||||||
_deviceInfoProvider.DeviceInfoChanged += (o, a) =>
|
_deviceInfoProvider.DeviceInfoChanged += (o, a) =>
|
||||||
{
|
{
|
||||||
debounceTimer.Stop();
|
PostStatusMessage(JToken.FromObject(new
|
||||||
debounceTimer.Start();
|
{
|
||||||
|
deviceInfo = a.DeviceInfo
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
AddAction("/fullStatus", (id, context) => PostStatusMessage(new DeviceInfoStateMessage
|
AddAction("/fullStatus", (id, context) => PostStatusMessage(new DeviceInfoStateMessage
|
||||||
@@ -76,12 +34,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a message containing the state information of a device, including detailed device information.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This class is used to encapsulate the state of a device along with its associated
|
|
||||||
/// information. It extends <see cref="DeviceStateMessageBase"/> to provide additional details about the
|
|
||||||
/// device.</remarks>
|
|
||||||
public class DeviceInfoStateMessage : DeviceStateMessageBase
|
public class DeviceInfoStateMessage : DeviceStateMessageBase
|
||||||
{
|
{
|
||||||
[JsonProperty("deviceInfo")]
|
[JsonProperty("deviceInfo")]
|
||||||
|
|||||||
@@ -8,42 +8,16 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace PepperDash.Essentials.AppServer.Messengers
|
namespace PepperDash.Essentials.AppServer.Messengers
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Provides messaging functionality for managing room combination scenarios and partition states in an <see
|
|
||||||
/// cref="IEssentialsRoomCombiner"/> instance. Enables external systems to interact with the room combiner via
|
|
||||||
/// predefined actions and status updates.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This class facilitates communication with an <see cref="IEssentialsRoomCombiner"/> by
|
|
||||||
/// exposing actions for toggling modes, managing partitions, and setting room combination scenarios. It also
|
|
||||||
/// listens for feedback changes and broadcasts status updates to connected systems. Typical usage involves
|
|
||||||
/// registering actions for external commands and handling feedback events to synchronize state changes.</remarks>
|
|
||||||
public class IEssentialsRoomCombinerMessenger : MessengerBase
|
public class IEssentialsRoomCombinerMessenger : MessengerBase
|
||||||
{
|
{
|
||||||
private readonly IEssentialsRoomCombiner _roomCombiner;
|
private readonly IEssentialsRoomCombiner _roomCombiner;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="IEssentialsRoomCombinerMessenger"/> class, which facilitates
|
|
||||||
/// messaging for an <see cref="IEssentialsRoomCombiner"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This class is designed to enable communication and interaction with an <see
|
|
||||||
/// cref="IEssentialsRoomCombiner"/> through the specified messaging path. Ensure that the <paramref
|
|
||||||
/// name="roomCombiner"/> parameter is not null when creating an instance.</remarks>
|
|
||||||
/// <param name="key">The unique key identifying this messenger instance.</param>
|
|
||||||
/// <param name="messagePath">The path used for messaging operations.</param>
|
|
||||||
/// <param name="roomCombiner">The <see cref="IEssentialsRoomCombiner"/> instance associated with this messenger.</param>
|
|
||||||
public IEssentialsRoomCombinerMessenger(string key, string messagePath, IEssentialsRoomCombiner roomCombiner)
|
public IEssentialsRoomCombinerMessenger(string key, string messagePath, IEssentialsRoomCombiner roomCombiner)
|
||||||
: base(key, messagePath, roomCombiner as IKeyName)
|
: base(key, messagePath, roomCombiner as IKeyName)
|
||||||
{
|
{
|
||||||
_roomCombiner = roomCombiner;
|
_roomCombiner = roomCombiner;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers actions and event handlers for managing room combination scenarios and partition states.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This method sets up various actions that can be triggered via specific endpoints,
|
|
||||||
/// such as toggling modes, setting room combination scenarios, and managing partition states. It also
|
|
||||||
/// subscribes to feedback events to update the status when changes occur in room combination scenarios or
|
|
||||||
/// partition states.</remarks>
|
|
||||||
protected override void RegisterActions()
|
protected override void RegisterActions()
|
||||||
{
|
{
|
||||||
AddAction("/fullStatus", (id, content) => SendFullStatus());
|
AddAction("/fullStatus", (id, content) => SendFullStatus());
|
||||||
@@ -133,7 +107,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
|
|
||||||
var message = new IEssentialsRoomCombinerStateMessage
|
var message = new IEssentialsRoomCombinerStateMessage
|
||||||
{
|
{
|
||||||
DisableAutoMode = _roomCombiner.DisableAutoMode,
|
|
||||||
IsInAutoMode = _roomCombiner.IsInAutoMode,
|
IsInAutoMode = _roomCombiner.IsInAutoMode,
|
||||||
CurrentScenario = _roomCombiner.CurrentScenario,
|
CurrentScenario = _roomCombiner.CurrentScenario,
|
||||||
Rooms = rooms,
|
Rooms = rooms,
|
||||||
@@ -159,48 +132,20 @@ namespace PepperDash.Essentials.AppServer.Messengers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the state message for a room combiner system, providing information about the current configuration,
|
|
||||||
/// operational mode, and associated rooms, partitions, and scenarios.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>This class is used to encapsulate the state of a room combiner system, including its current
|
|
||||||
/// mode of operation, active room combination scenario, and the list of rooms and partitions involved. It is
|
|
||||||
/// typically serialized and transmitted to communicate the state of the system.</remarks>
|
|
||||||
public class IEssentialsRoomCombinerStateMessage : DeviceStateMessageBase
|
public class IEssentialsRoomCombinerStateMessage : DeviceStateMessageBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether automatic mode is disabled.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("disableAutoMode", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public bool DisableAutoMode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether the system is operating in automatic mode.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("isInAutoMode", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("isInAutoMode", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public bool IsInAutoMode { get; set; }
|
public bool IsInAutoMode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the current room combination scenario.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("currentScenario", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("currentScenario", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public IRoomCombinationScenario CurrentScenario { get; set; }
|
public IRoomCombinationScenario CurrentScenario { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the collection of rooms associated with the entity.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("rooms", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("rooms", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public List<IKeyName> Rooms { get; set; }
|
public List<IKeyName> Rooms { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the collection of room combination scenarios.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("roomCombinationScenarios", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("roomCombinationScenarios", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public List<IRoomCombinationScenario> RoomCombinationScenarios { get; set; }
|
public List<IRoomCombinationScenario> RoomCombinationScenarios { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the collection of partition controllers.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("partitions", NullValueHandling = NullValueHandling.Ignore)]
|
[JsonProperty("partitions", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public List<IPartitionController> Partitions { get; set; }
|
public List<IPartitionController> Partitions { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using PepperDash.Essentials.Devices.Common.Cameras;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.AppServer.Messengers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Messenger for devices that implement the IHasCameras interface.
|
|
||||||
/// </summary>
|
|
||||||
public class IHasCamerasMessenger : MessengerBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Device being bridged that implements IHasCameras interface.
|
|
||||||
/// </summary>
|
|
||||||
public IHasCameras CameraController { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Messenger for devices that implement IHasCameras interface.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="cameraController"></param>
|
|
||||||
/// <param name="messagePath"></param>
|
|
||||||
/// <exception cref="ArgumentNullException"></exception>
|
|
||||||
public IHasCamerasMessenger(string key, string messagePath , IHasCameras cameraController)
|
|
||||||
: base(key, messagePath, cameraController)
|
|
||||||
{
|
|
||||||
CameraController = cameraController ?? throw new ArgumentNullException("cameraController");
|
|
||||||
CameraController.CameraSelected += CameraController_CameraSelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CameraController_CameraSelected(object sender, CameraSelectedEventArgs e)
|
|
||||||
{
|
|
||||||
PostStatusMessage(new IHasCamerasStateMessage
|
|
||||||
{
|
|
||||||
SelectedCamera = e.SelectedCamera
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers the actions for this messenger.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="ArgumentException"></exception>
|
|
||||||
protected override void RegisterActions()
|
|
||||||
{
|
|
||||||
base.RegisterActions();
|
|
||||||
|
|
||||||
AddAction("/fullStatus", (id, context) =>
|
|
||||||
{
|
|
||||||
SendFullStatus(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
AddAction("/selectCamera", (id, content) =>
|
|
||||||
{
|
|
||||||
var cameraKey = content?.ToObject<string>();
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(cameraKey))
|
|
||||||
{
|
|
||||||
CameraController.SelectCamera(cameraKey);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Content must be a string representing the camera key");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendFullStatus(string clientId)
|
|
||||||
{
|
|
||||||
var state = new IHasCamerasStateMessage
|
|
||||||
{
|
|
||||||
CameraList = CameraController.Cameras,
|
|
||||||
SelectedCamera = CameraController.SelectedCamera
|
|
||||||
};
|
|
||||||
|
|
||||||
PostStatusMessage(state, clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// State message for devices that implement the IHasCameras interface.
|
|
||||||
/// </summary>
|
|
||||||
public class IHasCamerasStateMessage : DeviceStateMessageBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// List of cameras available in the device.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("cameraList", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public List<CameraBase> CameraList { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The currently selected camera on the device.
|
|
||||||
/// </summary>
|
|
||||||
[JsonProperty("selectedCamera", NullValueHandling = NullValueHandling.Ignore)]
|
|
||||||
public CameraBase SelectedCamera { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -907,19 +907,6 @@ namespace PepperDash.Essentials
|
|||||||
messengerAdded = true;
|
messengerAdded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device is IHasCameras cameras)
|
|
||||||
{
|
|
||||||
this.LogVerbose("Adding IHasCamerasMessenger for {deviceKey}", device.Key
|
|
||||||
);
|
|
||||||
var messenger = new IHasCamerasMessenger(
|
|
||||||
$"{device.Key}-cameras-{Key}",
|
|
||||||
$"/device/{device.Key}",
|
|
||||||
cameras
|
|
||||||
);
|
|
||||||
AddDefaultDeviceMessenger(messenger);
|
|
||||||
messengerAdded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.LogVerbose("Trying to cast to generic device for device: {key}", device.Key);
|
this.LogVerbose("Trying to cast to generic device for device: {key}", device.Key);
|
||||||
|
|
||||||
if (device is EssentialsDevice)
|
if (device is EssentialsDevice)
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
using Crestron.SimplSharpPro;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
using Crestron.SimplSharpPro;
|
|
||||||
using Crestron.SimplSharpPro.DeviceSupport;
|
using Crestron.SimplSharpPro.DeviceSupport;
|
||||||
using Crestron.SimplSharpPro.UI;
|
using Crestron.SimplSharpPro.UI;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -14,105 +10,64 @@ using PepperDash.Essentials.Core.Config;
|
|||||||
using PepperDash.Essentials.Core.DeviceInfo;
|
using PepperDash.Essentials.Core.DeviceInfo;
|
||||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||||
using PepperDash.Essentials.Core.UI;
|
using PepperDash.Essentials.Core.UI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Feedback = PepperDash.Essentials.Core.Feedback;
|
using Feedback = PepperDash.Essentials.Core.Feedback;
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Touchpanel
|
namespace PepperDash.Essentials.Touchpanel
|
||||||
{
|
{
|
||||||
/// <summary>
|
//public interface IMobileControlTouchpanelController
|
||||||
/// Mobile Control touchpanel controller that provides app control, Zoom integration,
|
//{
|
||||||
/// and mobile control functionality for Crestron touchpanels.
|
// StringFeedback AppUrlFeedback { get; }
|
||||||
/// </summary>
|
// string DefaultRoomKey { get; }
|
||||||
public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlCrestronTouchpanelController, ITheme
|
// string DeviceKey { get; }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
public class MobileControlTouchpanelController : TouchpanelBase, IHasFeedback, ITswAppControl, ITswZoomControl, IDeviceInfoProvider, IMobileControlTouchpanelController, ITheme
|
||||||
{
|
{
|
||||||
private readonly MobileControlTouchpanelProperties localConfig;
|
private readonly MobileControlTouchpanelProperties localConfig;
|
||||||
private IMobileControlRoomMessenger _bridge;
|
private IMobileControlRoomMessenger _bridge;
|
||||||
|
|
||||||
private string _appUrl;
|
private string _appUrl;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets feedback for the current application URL.
|
|
||||||
/// </summary>
|
|
||||||
public StringFeedback AppUrlFeedback { get; private set; }
|
public StringFeedback AppUrlFeedback { get; private set; }
|
||||||
|
|
||||||
private readonly StringFeedback QrCodeUrlFeedback;
|
private readonly StringFeedback QrCodeUrlFeedback;
|
||||||
private readonly StringFeedback McServerUrlFeedback;
|
private readonly StringFeedback McServerUrlFeedback;
|
||||||
private readonly StringFeedback UserCodeFeedback;
|
private readonly StringFeedback UserCodeFeedback;
|
||||||
|
|
||||||
private readonly BoolFeedback _appOpenFeedback;
|
private readonly BoolFeedback _appOpenFeedback;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets feedback indicating whether an application is currently open on the touchpanel.
|
|
||||||
/// </summary>
|
|
||||||
public BoolFeedback AppOpenFeedback => _appOpenFeedback;
|
public BoolFeedback AppOpenFeedback => _appOpenFeedback;
|
||||||
|
|
||||||
private readonly BoolFeedback _zoomIncomingCallFeedback;
|
private readonly BoolFeedback _zoomIncomingCallFeedback;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets feedback indicating whether there is an incoming Zoom call.
|
|
||||||
/// </summary>
|
|
||||||
public BoolFeedback ZoomIncomingCallFeedback => _zoomIncomingCallFeedback;
|
public BoolFeedback ZoomIncomingCallFeedback => _zoomIncomingCallFeedback;
|
||||||
|
|
||||||
private readonly BoolFeedback _zoomInCallFeedback;
|
private readonly BoolFeedback _zoomInCallFeedback;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event that is raised when device information changes.
|
|
||||||
/// </summary>
|
|
||||||
public event DeviceInfoChangeHandler DeviceInfoChanged;
|
public event DeviceInfoChangeHandler DeviceInfoChanged;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets feedback indicating whether a Zoom call is currently active.
|
|
||||||
/// </summary>
|
|
||||||
public BoolFeedback ZoomInCallFeedback => _zoomInCallFeedback;
|
public BoolFeedback ZoomInCallFeedback => _zoomInCallFeedback;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the collection of feedback objects for this touchpanel controller.
|
|
||||||
/// </summary>
|
|
||||||
public FeedbackCollection<Feedback> Feedbacks { get; private set; }
|
public FeedbackCollection<Feedback> Feedbacks { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the collection of Zoom-related feedback objects.
|
|
||||||
/// </summary>
|
|
||||||
public FeedbackCollection<Feedback> ZoomFeedbacks { get; private set; }
|
public FeedbackCollection<Feedback> ZoomFeedbacks { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the default room key for this touchpanel controller.
|
|
||||||
/// </summary>
|
|
||||||
public string DefaultRoomKey => _config.DefaultRoomKey;
|
public string DefaultRoomKey => _config.DefaultRoomKey;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether to use direct server communication.
|
|
||||||
/// </summary>
|
|
||||||
public bool UseDirectServer => localConfig.UseDirectServer;
|
public bool UseDirectServer => localConfig.UseDirectServer;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this touchpanel acts as a Zoom Room controller.
|
|
||||||
/// </summary>
|
|
||||||
public bool ZoomRoomController => localConfig.ZoomRoomController;
|
public bool ZoomRoomController => localConfig.ZoomRoomController;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current theme for the touchpanel interface.
|
|
||||||
/// </summary>
|
|
||||||
public string Theme => localConfig.Theme;
|
public string Theme => localConfig.Theme;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets feedback for the current theme setting.
|
|
||||||
/// </summary>
|
|
||||||
public StringFeedback ThemeFeedback { get; private set; }
|
public StringFeedback ThemeFeedback { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets device information including MAC address and IP address.
|
|
||||||
/// </summary>
|
|
||||||
public DeviceInfo DeviceInfo => new DeviceInfo();
|
public DeviceInfo DeviceInfo => new DeviceInfo();
|
||||||
|
|
||||||
public ReadOnlyCollection<ConnectedIpInformation> ConnectedIps => Panel.ConnectedIpList;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the MobileControlTouchpanelController class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">The unique key identifier for this touchpanel controller.</param>
|
|
||||||
/// <param name="name">The friendly name for this touchpanel controller.</param>
|
|
||||||
/// <param name="panel">The touchpanel hardware device.</param>
|
|
||||||
/// <param name="config">The configuration properties for this controller.</param>
|
|
||||||
public MobileControlTouchpanelController(string key, string name, BasicTriListWithSmartObject panel, MobileControlTouchpanelProperties config) : base(key, name, panel, config)
|
public MobileControlTouchpanelController(string key, string name, BasicTriListWithSmartObject panel, MobileControlTouchpanelProperties config) : base(key, name, panel, config)
|
||||||
{
|
{
|
||||||
localConfig = config;
|
localConfig = config;
|
||||||
@@ -184,10 +139,6 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
RegisterForExtenders();
|
RegisterForExtenders();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the theme setting for this touchpanel controller and persists the change to configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="theme">The new theme identifier to apply.</param>
|
|
||||||
public void UpdateTheme(string theme)
|
public void UpdateTheme(string theme)
|
||||||
{
|
{
|
||||||
localConfig.Theme = theme;
|
localConfig.Theme = theme;
|
||||||
@@ -223,6 +174,8 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
x70Panel.ExtenderButtonToolbarReservedSigs.HideButtonToolbar();
|
x70Panel.ExtenderButtonToolbarReservedSigs.HideButtonToolbar();
|
||||||
x70Panel.ExtenderButtonToolbarReservedSigs.Button2Off();
|
x70Panel.ExtenderButtonToolbarReservedSigs.Button2Off();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -320,11 +273,31 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void UpdatePanelHardButtons()
|
||||||
/// Performs custom activation setup for the touchpanel controller, including
|
{
|
||||||
/// registering messengers and linking to mobile control.
|
|
||||||
/// </summary>
|
if(Panel is TswX70Base x70Panel)
|
||||||
/// <returns>True if activation was successful; otherwise, false.</returns>
|
{
|
||||||
|
x70Panel.ExtenderButtonToolbarReservedSigs.Button1Off();
|
||||||
|
x70Panel.ExtenderButtonToolbarReservedSigs.Button3Off();
|
||||||
|
x70Panel.ExtenderButtonToolbarReservedSigs.Button4Off();
|
||||||
|
x70Panel.ExtenderButtonToolbarReservedSigs.Button5Off();
|
||||||
|
x70Panel.ExtenderButtonToolbarReservedSigs.Button6Off();
|
||||||
|
|
||||||
|
if (!x70Panel.ExtenderApplicationControlReservedSigs.HideOpenedApplicationFeedback.BoolValue)
|
||||||
|
{
|
||||||
|
x70Panel.ExtenderButtonToolbarReservedSigs.ShowButtonToolbar();
|
||||||
|
x70Panel.ExtenderButtonToolbarReservedSigs.Button2On();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x70Panel.ExtenderButtonToolbarReservedSigs.HideButtonToolbar();
|
||||||
|
x70Panel.ExtenderButtonToolbarReservedSigs.Button2Off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public override bool CustomActivate()
|
public override bool CustomActivate()
|
||||||
{
|
{
|
||||||
var appMessenger = new ITswAppControlMessenger($"appControlMessenger-{Key}", $"/device/{Key}", this);
|
var appMessenger = new ITswAppControlMessenger($"appControlMessenger-{Key}", $"/device/{Key}", this);
|
||||||
@@ -354,20 +327,12 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
return base.CustomActivate();
|
return base.CustomActivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles device extender signal changes for system reserved signals.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="currentDeviceExtender">The device extender that generated the signal change.</param>
|
|
||||||
/// <param name="args">The signal event arguments containing the changed signal information.</param>
|
|
||||||
protected override void ExtenderSystemReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args)
|
protected override void ExtenderSystemReservedSigs_DeviceExtenderSigChange(DeviceExtender currentDeviceExtender, SigEventArgs args)
|
||||||
{
|
{
|
||||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, this, $"System Device Extender args: ${args.Event}:${args.Sig}");
|
Debug.LogMessage(Serilog.Events.LogEventLevel.Verbose, this, $"System Device Extender args: ${args.Event}:${args.Sig}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets up the panel drivers and signal mappings for the specified room.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="roomKey">The room key to configure the panel drivers for.</param>
|
|
||||||
protected override void SetupPanelDrivers(string roomKey)
|
protected override void SetupPanelDrivers(string roomKey)
|
||||||
{
|
{
|
||||||
AppUrlFeedback.LinkInputSig(Panel.StringInput[1]);
|
AppUrlFeedback.LinkInputSig(Panel.StringInput[1]);
|
||||||
@@ -385,6 +350,12 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
Panel.StringInput[2].StringValue = QrCodeUrlFeedback.StringValue;
|
Panel.StringInput[2].StringValue = QrCodeUrlFeedback.StringValue;
|
||||||
Panel.StringInput[3].StringValue = McServerUrlFeedback.StringValue;
|
Panel.StringInput[3].StringValue = McServerUrlFeedback.StringValue;
|
||||||
Panel.StringInput[4].StringValue = UserCodeFeedback.StringValue;
|
Panel.StringInput[4].StringValue = UserCodeFeedback.StringValue;
|
||||||
|
|
||||||
|
this.LogInformation("Updating button feedbacks from Panel online status change");
|
||||||
|
|
||||||
|
UpdatePanelHardButtons();
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,10 +399,6 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
SetAppUrl(_bridge.AppUrl);
|
SetAppUrl(_bridge.AppUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the application URL and updates the corresponding feedback.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="url">The new application URL to set.</param>
|
|
||||||
public void SetAppUrl(string url)
|
public void SetAppUrl(string url)
|
||||||
{
|
{
|
||||||
_appUrl = url;
|
_appUrl = url;
|
||||||
@@ -457,9 +424,6 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hides the currently open application on the touchpanel.
|
|
||||||
/// </summary>
|
|
||||||
public void HideOpenApp()
|
public void HideOpenApp()
|
||||||
{
|
{
|
||||||
if (Panel is TswX70Base x70Panel)
|
if (Panel is TswX70Base x70Panel)
|
||||||
@@ -475,9 +439,6 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Opens an application on the touchpanel. Note: X60 panels do not support Zoom app opening.
|
|
||||||
/// </summary>
|
|
||||||
public void OpenApp()
|
public void OpenApp()
|
||||||
{
|
{
|
||||||
if (Panel is TswX70Base x70Panel)
|
if (Panel is TswX70Base x70Panel)
|
||||||
@@ -493,9 +454,6 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Closes the currently open application on the touchpanel.
|
|
||||||
/// </summary>
|
|
||||||
public void CloseOpenApp()
|
public void CloseOpenApp()
|
||||||
{
|
{
|
||||||
if (Panel is TswX70Base x70Panel)
|
if (Panel is TswX70Base x70Panel)
|
||||||
@@ -511,9 +469,6 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ends the current Zoom call on the touchpanel.
|
|
||||||
/// </summary>
|
|
||||||
public void EndZoomCall()
|
public void EndZoomCall()
|
||||||
{
|
{
|
||||||
if (Panel is TswX70Base x70Panel)
|
if (Panel is TswX70Base x70Panel)
|
||||||
@@ -529,10 +484,6 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the device information (MAC address and IP address) from the touchpanel
|
|
||||||
/// and raises the DeviceInfoChanged event.
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateDeviceInfo()
|
public void UpdateDeviceInfo()
|
||||||
{
|
{
|
||||||
if (Panel is TswXX70Base x70Panel)
|
if (Panel is TswXX70Base x70Panel)
|
||||||
@@ -569,27 +520,14 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Factory class for creating MobileControlTouchpanelController instances from device configuration.
|
|
||||||
/// Supports various Crestron touchpanel models including TSW, TS, CrestronApp, XPanel, and DGE series.
|
|
||||||
/// </summary>
|
|
||||||
public class MobileControlTouchpanelControllerFactory : EssentialsPluginDeviceFactory<MobileControlTouchpanelController>
|
public class MobileControlTouchpanelControllerFactory : EssentialsPluginDeviceFactory<MobileControlTouchpanelController>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the MobileControlTouchpanelControllerFactory class.
|
|
||||||
/// Sets up supported device type names and minimum framework version requirements.
|
|
||||||
/// </summary>
|
|
||||||
public MobileControlTouchpanelControllerFactory()
|
public MobileControlTouchpanelControllerFactory()
|
||||||
{
|
{
|
||||||
TypeNames = new List<string>() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel", "mcdge1000" };
|
TypeNames = new List<string>() { "mccrestronapp", "mctsw550", "mctsw750", "mctsw1050", "mctsw560", "mctsw760", "mctsw1060", "mctsw570", "mctsw770", "mcts770", "mctsw1070", "mcts1070", "mcxpanel" };
|
||||||
MinimumEssentialsFrameworkVersion = "2.0.0";
|
MinimumEssentialsFrameworkVersion = "2.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Builds a MobileControlTouchpanelController device from the provided device configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dc">The device configuration containing the device properties and settings.</param>
|
|
||||||
/// <returns>A configured MobileControlTouchpanelController instance.</returns>
|
|
||||||
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
public override EssentialsDevice BuildDevice(DeviceConfig dc)
|
||||||
{
|
{
|
||||||
var comm = CommFactory.GetControlPropertiesConfig(dc);
|
var comm = CommFactory.GetControlPropertiesConfig(dc);
|
||||||
@@ -650,10 +588,7 @@ namespace PepperDash.Essentials.Touchpanel
|
|||||||
return new Tsw1070(id, Global.ControlSystem);
|
return new Tsw1070(id, Global.ControlSystem);
|
||||||
else if (type == "ts1070")
|
else if (type == "ts1070")
|
||||||
return new Ts1070(id, Global.ControlSystem);
|
return new Ts1070(id, Global.ControlSystem);
|
||||||
else if (type == "dge1000")
|
|
||||||
return new Dge1000(id, Global.ControlSystem);
|
|
||||||
else
|
else
|
||||||
|
|
||||||
{
|
{
|
||||||
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "WARNING: Cannot create TSW controller with type '{0}'", type);
|
Debug.LogMessage(Serilog.Events.LogEventLevel.Warning, "WARNING: Cannot create TSW controller with type '{0}'", type);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,27 +1,161 @@
|
|||||||
using System;
|
using Crestron.SimplSharp;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using Crestron.SimplSharp;
|
|
||||||
using Crestron.SimplSharp.WebScripting;
|
using Crestron.SimplSharp.WebScripting;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using PepperDash.Core;
|
using PepperDash.Core;
|
||||||
using PepperDash.Core.Logging;
|
using PepperDash.Core.Logging;
|
||||||
|
using PepperDash.Essentials.AppServer.Messengers;
|
||||||
using PepperDash.Essentials.Core;
|
using PepperDash.Essentials.Core;
|
||||||
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
using PepperDash.Essentials.Core.DeviceTypeInterfaces;
|
||||||
using PepperDash.Essentials.Core.Web;
|
using PepperDash.Essentials.Core.Web;
|
||||||
using PepperDash.Essentials.RoomBridges;
|
using PepperDash.Essentials.RoomBridges;
|
||||||
using PepperDash.Essentials.WebApiHandlers;
|
using PepperDash.Essentials.WebApiHandlers;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using WebSocketSharp;
|
using WebSocketSharp;
|
||||||
using WebSocketSharp.Net;
|
using WebSocketSharp.Net;
|
||||||
using WebSocketSharp.Server;
|
using WebSocketSharp.Server;
|
||||||
|
using ErrorEventArgs = WebSocketSharp.ErrorEventArgs;
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.WebSocketServer
|
namespace PepperDash.Essentials.WebSocketServer
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the behaviour to associate with a UiClient for WebSocket communication
|
||||||
|
/// </summary>
|
||||||
|
public class UiClient : WebSocketBehavior
|
||||||
|
{
|
||||||
|
public MobileControlSystemController Controller { get; set; }
|
||||||
|
|
||||||
|
public string RoomKey { get; set; }
|
||||||
|
|
||||||
|
private string _clientId;
|
||||||
|
|
||||||
|
private DateTime _connectionTime;
|
||||||
|
|
||||||
|
public TimeSpan ConnectedDuration
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Context.WebSocket.IsAlive)
|
||||||
|
{
|
||||||
|
return DateTime.Now - _connectionTime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new TimeSpan(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UiClient()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnOpen()
|
||||||
|
{
|
||||||
|
base.OnOpen();
|
||||||
|
|
||||||
|
var url = Context.WebSocket.Url;
|
||||||
|
Debug.LogMessage(LogEventLevel.Verbose, "New WebSocket Connection from: {0}", null, url);
|
||||||
|
|
||||||
|
var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)");
|
||||||
|
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
_connectionTime = DateTime.Now;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientId = match.Groups[1].Value;
|
||||||
|
_clientId = clientId;
|
||||||
|
|
||||||
|
if (Controller == null)
|
||||||
|
{
|
||||||
|
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
|
||||||
|
_connectionTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientJoinedMessage = new MobileControlMessage
|
||||||
|
{
|
||||||
|
Type = "/system/clientJoined",
|
||||||
|
Content = JToken.FromObject(new
|
||||||
|
{
|
||||||
|
clientId,
|
||||||
|
roomKey = RoomKey,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
Controller.HandleClientMessage(JsonConvert.SerializeObject(clientJoinedMessage));
|
||||||
|
|
||||||
|
var bridge = Controller.GetRoomBridge(RoomKey);
|
||||||
|
|
||||||
|
if (bridge == null) return;
|
||||||
|
|
||||||
|
SendUserCodeToClient(bridge, clientId);
|
||||||
|
|
||||||
|
bridge.UserCodeChanged -= Bridge_UserCodeChanged;
|
||||||
|
bridge.UserCodeChanged += Bridge_UserCodeChanged;
|
||||||
|
|
||||||
|
// TODO: Future: Check token to see if there's already an open session using that token and reject/close the session
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Bridge_UserCodeChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
SendUserCodeToClient((MobileControlEssentialsRoomBridge)sender, _clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendUserCodeToClient(MobileControlBridgeBase bridge, string clientId)
|
||||||
|
{
|
||||||
|
var content = new
|
||||||
|
{
|
||||||
|
userCode = bridge.UserCode,
|
||||||
|
qrUrl = bridge.QrCodeUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
var message = new MobileControlMessage
|
||||||
|
{
|
||||||
|
Type = "/system/userCodeChanged",
|
||||||
|
ClientId = clientId,
|
||||||
|
Content = JToken.FromObject(content)
|
||||||
|
};
|
||||||
|
|
||||||
|
Controller.SendMessageObjectToDirectClient(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMessage(MessageEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMessage(e);
|
||||||
|
|
||||||
|
if (e.IsText && e.Data.Length > 0 && Controller != null)
|
||||||
|
{
|
||||||
|
// Forward the message to the controller to be put on the receive queue
|
||||||
|
Controller.HandleClientMessage(e.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClose(CloseEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnClose(e);
|
||||||
|
|
||||||
|
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Closing: {0} reason: {1}", null, e.Code, e.Reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnError(ErrorEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnError(e);
|
||||||
|
|
||||||
|
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Error: {exception} message: {message}", e.Exception, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class MobileControlWebsocketServer : EssentialsDevice
|
public class MobileControlWebsocketServer : EssentialsDevice
|
||||||
{
|
{
|
||||||
private readonly string userAppPath = Global.FilePathPrefix + "mcUserApp" + Global.DirectorySeparator;
|
private readonly string userAppPath = Global.FilePathPrefix + "mcUserApp" + Global.DirectorySeparator;
|
||||||
@@ -29,7 +163,6 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
private readonly string localConfigFolderName = "_local-config";
|
private readonly string localConfigFolderName = "_local-config";
|
||||||
|
|
||||||
private readonly string appConfigFileName = "_config.local.json";
|
private readonly string appConfigFileName = "_config.local.json";
|
||||||
private readonly string appConfigCsFileName = "_config.cs.json";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Where the key is the join token and the value is the room key
|
/// Where the key is the join token and the value is the room key
|
||||||
@@ -58,12 +191,6 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string lanIpAddress => CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter));
|
|
||||||
|
|
||||||
private System.Net.IPAddress csIpAddress;
|
|
||||||
|
|
||||||
private System.Net.IPAddress csSubnetMask;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path for the WebSocket messaging
|
/// The path for the WebSocket messaging
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -82,7 +209,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
private string _userAppBaseHref = "/mc/app";
|
private string _userAppBaseHref = "/mc/app";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The port the server will run on
|
/// The prot the server will run on
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Port { get; private set; }
|
public int Port { get; private set; }
|
||||||
|
|
||||||
@@ -133,6 +260,9 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
|
||||||
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, "Automatically forwarding port {0} to CS LAN", Port);
|
Debug.LogMessage(LogEventLevel.Information, "Automatically forwarding port {0} to CS LAN", Port);
|
||||||
|
|
||||||
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
|
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
|
||||||
@@ -155,23 +285,6 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
|
|
||||||
var csSubnetMask = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_MASK, csAdapterId);
|
|
||||||
var csIpAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
|
||||||
|
|
||||||
this.csSubnetMask = System.Net.IPAddress.Parse(csSubnetMask);
|
|
||||||
this.csIpAddress = System.Net.IPAddress.Parse(csIpAddress);
|
|
||||||
}
|
|
||||||
catch (ArgumentException)
|
|
||||||
{
|
|
||||||
if (parent.Config.DirectServer.AutomaticallyForwardPortToCSLAN == false)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Information, "This processor does not have a CS LAN", this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
UiClients = new Dictionary<string, UiClientContext>();
|
UiClients = new Dictionary<string, UiClientContext>();
|
||||||
|
|
||||||
@@ -298,6 +411,8 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
|
|
||||||
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId);
|
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId);
|
||||||
|
|
||||||
|
this.LogVerbose("Processor IP: {processorIp}", processorIp);
|
||||||
|
|
||||||
foreach (var touchpanel in touchpanels.Select(tp =>
|
foreach (var touchpanel in touchpanels.Select(tp =>
|
||||||
{
|
{
|
||||||
var token = _secret.Tokens.FirstOrDefault((t) => t.Value.TouchpanelKey.Equals(tp.Key, StringComparison.InvariantCultureIgnoreCase));
|
var token = _secret.Tokens.FirstOrDefault((t) => t.Value.TouchpanelKey.Equals(tp.Key, StringComparison.InvariantCultureIgnoreCase));
|
||||||
@@ -319,25 +434,11 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
string ip = processorIp;
|
var appUrl = $"http://{processorIp}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}";
|
||||||
if (touchpanel.Touchpanel is IMobileControlCrestronTouchpanelController crestronTouchpanel)
|
|
||||||
{
|
|
||||||
ip = crestronTouchpanel.ConnectedIps.Any(ipInfo =>
|
|
||||||
{
|
|
||||||
if (System.Net.IPAddress.TryParse(ipInfo.DeviceIpAddress, out var parsedIp))
|
|
||||||
{
|
|
||||||
return csIpAddress.IsInSameSubnet(parsedIp, csSubnetMask);
|
|
||||||
}
|
|
||||||
this.LogWarning("Invalid IP address: {deviceIpAddress}", ipInfo.DeviceIpAddress);
|
|
||||||
return false;
|
|
||||||
}) ? csIpAddress.ToString() : processorIp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var appUrl = $"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}";
|
|
||||||
|
|
||||||
this.LogVerbose("Sending URL {appUrl}", appUrl);
|
this.LogVerbose("Sending URL {appUrl}", appUrl);
|
||||||
|
|
||||||
touchpanel.Messenger.UpdateAppUrl($"http://{ip}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}");
|
touchpanel.Messenger.UpdateAppUrl($"http://{processorIp}:{_parent.Config.DirectServer.Port}/mc/app?token={touchpanel.Key}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,45 +466,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
|
|
||||||
using (var sw = new StreamWriter(File.Open($"{userAppPath}{localConfigFolderName}{Global.DirectorySeparator}{appConfigFileName}", FileMode.Create, FileAccess.ReadWrite)))
|
using (var sw = new StreamWriter(File.Open($"{userAppPath}{localConfigFolderName}{Global.DirectorySeparator}{appConfigFileName}", FileMode.Create, FileAccess.ReadWrite)))
|
||||||
{
|
{
|
||||||
// Write the LAN application configuration file. Used when a request comes in for the application config from the LAN
|
var config = GetApplicationConfig();
|
||||||
var lanAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter);
|
|
||||||
|
|
||||||
this.LogDebug("LAN Adapter ID: {lanAdapterId}", lanAdapterId);
|
|
||||||
|
|
||||||
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId);
|
|
||||||
|
|
||||||
var config = GetApplicationConfig(processorIp);
|
|
||||||
|
|
||||||
var contents = JsonConvert.SerializeObject(config, Formatting.Indented);
|
|
||||||
|
|
||||||
sw.Write(contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
short csAdapterId;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
csAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetCSAdapter);
|
|
||||||
}
|
|
||||||
catch (ArgumentException)
|
|
||||||
{
|
|
||||||
this.LogDebug("This processor does not have a CS LAN");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (csAdapterId == -1)
|
|
||||||
{
|
|
||||||
this.LogDebug("CS LAN Adapter not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.LogDebug("CS LAN Adapter ID: {csAdapterId}. Adding CS Config", csAdapterId);
|
|
||||||
|
|
||||||
using (var sw = new StreamWriter(File.Open($"{userAppPath}{localConfigFolderName}{Global.DirectorySeparator}{appConfigCsFileName}", FileMode.Create, FileAccess.ReadWrite)))
|
|
||||||
{
|
|
||||||
// Write the CS application configuration file. Used when a request comes in for the application config from the CS
|
|
||||||
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, csAdapterId);
|
|
||||||
|
|
||||||
var config = GetApplicationConfig(processorIp);
|
|
||||||
|
|
||||||
var contents = JsonConvert.SerializeObject(config, Formatting.Indented);
|
var contents = JsonConvert.SerializeObject(config, Formatting.Indented);
|
||||||
|
|
||||||
@@ -411,41 +474,74 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MobileControlApplicationConfig GetApplicationConfig(string processorIp)
|
private MobileControlApplicationConfig GetApplicationConfig()
|
||||||
{
|
{
|
||||||
|
MobileControlApplicationConfig config = null;
|
||||||
|
|
||||||
|
var lanAdapterId = CrestronEthernetHelper.GetAdapterdIdForSpecifiedAdapterType(EthernetAdapterType.EthernetLANAdapter);
|
||||||
|
|
||||||
|
var processorIp = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, lanAdapterId);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var config = new MobileControlApplicationConfig
|
if (_parent.Config.ApplicationConfig == null)
|
||||||
{
|
{
|
||||||
ApiPath = string.Format("http://{0}:{1}/mc/api", processorIp, _parent.Config.DirectServer.Port),
|
config = new MobileControlApplicationConfig
|
||||||
GatewayAppPath = "",
|
|
||||||
LogoPath = _parent.Config.ApplicationConfig?.LogoPath ?? "logo/logo.png",
|
|
||||||
EnableDev = _parent.Config.ApplicationConfig?.EnableDev ?? false,
|
|
||||||
IconSet = _parent.Config.ApplicationConfig?.IconSet ?? MCIconSet.GOOGLE,
|
|
||||||
LoginMode = _parent.Config.ApplicationConfig?.LoginMode ?? "room-list",
|
|
||||||
Modes = _parent.Config.ApplicationConfig?.Modes ?? new Dictionary<string, McMode>
|
|
||||||
{
|
{
|
||||||
|
ApiPath = string.Format("http://{0}:{1}/mc/api", processorIp, _parent.Config.DirectServer.Port),
|
||||||
|
GatewayAppPath = "",
|
||||||
|
LogoPath = "logo/logo.png",
|
||||||
|
EnableDev = false,
|
||||||
|
IconSet = MCIconSet.GOOGLE,
|
||||||
|
LoginMode = "room-list",
|
||||||
|
Modes = new Dictionary<string, McMode>
|
||||||
{
|
{
|
||||||
"room-list",
|
{
|
||||||
new McMode {
|
"room-list",
|
||||||
ListPageText = "Please select your room",
|
new McMode{
|
||||||
LoginHelpText = "Please select your room from the list, then enter the code shown on the display.",
|
ListPageText= "Please select your room",
|
||||||
PasscodePageText = "Please enter the code shown on this room's display"
|
LoginHelpText = "Please select your room from the list, then enter the code shown on the display.",
|
||||||
|
PasscodePageText = "Please enter the code shown on this room's display"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
Logging = _parent.Config.DirectServer.Logging.EnableRemoteLogging,
|
||||||
Logging = _parent.Config.ApplicationConfig?.Logging ?? false,
|
};
|
||||||
PartnerMetadata = _parent.Config.ApplicationConfig?.PartnerMetadata ?? new List<MobileControlPartnerMetadata>()
|
}
|
||||||
};
|
else
|
||||||
|
{
|
||||||
return config;
|
config = new MobileControlApplicationConfig
|
||||||
|
{
|
||||||
|
ApiPath = string.Format("http://{0}:{1}/mc/api", processorIp, _parent.Config.DirectServer.Port),
|
||||||
|
GatewayAppPath = "",
|
||||||
|
LogoPath = _parent.Config.ApplicationConfig.LogoPath ?? "logo/logo.png",
|
||||||
|
EnableDev = _parent.Config.ApplicationConfig.EnableDev ?? false,
|
||||||
|
IconSet = _parent.Config.ApplicationConfig.IconSet ?? MCIconSet.GOOGLE,
|
||||||
|
LoginMode = _parent.Config.ApplicationConfig.LoginMode ?? "room-list",
|
||||||
|
Modes = _parent.Config.ApplicationConfig.Modes ?? new Dictionary<string, McMode>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"room-list",
|
||||||
|
new McMode {
|
||||||
|
ListPageText = "Please select your room",
|
||||||
|
LoginHelpText = "Please select your room from the list, then enter the code shown on the display.",
|
||||||
|
PasscodePageText = "Please enter the code shown on this room's display"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Logging = _parent.Config.ApplicationConfig.Logging,
|
||||||
|
PartnerMetadata = _parent.Config.ApplicationConfig.PartnerMetadata ?? new List<MobileControlPartnerMetadata>()
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.LogError(ex, "Error getting application configuration");
|
Debug.LogMessage(ex, "Error getting application configuration", this);
|
||||||
|
|
||||||
return null;
|
Debug.LogMessage(LogEventLevel.Verbose, "Config Object: {config} from {parentConfig}", this, config, _parent.Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -584,7 +680,7 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
|
|
||||||
var values = s.Split(' ');
|
var values = s.Split(' ');
|
||||||
|
|
||||||
if (values.Length < 2)
|
if(values.Length < 2)
|
||||||
{
|
{
|
||||||
CrestronConsole.ConsoleCommandResponse("Invalid number of arguments. Please provide a room key and a grant code");
|
CrestronConsole.ConsoleCommandResponse("Invalid number of arguments. Please provide a room key and a grant code");
|
||||||
return;
|
return;
|
||||||
@@ -1111,14 +1207,6 @@ namespace PepperDash.Essentials.WebSocketServer
|
|||||||
|
|
||||||
this.LogVerbose("Attempting to serve file: {filePath}", filePath);
|
this.LogVerbose("Attempting to serve file: {filePath}", filePath);
|
||||||
|
|
||||||
var remoteIp = req.RemoteEndPoint.Address;
|
|
||||||
|
|
||||||
// Check if the request is coming from the CS LAN and if so, send the CS config instead of the LAN config
|
|
||||||
if (csSubnetMask != null && csIpAddress != null && remoteIp.IsInSameSubnet(csIpAddress, csSubnetMask) && filePath.Contains(appConfigFileName))
|
|
||||||
{
|
|
||||||
filePath = filePath.Replace(appConfigFileName, appConfigCsFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] contents;
|
byte[] contents;
|
||||||
if (File.Exists(filePath))
|
if (File.Exists(filePath))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using PepperDash.Core;
|
|
||||||
using PepperDash.Essentials.AppServer.Messengers;
|
|
||||||
using PepperDash.Essentials.RoomBridges;
|
|
||||||
using Serilog.Events;
|
|
||||||
using System;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using WebSocketSharp;
|
|
||||||
using WebSocketSharp.Server;
|
|
||||||
using ErrorEventArgs = WebSocketSharp.ErrorEventArgs;
|
|
||||||
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.WebSocketServer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the behaviour to associate with a UiClient for WebSocket communication
|
|
||||||
/// </summary>
|
|
||||||
public class UiClient : WebSocketBehavior
|
|
||||||
{
|
|
||||||
public MobileControlSystemController Controller { get; set; }
|
|
||||||
|
|
||||||
public string RoomKey { get; set; }
|
|
||||||
|
|
||||||
private string _clientId;
|
|
||||||
|
|
||||||
private DateTime _connectionTime;
|
|
||||||
|
|
||||||
public TimeSpan ConnectedDuration
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Context.WebSocket.IsAlive)
|
|
||||||
{
|
|
||||||
return DateTime.Now - _connectionTime;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new TimeSpan(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UiClient()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnOpen()
|
|
||||||
{
|
|
||||||
base.OnOpen();
|
|
||||||
|
|
||||||
var url = Context.WebSocket.Url;
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "New WebSocket Connection from: {0}", null, url);
|
|
||||||
|
|
||||||
var match = Regex.Match(url.AbsoluteUri, "(?:ws|wss):\\/\\/.*(?:\\/mc\\/api\\/ui\\/join\\/)(.*)");
|
|
||||||
|
|
||||||
if (!match.Success)
|
|
||||||
{
|
|
||||||
_connectionTime = DateTime.Now;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var clientId = match.Groups[1].Value;
|
|
||||||
_clientId = clientId;
|
|
||||||
|
|
||||||
if (Controller == null)
|
|
||||||
{
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Controller is null");
|
|
||||||
_connectionTime = DateTime.Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
var clientJoinedMessage = new MobileControlMessage
|
|
||||||
{
|
|
||||||
Type = "/system/clientJoined",
|
|
||||||
Content = JToken.FromObject(new
|
|
||||||
{
|
|
||||||
clientId,
|
|
||||||
roomKey = RoomKey,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
Controller.HandleClientMessage(JsonConvert.SerializeObject(clientJoinedMessage));
|
|
||||||
|
|
||||||
var bridge = Controller.GetRoomBridge(RoomKey);
|
|
||||||
|
|
||||||
if (bridge == null) return;
|
|
||||||
|
|
||||||
SendUserCodeToClient(bridge, clientId);
|
|
||||||
|
|
||||||
bridge.UserCodeChanged -= Bridge_UserCodeChanged;
|
|
||||||
bridge.UserCodeChanged += Bridge_UserCodeChanged;
|
|
||||||
|
|
||||||
// TODO: Future: Check token to see if there's already an open session using that token and reject/close the session
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Bridge_UserCodeChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
SendUserCodeToClient((MobileControlEssentialsRoomBridge)sender, _clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendUserCodeToClient(MobileControlBridgeBase bridge, string clientId)
|
|
||||||
{
|
|
||||||
var content = new
|
|
||||||
{
|
|
||||||
userCode = bridge.UserCode,
|
|
||||||
qrUrl = bridge.QrCodeUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
var message = new MobileControlMessage
|
|
||||||
{
|
|
||||||
Type = "/system/userCodeChanged",
|
|
||||||
ClientId = clientId,
|
|
||||||
Content = JToken.FromObject(content)
|
|
||||||
};
|
|
||||||
|
|
||||||
Controller.SendMessageObjectToDirectClient(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnMessage(MessageEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnMessage(e);
|
|
||||||
|
|
||||||
if (e.IsText && e.Data.Length > 0 && Controller != null)
|
|
||||||
{
|
|
||||||
// Forward the message to the controller to be put on the receive queue
|
|
||||||
Controller.HandleClientMessage(e.Data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnClose(CloseEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnClose(e);
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Closing: {0} reason: {1}", null, e.Code, e.Reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnError(ErrorEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnError(e);
|
|
||||||
|
|
||||||
Debug.LogMessage(LogEventLevel.Verbose, "WebSocket UiClient Error: {exception} message: {message}", e.Exception, e.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Tests.Abstractions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Abstraction for logging operations to enable testing
|
|
||||||
/// </summary>
|
|
||||||
public interface ILogger
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Logs a debug message
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">Source of the log message</param>
|
|
||||||
/// <param name="message">Message to log</param>
|
|
||||||
/// <param name="args">Format arguments</param>
|
|
||||||
void LogDebug(object source, string message, params object[] args);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Logs a verbose message
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">Source of the log message</param>
|
|
||||||
/// <param name="message">Message to log</param>
|
|
||||||
/// <param name="args">Format arguments</param>
|
|
||||||
void LogVerbose(object source, string message, params object[] args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Tests.Abstractions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Abstraction for queue operations to enable testing
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of items in the queue</typeparam>
|
|
||||||
public interface IQueue<T>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Number of items in the queue
|
|
||||||
/// </summary>
|
|
||||||
int Count { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds an item to the queue
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">Item to add</param>
|
|
||||||
void Enqueue(T item);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes and returns the next item from the queue
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The next item, or default(T) if queue is empty</returns>
|
|
||||||
T Dequeue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Tests.Abstractions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Abstraction for thread operations to enable testing
|
|
||||||
/// </summary>
|
|
||||||
public interface IThreadService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Sleeps the current thread for the specified milliseconds
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="milliseconds">Time to sleep in milliseconds</param>
|
|
||||||
void Sleep(int milliseconds);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates and starts a new thread
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="threadFunction">The function to execute in the thread</param>
|
|
||||||
/// <param name="parameter">Parameter to pass to the thread function</param>
|
|
||||||
/// <returns>Thread identifier or handle</returns>
|
|
||||||
object CreateAndStartThread(Func<object, object> threadFunction, object parameter);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Aborts the specified thread
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="thread">The thread to abort</param>
|
|
||||||
void AbortThread(object thread);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the thread is currently running
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="thread">The thread to check</param>
|
|
||||||
/// <returns>True if the thread is running, false otherwise</returns>
|
|
||||||
bool IsThreadRunning(object thread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
global using Xunit;
|
|
||||||
global using Moq;
|
|
||||||
global using System;
|
|
||||||
global using System.Collections.Generic;
|
|
||||||
global using System.Linq;
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
<IsTestProject>true</IsTestProject>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
|
||||||
<PackageReference Include="xunit" Version="2.6.2" />
|
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Moq" Version="4.20.69" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using PepperDash.Essentials.Core.Tests.Abstractions;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Tests.TestableComponents
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A simplified, testable action sequence that demonstrates abstraction patterns
|
|
||||||
/// This shows how we can separate business logic from SDK dependencies
|
|
||||||
/// </summary>
|
|
||||||
public class TestableActionSequence
|
|
||||||
{
|
|
||||||
private readonly IQueue<TestableSequencedAction> _actionQueue;
|
|
||||||
private readonly IThreadService _threadService;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly List<TestableSequencedAction> _configuredActions;
|
|
||||||
|
|
||||||
private object _workerThread;
|
|
||||||
private bool _allowActionsToExecute;
|
|
||||||
|
|
||||||
public TestableActionSequence(
|
|
||||||
IQueue<TestableSequencedAction> actionQueue,
|
|
||||||
IThreadService threadService,
|
|
||||||
ILogger logger,
|
|
||||||
List<TestableSequencedAction> actions)
|
|
||||||
{
|
|
||||||
_actionQueue = actionQueue ?? throw new ArgumentNullException(nameof(actionQueue));
|
|
||||||
_threadService = threadService ?? throw new ArgumentNullException(nameof(threadService));
|
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
||||||
_configuredActions = actions ?? new List<TestableSequencedAction>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Starts executing the sequenced actions
|
|
||||||
/// </summary>
|
|
||||||
public void StartSequence()
|
|
||||||
{
|
|
||||||
if (_workerThread != null && _threadService.IsThreadRunning(_workerThread))
|
|
||||||
{
|
|
||||||
_logger.LogDebug(this, "Thread already running. Cannot Start Sequence");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogDebug(this, "Starting Action Sequence");
|
|
||||||
_allowActionsToExecute = true;
|
|
||||||
AddActionsToQueue();
|
|
||||||
_workerThread = _threadService.CreateAndStartThread(ProcessActions, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops executing the sequenced actions
|
|
||||||
/// </summary>
|
|
||||||
public void StopSequence()
|
|
||||||
{
|
|
||||||
_logger.LogDebug(this, "Stopping Action Sequence");
|
|
||||||
_allowActionsToExecute = false;
|
|
||||||
if (_workerThread != null)
|
|
||||||
{
|
|
||||||
_threadService.AbortThread(_workerThread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the current status of the sequence
|
|
||||||
/// </summary>
|
|
||||||
public bool IsRunning => _allowActionsToExecute && _threadService.IsThreadRunning(_workerThread);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the number of pending actions
|
|
||||||
/// </summary>
|
|
||||||
public int PendingActionsCount => _actionQueue.Count;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Populates the queue from the configuration information
|
|
||||||
/// </summary>
|
|
||||||
private void AddActionsToQueue()
|
|
||||||
{
|
|
||||||
_logger.LogDebug(this, "Adding {0} actions to queue", _configuredActions.Count);
|
|
||||||
|
|
||||||
foreach (var action in _configuredActions)
|
|
||||||
{
|
|
||||||
_actionQueue.Enqueue(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private object ProcessActions(object obj)
|
|
||||||
{
|
|
||||||
while (_allowActionsToExecute && _actionQueue.Count > 0)
|
|
||||||
{
|
|
||||||
var action = _actionQueue.Dequeue();
|
|
||||||
if (action == null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Delay before executing
|
|
||||||
if (action.DelayMs > 0)
|
|
||||||
_threadService.Sleep(action.DelayMs);
|
|
||||||
|
|
||||||
ExecuteAction(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecuteAction(TestableSequencedAction action)
|
|
||||||
{
|
|
||||||
if (action == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogDebug(this, "Executing action: {0} with delay: {1}ms", action.Name, action.DelayMs);
|
|
||||||
action.Execute();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogVerbose(this, "Error Executing Action: {0}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A testable action that can be sequenced
|
|
||||||
/// </summary>
|
|
||||||
public class TestableSequencedAction
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public int DelayMs { get; set; }
|
|
||||||
public Action ActionToExecute { get; set; }
|
|
||||||
|
|
||||||
public TestableSequencedAction(string name, int delayMs = 0, Action actionToExecute = null)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
DelayMs = delayMs;
|
|
||||||
ActionToExecute = actionToExecute ?? (() => { });
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Execute()
|
|
||||||
{
|
|
||||||
ActionToExecute?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
using PepperDash.Essentials.Core.Tests.Abstractions;
|
|
||||||
using PepperDash.Essentials.Core.Tests.TestableComponents;
|
|
||||||
|
|
||||||
namespace PepperDash.Essentials.Core.Tests.Unit
|
|
||||||
{
|
|
||||||
public class TestableActionSequenceTests
|
|
||||||
{
|
|
||||||
private readonly Mock<IQueue<TestableSequencedAction>> _mockQueue;
|
|
||||||
private readonly Mock<IThreadService> _mockThreadService;
|
|
||||||
private readonly Mock<ILogger> _mockLogger;
|
|
||||||
private readonly TestableActionSequence _actionSequence;
|
|
||||||
private readonly List<TestableSequencedAction> _testActions;
|
|
||||||
|
|
||||||
public TestableActionSequenceTests()
|
|
||||||
{
|
|
||||||
_mockQueue = new Mock<IQueue<TestableSequencedAction>>();
|
|
||||||
_mockThreadService = new Mock<IThreadService>();
|
|
||||||
_mockLogger = new Mock<ILogger>();
|
|
||||||
|
|
||||||
_testActions = new List<TestableSequencedAction>
|
|
||||||
{
|
|
||||||
new TestableSequencedAction("Action1", 100),
|
|
||||||
new TestableSequencedAction("Action2", 200)
|
|
||||||
};
|
|
||||||
|
|
||||||
_actionSequence = new TestableActionSequence(
|
|
||||||
_mockQueue.Object,
|
|
||||||
_mockThreadService.Object,
|
|
||||||
_mockLogger.Object,
|
|
||||||
_testActions);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Constructor_WithNullQueue_ThrowsArgumentNullException()
|
|
||||||
{
|
|
||||||
// Act & Assert
|
|
||||||
Assert.Throws<ArgumentNullException>(() =>
|
|
||||||
new TestableActionSequence(null, _mockThreadService.Object, _mockLogger.Object, _testActions));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Constructor_WithNullThreadService_ThrowsArgumentNullException()
|
|
||||||
{
|
|
||||||
// Act & Assert
|
|
||||||
Assert.Throws<ArgumentNullException>(() =>
|
|
||||||
new TestableActionSequence(_mockQueue.Object, null, _mockLogger.Object, _testActions));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Constructor_WithNullLogger_ThrowsArgumentNullException()
|
|
||||||
{
|
|
||||||
// Act & Assert
|
|
||||||
Assert.Throws<ArgumentNullException>(() =>
|
|
||||||
new TestableActionSequence(_mockQueue.Object, _mockThreadService.Object, null, _testActions));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void StartSequence_WhenNoThreadRunning_StartsNewThread()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
_mockThreadService.Setup(x => x.IsThreadRunning(It.IsAny<object>())).Returns(false);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
_actionSequence.StartSequence();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
_mockLogger.Verify(x => x.LogDebug(_actionSequence, "Starting Action Sequence"), Times.Once);
|
|
||||||
_mockThreadService.Verify(x => x.CreateAndStartThread(It.IsAny<Func<object, object>>(), null), Times.Once);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void StartSequence_WhenThreadAlreadyRunning_DoesNotStartNewThread()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var mockThread = new object();
|
|
||||||
_mockThreadService.Setup(x => x.CreateAndStartThread(It.IsAny<Func<object, object>>(), null))
|
|
||||||
.Returns(mockThread);
|
|
||||||
_mockThreadService.Setup(x => x.IsThreadRunning(mockThread)).Returns(true);
|
|
||||||
|
|
||||||
// First call to set up the worker thread
|
|
||||||
_actionSequence.StartSequence();
|
|
||||||
|
|
||||||
// Reset invocations to verify only the second call behavior
|
|
||||||
_mockLogger.Invocations.Clear();
|
|
||||||
_mockThreadService.Invocations.Clear();
|
|
||||||
|
|
||||||
// Act - Second call should detect running thread
|
|
||||||
_actionSequence.StartSequence();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
_mockLogger.Verify(x => x.LogDebug(_actionSequence, "Thread already running. Cannot Start Sequence"), Times.Once);
|
|
||||||
_mockThreadService.Verify(x => x.CreateAndStartThread(It.IsAny<Func<object, object>>(), null), Times.Never);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void StartSequence_AddsConfiguredActionsToQueue()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
_mockThreadService.Setup(x => x.IsThreadRunning(It.IsAny<object>())).Returns(false);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
_actionSequence.StartSequence();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
_mockLogger.Verify(x => x.LogDebug(_actionSequence, "Adding {0} actions to queue", 2), Times.Once);
|
|
||||||
_mockQueue.Verify(x => x.Enqueue(It.IsAny<TestableSequencedAction>()), Times.Exactly(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void StopSequence_SetsAllowActionsToFalseAndAbortsThread()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var mockThread = new object();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
_actionSequence.StopSequence();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
_mockLogger.Verify(x => x.LogDebug(_actionSequence, "Stopping Action Sequence"), Times.Once);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void PendingActionsCount_ReturnsQueueCount()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
_mockQueue.Setup(x => x.Count).Returns(5);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var count = _actionSequence.PendingActionsCount;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(5, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TestableSequencedAction_ExecutesActionWhenCalled()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
bool actionExecuted = false;
|
|
||||||
var action = new TestableSequencedAction("TestAction", 0, () => actionExecuted = true);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
action.Execute();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(actionExecuted);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void TestableSequencedAction_WithNullAction_DoesNotThrow()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var action = new TestableSequencedAction("TestAction", 0, null);
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
var exception = Record.Exception(() => action.Execute());
|
|
||||||
Assert.Null(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user