Compare commits

...

9 Commits

19 changed files with 851 additions and 241 deletions

View File

@@ -1,94 +0,0 @@
name: Branch Build Using Docker
on:
push:
branches:
- feature-2.0.0/*
- hotfix-2.0.0/*
- release-2.0.0/*
- development-2.0.0
env:
# solution path doesn't need slashes unless there it is multiple folders deep
# solution name does not include extension. .sln is assumed
SOLUTION_PATH: .
SOLUTION_FILE: PepperDash.Essentials
# Do not edit this, we're just creating it here
VERSION: 0.0.0-buildtype-buildnumber
# Defaults to debug for build type
BUILD_TYPE: Debug
# Defaults to main as the release branch. Change as necessary
RELEASE_BRANCH: main
jobs:
Build_Project_4-Series:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Set Version Number
id: setVersion
shell: powershell
run: |
$latestVersion = [version]"2.0.0"
$newVersion = [version]$latestVersion
$phase = ""
$newVersionString = ""
switch -regex ($Env:GITHUB_REF) {
'^refs\/pull\/*.' {
$phase = 'beta';
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
}
'^refs\/heads\/hotfix-2.0.0\/*.' {
$phase = 'hotfix'
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
}
'^refs\/heads\/release-2.0.0\/*.' {
$splitRef = $Env:GITHUB_REF -split "/"
$version = [version]($splitRef[-1] -replace "v", "")
$phase = 'rc'
$newVersionString = "{0}-{1}-{2}" -f $version, $phase, $Env:GITHUB_RUN_NUMBER
}
'^refs\/heads\/feature-2.0.0\/*.' {
$phase = 'alpha'
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
}
'development-2.0.0' {
$phase = 'beta'
$newVersionString = "{0}-{1}-{2}" -f $newVersion, $phase, $Env:GITHUB_RUN_NUMBER
}
}
echo "version=$newVersionString" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
- name: Setup MS Build
uses: microsoft/setup-msbuild@v1.1
- name: restore Nuget Packages
run: nuget restore .\$($Env:SOLUTION_FILE).sln
# Build the solutions in the docker image
- name: Build Solution
run: msbuild .\$($Env:SOLUTION_FILE).sln /p:Platform="Any CPU" /p:Configuration="Debug" /p:Version="${{ steps.setVersion.outputs.version }}" -m
- name: Pack Solution
run: dotnet pack .\$($Env:SOLUTION_FILE).sln --configuration $env:BUILD_TYPE --output ./output /p:Version="${{ steps.setVersion.outputs.version }}"
- name: Create tag for non-rc builds
if: ${{ !contains(steps.setVersion.outputs.version, 'rc') }}
run: |
git tag ${{ steps.setVersion.outputs.version }}
git push --tags origin
# Create the release on the source repo
- name: Create Release
id: create_release
uses: ncipollo/release-action@v1
with:
artifacts: 'output\**\*.*(cpz|cplz)'
generateReleaseNotes: true
prerelease: ${{contains('debug', env.BUILD_TYPE)}}
tag: ${{ steps.setVersion.outputs.version }}
- name: Setup Nuget
run: |
nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username pepperdash -password ${{ secrets.GITHUB_TOKEN }}
nuget setApiKey ${{ secrets.GITHUB_TOKEN }} -Source github
nuget setApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json
- name: Publish to Nuget
run: nuget push .\output\*.nupkg -Source https://api.nuget.org/v3/index.json
- name: Publish to Github Nuget
run: nuget push .\output\*.nupkg -Source github

View File

@@ -0,0 +1,247 @@
name: Essentials v3 Development Build
on:
push:
branches:
- feature-3.0.0/*
- hotfix-3.0.0/*
- release-3.0.0/*
- development-3.0.0
env:
SOLUTION_PATH: .
SOLUTION_FILE: PepperDash.Essentials
VERSION: 0.0.0-buildtype-buildnumber
BUILD_TYPE: Debug
RELEASE_BRANCH: main
jobs:
Build_Project_4-Series:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Detect environment (Act vs GitHub)
- name: Detect environment
id: detect_env
run: |
if [ -n "$ACT" ]; then
echo "is_local=true" >> $GITHUB_OUTPUT
else
echo "is_local=false" >> $GITHUB_OUTPUT
fi
- name: Install prerequisites
run: |
if [ "${{ steps.detect_env.outputs.is_local }}" == "true" ]; then
# For Act - no sudo needed
apt-get update
apt-get install -y curl wget libicu-dev git unzip
else
# For GitHub runners - sudo required
sudo apt-get update
sudo apt-get install -y curl wget libicu-dev git unzip
fi
- name: Set Version Number
id: setVersion
shell: bash
run: |
latestVersion="3.0.0"
newVersion=$latestVersion
phase=""
newVersionString=""
if [[ $GITHUB_REF =~ ^refs/pull/.* ]]; then
phase="beta"
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
elif [[ $GITHUB_REF =~ ^refs/heads/hotfix-3.0.0/.* ]]; then
phase="hotfix"
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
elif [[ $GITHUB_REF =~ ^refs/heads/feature-3.0.0/.* ]]; then
phase="alpha"
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
elif [[ $GITHUB_REF == "refs/heads/development-3.0.0" ]]; then
phase="beta"
newVersionString="${newVersion}-${phase}-${GITHUB_RUN_NUMBER}"
elif [[ $GITHUB_REF =~ ^refs/heads/release-3.0.0/.* ]]; then
version=$(echo $GITHUB_REF | awk -F '/' '{print $NF}' | sed 's/v//')
phase="rc"
newVersionString="${version}-${phase}-${GITHUB_RUN_NUMBER}"
else
# For local builds or unrecognized branches
newVersionString="${newVersion}-local"
fi
echo "version=$newVersionString" >> $GITHUB_OUTPUT
# Create Build Properties file
- name: Create Build Properties
run: |
cat > Directory.Build.props << EOF
<Project>
<PropertyGroup>
<Version>${{ steps.setVersion.outputs.version }}</Version>
<AssemblyVersion>${{ steps.setVersion.outputs.version }}</AssemblyVersion>
<FileVersion>${{ steps.setVersion.outputs.version }}</FileVersion>
<InformationalVersion>${{ steps.setVersion.outputs.version }}</InformationalVersion>
<PackageVersion>${{ steps.setVersion.outputs.version }}</PackageVersion>
<NuGetVersion>${{ steps.setVersion.outputs.version }}</NuGetVersion>
</PropertyGroup>
</Project>
EOF
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Restore NuGet Packages
run: dotnet restore ${SOLUTION_FILE}.sln
- name: Build Solution
run: dotnet build ${SOLUTION_FILE}.sln --configuration ${BUILD_TYPE} --no-restore
# Copy the CPZ file to the output directory with version in the filename
- name: Copy and Rename CPZ Files
run: |
mkdir -p ./output/cpz
# Find the main CPZ file in the build output
if [ -f "./src/PepperDash.Essentials/bin/${BUILD_TYPE}/net8/PepperDashEssentials.cpz" ]; then
cp "./src/PepperDash.Essentials/bin/${BUILD_TYPE}/net8/PepperDashEssentials.cpz" "./output/cpz/PepperDashEssentials.${{ steps.setVersion.outputs.version }}.cpz"
echo "Main CPZ file copied and renamed successfully."
else
echo "Warning: Main CPZ file not found at expected location."
find ./src -name "*.cpz" | xargs -I {} cp {} ./output/cpz/
fi
- name: Pack Solution
run: dotnet pack ${SOLUTION_FILE}.sln --configuration ${BUILD_TYPE} --output ./output/nuget --no-build
# List build artifacts (runs in both environments)
- name: List Build Artifacts
run: |
echo "=== Build Artifacts ==="
echo "NuGet Packages:"
find ./output/nuget -type f | sort
echo ""
echo "CPZ/CPLZ Files:"
find ./output -name "*.cpz" -o -name "*.cplz" | sort
echo "======================="
# Enhanced package inspection for local runs
- name: Inspect NuGet Packages
if: steps.detect_env.outputs.is_local == 'true'
run: |
echo "=== NuGet Package Details ==="
for pkg in $(find ./output/nuget -name "*.nupkg"); do
echo "Package: $(basename "$pkg")"
echo "Size: $(du -h "$pkg" | cut -f1)"
# Extract and show package contents
echo "Contents:"
unzip -l "$pkg" | tail -n +4 | head -n -2
echo "--------------------------"
# Try to extract and show the nuspec file (contains metadata)
echo "Metadata:"
unzip -p "$pkg" "*.nuspec" 2>/dev/null | grep -E "(<id>|<version>|<description>|<authors>|<dependencies>)" || echo "Metadata extraction failed"
echo "--------------------------"
done
echo "==========================="
# Tag creation - GitHub version
- name: Create tag for non-rc builds (GitHub)
if: ${{ !contains(steps.setVersion.outputs.version, 'rc') && steps.detect_env.outputs.is_local == 'false' }}
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
git tag ${{ steps.setVersion.outputs.version }}
git push --tags origin
# Tag creation - Act mock version
- name: Create tag for non-rc builds (Act Mock)
if: ${{ !contains(steps.setVersion.outputs.version, 'rc') && steps.detect_env.outputs.is_local == 'true' }}
run: |
echo "Would create git tag: ${{ steps.setVersion.outputs.version }}"
echo "Would push tag to: origin"
# Release creation - GitHub version
- name: Create Release (GitHub)
if: steps.detect_env.outputs.is_local == 'false'
id: create_release
uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: 'output/cpz/*,output/**/*.cplz'
generateReleaseNotes: true
prerelease: ${{contains('debug', env.BUILD_TYPE)}}
tag: ${{ steps.setVersion.outputs.version }}
# Release creation - Act mock version with enhanced output
- name: Create Release (Act Mock)
if: steps.detect_env.outputs.is_local == 'true'
run: |
echo "=== Mock Release Creation ==="
echo "Would create release with:"
echo "- Tag: ${{ steps.setVersion.outputs.version }}"
echo "- Prerelease: ${{contains('debug', env.BUILD_TYPE)}}"
echo "- Artifacts matching pattern: output/cpz/*,output/**/*.cplz"
echo ""
echo "Matching artifacts:"
find ./output/cpz -type f
find ./output -name "*.cplz"
# Detailed info about release artifacts
echo ""
echo "Artifact Details:"
for artifact in $(find ./output/cpz -type f; find ./output -name "*.cplz"); do
echo "File: $(basename "$artifact")"
echo "Size: $(du -h "$artifact" | cut -f1)"
echo "Created: $(stat -c %y "$artifact")"
echo "MD5: $(md5sum "$artifact" | cut -d' ' -f1)"
echo "--------------------------"
done
echo "============================"
# NuGet setup - GitHub version
- name: Setup NuGet (GitHub)
if: steps.detect_env.outputs.is_local == 'false'
run: |
dotnet nuget add source https://nuget.pkg.github.com/pepperdash/index.json -n github -u pepperdash -p ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text
# NuGet setup - Act mock version
- name: Setup NuGet (Act Mock)
if: steps.detect_env.outputs.is_local == 'true'
run: |
echo "=== Mock NuGet Setup ==="
echo "Would add GitHub NuGet source: https://nuget.pkg.github.com/pepperdash/index.json"
echo "======================="
# Publish to NuGet - GitHub version
- name: Publish to Nuget (GitHub)
if: steps.detect_env.outputs.is_local == 'false'
run: dotnet nuget push ./output/nuget/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
# Publish to NuGet - Act mock version
- name: Publish to Nuget (Act Mock)
if: steps.detect_env.outputs.is_local == 'true'
run: |
echo "=== Mock Publish to NuGet ==="
echo "Would publish the following packages to https://api.nuget.org/v3/index.json:"
find ./output/nuget -name "*.nupkg" | sort
echo "============================="
# Publish to GitHub NuGet - GitHub version
- name: Publish to Github Nuget (GitHub)
if: steps.detect_env.outputs.is_local == 'false'
run: dotnet nuget push ./output/nuget/*.nupkg --source github --api-key ${{ secrets.GITHUB_TOKEN }}
# Publish to GitHub NuGet - Act mock version
- name: Publish to Github Nuget (Act Mock)
if: steps.detect_env.outputs.is_local == 'true'
run: |
echo "=== Mock Publish to GitHub NuGet ==="
echo "Would publish the following packages to the GitHub NuGet registry:"
find ./output/nuget -name "*.nupkg" | sort
echo "=================================="

View File

@@ -1,57 +0,0 @@
name: main Build using Docker
on:
release:
types:
- published
branches:
- main-2.0.0
env:
# solution path doesn't need slashes unless there it is multiple folders deep
# solution name does not include extension. .sln is assumed
SOLUTION_PATH: PepperDashEssentials
SOLUTION_FILE: PepperDashEssentials
# Do not edit this, we're just creating it here
VERSION: 0.0.0-buildtype-buildnumber
# Defaults to debug for build type
BUILD_TYPE: Release
# Defaults to main as the release branch. Change as necessary
RELEASE_BRANCH: main
jobs:
Build_Project:
runs-on: windows-2019
steps:
# First we checkout the source repo
- name: Checkout repo
uses: actions/checkout@v3
# Generate the appropriate version number
- name: Set Version Number
shell: powershell
id: setVersion
env:
TAG_NAME: ${{ github.event.release.tag_name }}
run: echo "VERSION=$($Env:TAG_NAME)" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
- name: Setup MS Build
uses: microsoft/setup-msbuild@v1.1
- name: restore Nuget Packages
run: nuget restore .\$($Env:SOLUTION_FILE).sln
- name: Build Solution
run: msbuild .\$($Env:SOLUTION_FILE).sln /p:Platform="Any CPU" /p:Configuration="Debug" /p:Version="${{ steps.setVersion.outputs.version }}" -m
- name: Pack Solution
run: dotnet pack .\$($Env:SOLUTION_FILE).sln --configuration $env:BUILD_TYPE --output ./output /p:Version="${{ steps.setVersion.outputs.version }}"
- name: Upload Release
id: create_release
uses: ncipollo/release-action@v1
with:
updateRelease: true
artifacts: 'output\**\*.*(cpz|cplz)'
tag: ${{ steps.setVersion.outputs.version }}
- name: Setup Nuget
run: |
nuget sources add -name github -source https://nuget.pkg.github.com/pepperdash/index.json -username pepperdash -password ${{ secrets.GITHUB_TOKEN }}
nuget setApiKey ${{ secrets.GITHUB_TOKEN }} -Source github
nuget setApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json
- name: Publish to Nuget
run: nuget push .\output\*.nupkg -Source https://api.nuget.org/v3/index.json
- name: Publish to Github Nuget
run: nuget push .\output\*.nupkg -Source github

7
runtimeconfig.json Normal file
View File

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

View File

@@ -15,7 +15,7 @@
<MakeDir Directories="$(PackageOutputPath)\$(AssemblyName)" Condition="!Exists('$(PackageOutputPath)\$(AssemblyName)')" />
<ZipDirectory SourceDirectory="$(TargetDir)" DestinationFile="$(PackageOutputPath)\$(AssemblyName)\$(TargetName).$(Version).$(TargetFramework).cplz" Overwrite="true"/>
</Target>
<Target Name="Copy CPZ NET6" AfterTargets="SimplSharpPostProcess" Condition="($(ProjectType) == 'Program' And ( '$(TargetFramework)' == 'net6.0' ) Or ( '$(TargetFramework)' == 'net8.0' ))">
<Target Name="Copy CPZ NET6" AfterTargets="SimplSharpPostProcess" Condition="$(ProjectType) == 'Program' And ( ('$(TargetFramework)' == 'net6.0') Or ('$(TargetFramework)' == 'net8.0') )">
<Message Text="Copying CPZ"></Message>
<Move SourceFiles="$(TargetDir)$(TargetName).cpz" DestinationFiles="$(PackageOutputPath)\$(AssemblyName)\$(TargetName).$(Version).$(TargetFramework).cpz" />
</Target>

View File

@@ -1,6 +1,4 @@
using System;
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Globalization;
@@ -35,7 +33,8 @@ namespace PepperDash.Essentials.Core
public static eCrestronSeries ProcessorSeries { get { return CrestronEnvironment.ProgramCompatibility; } }
// TODO: consider making this configurable later
public static IFormatProvider Culture = CultureInfo.CreateSpecificCulture("en-US");
public static IFormatProvider Culture = CultureInfo.InvariantCulture;
/// <summary>
/// True when the processor type is a DMPS variant
@@ -279,6 +278,18 @@ namespace PepperDash.Essentials.Core
CrestronConsole.PrintLine("Error starting CrestronDataStoreStatic: {0}", err);
return;
}
try
{
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CreateSpecificCulture("en");
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CreateSpecificCulture("en");
}
catch (CultureNotFoundException)
{
// If specific culture fails, fall back to invariant
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
}
}
}

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharp.Reflection;
using Crestron.SimplSharp.Scheduler;
using PepperDash.Core;

View File

@@ -4,7 +4,7 @@
<Configurations>Debug;Release;Debug 4.7.2</Configurations>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net472;net6</TargetFrameworks>
<TargetFramework>net8</TargetFramework>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<OutputPath>bin\$(Configuration)\</OutputPath>
<AssemblyName>PepperDash_Essentials_Core</AssemblyName>
@@ -25,8 +25,8 @@
<DebugType>pdbonly</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.20.66" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-451" />
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-462" />
</ItemGroup>
<ItemGroup>
<None Include="Crestron\CrestronGenericBaseDevice.cs.orig" />

View File

@@ -1,14 +1,18 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using System.Reflection;
using System.IO;
using System.Reflection.PortableExecutable;
using System.Reflection.Metadata;
using PepperDash.Core;
using PepperDash.Essentials.Core;
using Serilog.Events;
using Newtonsoft.Json;
using CrestronIO = Crestron.SimplSharp.CrestronIO;
using SystemIO = System.IO;
namespace PepperDash.Essentials
{
@@ -27,6 +31,11 @@ namespace PepperDash.Essentials
/// </summary>
static List<LoadedAssembly> LoadedPluginFolderAssemblies;
/// <summary>
/// List of plugins that were found to be incompatible with .NET 8
/// </summary>
public static List<IncompatiblePlugin> IncompatiblePlugins { get; private set; }
public static LoadedAssembly EssentialsAssembly { get; private set; }
public static LoadedAssembly PepperDashCoreAssembly { get; private set; }
@@ -46,12 +55,28 @@ namespace PepperDash.Essentials
// The temp directory where .cplz archives will be unzipped to
static string _tempDirectory => _pluginDirectory + Global.DirectorySeparator + "temp";
// Known incompatible types in .NET 8
private static readonly HashSet<string> KnownIncompatibleTypes = new HashSet<string>
{
"System.Net.ICertificatePolicy",
"System.Security.Cryptography.SHA1CryptoServiceProvider",
"System.Web.HttpUtility",
"System.Configuration.ConfigurationManager",
"System.Web.Services.Protocols.SoapHttpClientProtocol",
"System.Runtime.Remoting",
"System.EnterpriseServices",
"System.Runtime.Serialization.Formatters.Binary.BinaryFormatter",
"System.Security.SecurityManager",
"System.Security.Permissions.FileIOPermission",
"System.AppDomain.CreateDomain"
};
static PluginLoader()
{
LoadedAssemblies = new List<LoadedAssembly>();
LoadedPluginFolderAssemblies = new List<LoadedAssembly>();
EssentialsPluginAssemblies = new List<LoadedAssembly>();
IncompatiblePlugins = new List<IncompatiblePlugin>();
}
/// <summary>
@@ -61,7 +86,7 @@ namespace PepperDash.Essentials
{
Debug.LogMessage(LogEventLevel.Verbose, "Getting Assemblies loaded with Essentials");
// Get the loaded assembly filenames
var appDi = new DirectoryInfo(Global.ApplicationDirectoryPathPrefix);
var appDi = new SystemIO.DirectoryInfo(Global.ApplicationDirectoryPathPrefix);
var assemblyFiles = appDi.GetFiles("*.dll");
Debug.LogMessage(LogEventLevel.Verbose, "Found {0} Assemblies", assemblyFiles.Length);
@@ -113,7 +138,6 @@ namespace PepperDash.Essentials
}
}
public static void SetEssentialsAssembly(string name, Assembly assembly)
{
var loadedAssembly = LoadedAssemblies.FirstOrDefault(la => la.Name.Equals(name));
@@ -125,19 +149,108 @@ namespace PepperDash.Essentials
}
/// <summary>
/// Loads an assembly via Reflection and adds it to the list of loaded assemblies
/// Checks if a plugin is compatible with .NET 8 by examining its metadata
/// </summary>
/// <param name="fileName"></param>
static LoadedAssembly LoadAssembly(string filePath)
/// <param name="filePath">Path to the plugin assembly</param>
/// <returns>Tuple with compatibility result, reason if incompatible, and referenced assemblies</returns>
public static (bool IsCompatible, string Reason, List<string> References) IsPluginCompatibleWithNet8(string filePath)
{
try
{
//Debug.LogMessage(LogEventLevel.Verbose, "Attempting to load {0}", filePath);
List<string> referencedAssemblies = new List<string>();
using (SystemIO.FileStream fs = new SystemIO.FileStream(filePath, SystemIO.FileMode.Open,
SystemIO.FileAccess.Read, SystemIO.FileShare.ReadWrite))
using (PEReader peReader = new PEReader(fs))
{
if (!peReader.HasMetadata)
return (false, "Not a valid .NET assembly", referencedAssemblies);
MetadataReader metadataReader = peReader.GetMetadataReader();
// Collect assembly references
foreach (var assemblyRefHandle in metadataReader.AssemblyReferences)
{
var assemblyRef = metadataReader.GetAssemblyReference(assemblyRefHandle);
string assemblyName = metadataReader.GetString(assemblyRef.Name);
referencedAssemblies.Add(assemblyName);
}
// Check for references to known incompatible types
foreach (var typeRefHandle in metadataReader.TypeReferences)
{
var typeRef = metadataReader.GetTypeReference(typeRefHandle);
string typeNamespace = metadataReader.GetString(typeRef.Namespace);
string typeName = metadataReader.GetString(typeRef.Name);
string fullTypeName = $"{typeNamespace}.{typeName}";
if (KnownIncompatibleTypes.Contains(fullTypeName))
{
return (false, $"Uses incompatible type: {fullTypeName}", referencedAssemblies);
}
}
// Check for explicit .NET 8 compatibility attribute
bool hasNet8Attribute = false;
foreach (var customAttributeHandle in metadataReader.GetAssemblyDefinition().GetCustomAttributes())
{
var customAttribute = metadataReader.GetCustomAttribute(customAttributeHandle);
var ctorHandle = customAttribute.Constructor;
if (ctorHandle.Kind == HandleKind.MemberReference)
{
var memberRef = metadataReader.GetMemberReference((MemberReferenceHandle)ctorHandle);
var typeRef = metadataReader.GetTypeReference((TypeReferenceHandle)memberRef.Parent);
string typeName = metadataReader.GetString(typeRef.Name);
if (typeName == "Net8CompatibleAttribute" || typeName == "TargetFrameworkAttribute")
{
hasNet8Attribute = true;
break;
}
}
}
if (hasNet8Attribute)
{
return (true, null, referencedAssemblies);
}
// If we can't determine incompatibility, assume it's compatible
return (true, null, referencedAssemblies);
}
}
catch (Exception ex)
{
return (false, $"Error analyzing assembly: {ex.Message}", new List<string>());
}
}
/// <summary>
/// Loads an assembly via Reflection and adds it to the list of loaded assemblies
/// </summary>
/// <param name="filePath">Path to the assembly file</param>
/// <param name="requestedBy">Name of the plugin requesting this assembly (null for direct loads)</param>
static LoadedAssembly LoadAssembly(string filePath, string requestedBy = null)
{
try
{
// Check .NET 8 compatibility before loading
var (isCompatible, reason, references) = IsPluginCompatibleWithNet8(filePath);
if (!isCompatible)
{
string fileName = CrestronIO.Path.GetFileName(filePath);
Debug.LogMessage(LogEventLevel.Warning, "Assembly '{0}' is not compatible with .NET 8: {1}", fileName, reason);
var incompatiblePlugin = new IncompatiblePlugin(fileName, reason, requestedBy);
IncompatiblePlugins.Add(incompatiblePlugin);
return null;
}
var assembly = Assembly.LoadFrom(filePath);
if (assembly != null)
{
var assyVersion = GetAssemblyVersion(assembly);
var loadedAssembly = new LoadedAssembly(assembly.GetName().Name, assyVersion, assembly);
LoadedAssemblies.Add(loadedAssembly);
Debug.LogMessage(LogEventLevel.Information, "Loaded assembly '{0}', version {1}", loadedAssembly.Name, loadedAssembly.Version);
@@ -147,14 +260,47 @@ namespace PepperDash.Essentials
{
Debug.LogMessage(LogEventLevel.Information, "Unable to load assembly: '{0}'", filePath);
}
return null;
} catch(Exception ex)
{
Debug.LogMessage(ex, "Error loading assembly from {path}", null, filePath);
return null;
}
catch(SystemIO.FileLoadException ex) when (ex.Message.Contains("Assembly with same name is already loaded"))
{
// Get the assembly name from the file path
string assemblyName = CrestronIO.Path.GetFileNameWithoutExtension(filePath);
// Try to find the already loaded assembly
var existingAssembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name.Equals(assemblyName, StringComparison.OrdinalIgnoreCase));
if (existingAssembly != null)
{
Debug.LogMessage(LogEventLevel.Information, "Assembly '{0}' is already loaded, using existing instance", assemblyName);
var assyVersion = GetAssemblyVersion(existingAssembly);
var loadedAssembly = new LoadedAssembly(existingAssembly.GetName().Name, assyVersion, existingAssembly);
LoadedAssemblies.Add(loadedAssembly);
return loadedAssembly;
}
Debug.LogMessage(LogEventLevel.Warning, "Assembly with same name already loaded but couldn't find it: {0}", filePath);
return null;
}
catch(Exception ex)
{
string fileName = CrestronIO.Path.GetFileName(filePath);
// Check if this might be a .NET Framework compatibility issue
if (ex.Message.Contains("Could not load type") ||
ex.Message.Contains("Unable to load one or more of the requested types"))
{
Debug.LogMessage(LogEventLevel.Error, "Error loading assembly {0}: Likely .NET 8 compatibility issue: {1}",
fileName, ex.Message);
IncompatiblePlugins.Add(new IncompatiblePlugin(fileName, ex.Message, requestedBy));
}
else
{
Debug.LogMessage(ex, "Error loading assembly from {path}", null, filePath);
}
return null;
}
}
/// <summary>
@@ -216,12 +362,25 @@ namespace PepperDash.Essentials
CrestronConsole.ConsoleCommandResponse("{0} Version: {1}" + CrestronEnvironment.NewLine, assembly.Name, assembly.Version);
}
//CrestronConsole.ConsoleCommandResponse("Loaded Assemblies:" + CrestronEnvironment.NewLine);
//foreach (var assembly in LoadedAssemblies)
//{
// CrestronConsole.ConsoleCommandResponse("{0} Version: {1}" + CrestronEnvironment.NewLine, assembly.Name, assembly.Version);
//}
if (IncompatiblePlugins.Count > 0)
{
CrestronConsole.ConsoleCommandResponse("Incompatible Plugins:" + CrestronEnvironment.NewLine);
foreach (var plugin in IncompatiblePlugins)
{
if (plugin.TriggeredBy != "Direct load")
{
CrestronConsole.ConsoleCommandResponse("{0}: {1} (Required by: {2})" + CrestronEnvironment.NewLine,
plugin.Name, plugin.Reason, plugin.TriggeredBy);
}
else
{
CrestronConsole.ConsoleCommandResponse("{0}: {1}" + CrestronEnvironment.NewLine,
plugin.Name, plugin.Reason);
}
}
}
}
/// <summary>
/// Moves any .dll assemblies not already loaded from the plugins folder to loadedPlugins folder
/// </summary>
@@ -229,14 +388,14 @@ namespace PepperDash.Essentials
{
Debug.LogMessage(LogEventLevel.Information, "Looking for .dll assemblies from plugins folder...");
var pluginDi = new DirectoryInfo(_pluginDirectory);
var pluginDi = new SystemIO.DirectoryInfo(_pluginDirectory);
var pluginFiles = pluginDi.GetFiles("*.dll");
if (pluginFiles.Length > 0)
{
if (!Directory.Exists(_loadedPluginsDirectoryPath))
if (!SystemIO.Directory.Exists(_loadedPluginsDirectoryPath))
{
Directory.CreateDirectory(_loadedPluginsDirectoryPath);
SystemIO.Directory.CreateDirectory(_loadedPluginsDirectoryPath);
}
}
@@ -253,14 +412,14 @@ namespace PepperDash.Essentials
filePath = _loadedPluginsDirectoryPath + Global.DirectorySeparator + pluginFile.Name;
// Check if there is a previous file in the loadedPlugins directory and delete
if (File.Exists(filePath))
if (SystemIO.File.Exists(filePath))
{
Debug.LogMessage(LogEventLevel.Information, "Found existing file in loadedPlugins: {0} Deleting and moving new file to replace it", filePath);
File.Delete(filePath);
SystemIO.File.Delete(filePath);
}
// Move the file
File.Move(pluginFile.FullName, filePath);
SystemIO.File.Move(pluginFile.FullName, filePath);
Debug.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", pluginFile.FullName, filePath);
}
else
@@ -284,21 +443,21 @@ namespace PepperDash.Essentials
static void UnzipAndMoveCplzArchives()
{
Debug.LogMessage(LogEventLevel.Information, "Looking for .cplz archives from plugins folder...");
var di = new DirectoryInfo(_pluginDirectory);
var di = new SystemIO.DirectoryInfo(_pluginDirectory);
var zFiles = di.GetFiles("*.cplz");
if (zFiles.Length > 0)
{
if (!Directory.Exists(_loadedPluginsDirectoryPath))
if (!SystemIO.Directory.Exists(_loadedPluginsDirectoryPath))
{
Directory.CreateDirectory(_loadedPluginsDirectoryPath);
SystemIO.Directory.CreateDirectory(_loadedPluginsDirectoryPath);
}
}
foreach (var zfi in zFiles)
{
Directory.CreateDirectory(_tempDirectory);
var tempDi = new DirectoryInfo(_tempDirectory);
SystemIO.Directory.CreateDirectory(_tempDirectory);
var tempDi = new SystemIO.DirectoryInfo(_tempDirectory);
Debug.LogMessage(LogEventLevel.Information, "Found cplz: {0}. Unzipping into temp plugins directory", zfi.Name);
var result = CrestronZIP.Unzip(zfi.FullName, tempDi.FullName);
@@ -316,14 +475,14 @@ namespace PepperDash.Essentials
filePath = _loadedPluginsDirectoryPath + Global.DirectorySeparator + tempFile.Name;
// Check if there is a previous file in the loadedPlugins directory and delete
if (File.Exists(filePath))
if (SystemIO.File.Exists(filePath))
{
Debug.LogMessage(LogEventLevel.Information, "Found existing file in loadedPlugins: {0} Deleting and moving new file to replace it", filePath);
File.Delete(filePath);
SystemIO.File.Delete(filePath);
}
// Move the file
File.Move(tempFile.FullName, filePath);
SystemIO.File.Move(tempFile.FullName, filePath);
Debug.LogMessage(LogEventLevel.Verbose, "Moved {0} to {1}", tempFile.FullName, filePath);
}
else
@@ -339,7 +498,7 @@ namespace PepperDash.Essentials
}
// Delete the .cplz and the temp directory
Directory.Delete(_tempDirectory, true);
SystemIO.Directory.Delete(_tempDirectory, true);
zfi.Delete();
}
@@ -352,16 +511,40 @@ namespace PepperDash.Essentials
static void LoadPluginAssemblies()
{
Debug.LogMessage(LogEventLevel.Information, "Loading assemblies from loadedPlugins folder...");
var pluginDi = new DirectoryInfo(_loadedPluginsDirectoryPath);
var pluginDi = new CrestronIO.DirectoryInfo(_loadedPluginsDirectoryPath);
var pluginFiles = pluginDi.GetFiles("*.dll");
Debug.LogMessage(LogEventLevel.Verbose, "Found {0} plugin assemblies to load", pluginFiles.Length);
// First, check compatibility of all assemblies before loading any
var assemblyCompatibility = new Dictionary<string, (bool IsCompatible, string Reason, List<string> References)>();
foreach (var pluginFile in pluginFiles)
{
var loadedAssembly = LoadAssembly(pluginFile.FullName);
LoadedPluginFolderAssemblies.Add(loadedAssembly);
string fileName = pluginFile.Name;
assemblyCompatibility[fileName] = IsPluginCompatibleWithNet8(pluginFile.FullName);
}
// Now load compatible assemblies and track incompatible ones
foreach (var pluginFile in pluginFiles)
{
string fileName = pluginFile.Name;
var (isCompatible, reason, references) = assemblyCompatibility[fileName];
if (!isCompatible)
{
Debug.LogMessage(LogEventLevel.Warning, "Assembly '{0}' is not compatible with .NET 8: {1}", fileName, reason);
IncompatiblePlugins.Add(new IncompatiblePlugin(fileName, reason, null));
continue;
}
// Try to load the assembly
var loadedAssembly = LoadAssembly(pluginFile.FullName, null);
if (loadedAssembly != null)
{
LoadedPluginFolderAssemblies.Add(loadedAssembly);
}
}
Debug.LogMessage(LogEventLevel.Information, "All Plugins Loaded.");
@@ -373,8 +556,13 @@ namespace PepperDash.Essentials
static void LoadCustomPluginTypes()
{
Debug.LogMessage(LogEventLevel.Information, "Loading Custom Plugin Types...");
foreach (var loadedAssembly in LoadedPluginFolderAssemblies)
{
// Skip if assembly is null (can happen if we had loading issues)
if (loadedAssembly == null || loadedAssembly.Assembly == null)
continue;
// iteratate this assembly's classes, looking for "LoadPlugin()" methods
try
{
@@ -385,11 +573,49 @@ namespace PepperDash.Essentials
types = assy.GetTypes();
Debug.LogMessage(LogEventLevel.Debug, $"Got types for assembly {assy.GetName().Name}");
}
catch (ReflectionTypeLoadException e)
{
Debug.LogMessage(LogEventLevel.Error, "Unable to get types for assembly {0}: {1}",
loadedAssembly.Name, e.Message);
// Check if any of the loader exceptions are due to missing assemblies
foreach (var loaderEx in e.LoaderExceptions)
{
if (loaderEx is SystemIO.FileNotFoundException fileNotFoundEx)
{
string missingAssembly = fileNotFoundEx.FileName;
if (!string.IsNullOrEmpty(missingAssembly))
{
Debug.LogMessage(LogEventLevel.Warning, "Assembly {0} requires missing dependency: {1}",
loadedAssembly.Name, missingAssembly);
// Add to incompatible plugins with dependency information
IncompatiblePlugins.Add(new IncompatiblePlugin(
CrestronIO.Path.GetFileName(missingAssembly),
$"Missing dependency required by {loadedAssembly.Name}",
loadedAssembly.Name));
}
}
}
Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace);
continue;
}
catch (TypeLoadException e)
{
Debug.LogMessage(LogEventLevel.Error, "Unable to get types for assembly {0}: {1}",
loadedAssembly.Name, e.Message);
Debug.LogMessage(LogEventLevel.Verbose, e.StackTrace);
// Add to incompatible plugins if this is likely a .NET 8 compatibility issue
if (e.Message.Contains("Could not load type") ||
e.Message.Contains("Unable to load one or more of the requested types"))
{
IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name,
$"Type loading error: {e.Message}",
null));
}
continue;
}
@@ -414,22 +640,66 @@ namespace PepperDash.Essentials
loadedAssembly.Name, e.Message, type.Name);
continue;
}
}
}
catch (Exception e)
{
Debug.LogMessage(LogEventLevel.Information, "Error Loading assembly {0}: {1}",
loadedAssembly.Name, e.Message);
loadedAssembly.Name, e.Message);
Debug.LogMessage(LogEventLevel.Verbose, "{0}", e.StackTrace);
// Add to incompatible plugins if this is likely a .NET 8 compatibility issue
if (e.Message.Contains("Could not load type") ||
e.Message.Contains("Unable to load one or more of the requested types"))
{
IncompatiblePlugins.Add(new IncompatiblePlugin(loadedAssembly.Name,
$"Assembly loading error: {e.Message}",
null));
}
continue;
}
}
// Update incompatible plugins with dependency information
var pluginDependencies = new Dictionary<string, List<string>>();
// Populate pluginDependencies with relevant data
// Example: pluginDependencies["PluginA"] = new List<string> { "Dependency1", "Dependency2" };
UpdateIncompatiblePluginDependencies(pluginDependencies);
// plugin dll will be loaded. Any classes in plugin should have a static constructor
// that registers that class with the Core.DeviceFactory
Debug.LogMessage(LogEventLevel.Information, "Done Loading Custom Plugin Types.");
}
/// <summary>
/// Updates incompatible plugins with information about which plugins depend on them
/// </summary>
private static void UpdateIncompatiblePluginDependencies(Dictionary<string, List<string>> pluginDependencies)
{
// For each incompatible plugin
foreach (var incompatiblePlugin in IncompatiblePlugins)
{
// If it already has a requestedBy, skip it
if (incompatiblePlugin.TriggeredBy != "Direct load")
continue;
// Find plugins that depend on this incompatible plugin
foreach (var plugin in pluginDependencies)
{
string pluginName = plugin.Key;
List<string> dependencies = plugin.Value;
// If this plugin depends on the incompatible plugin
if (dependencies.Contains(incompatiblePlugin.Name) ||
dependencies.Any(d => d.StartsWith(incompatiblePlugin.Name + ",")))
{
incompatiblePlugin.UpdateTriggeringPlugin(pluginName);
break;
}
}
}
}
/// <summary>
/// Loads a
/// </summary>
@@ -506,7 +776,6 @@ namespace PepperDash.Essentials
Debug.LogMessage(LogEventLevel.Information, "Loading legacy plugin: {0}", loadedAssembly.Name);
loadPlugin.Invoke(null, null);
}
/// <summary>
@@ -516,7 +785,7 @@ namespace PepperDash.Essentials
{
Debug.LogMessage(LogEventLevel.Information, "Attempting to Load Plugins from {_pluginDirectory}", _pluginDirectory);
if (Directory.Exists(_pluginDirectory))
if (SystemIO.Directory.Exists(_pluginDirectory))
{
Debug.LogMessage(LogEventLevel.Information, "Plugins directory found, checking for plugins");
@@ -526,7 +795,7 @@ namespace PepperDash.Essentials
// Deal with any .cplz files
UnzipAndMoveCplzArchives();
if (Directory.Exists(_loadedPluginsDirectoryPath))
if (SystemIO.Directory.Exists(_loadedPluginsDirectoryPath))
{
// Load the assemblies from the loadedPlugins folder into the AppDomain
LoadPluginAssemblies();
@@ -534,9 +803,27 @@ namespace PepperDash.Essentials
// Load the types from any custom plugin assemblies
LoadCustomPluginTypes();
}
// Report on incompatible plugins
if (IncompatiblePlugins.Count > 0)
{
Debug.LogMessage(LogEventLevel.Warning, "Found {0} incompatible plugins:", IncompatiblePlugins.Count);
foreach (var plugin in IncompatiblePlugins)
{
if (plugin.TriggeredBy != "Direct load")
{
Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1} (Required by: {2})",
plugin.Name, plugin.Reason, plugin.TriggeredBy);
}
else
{
Debug.LogMessage(LogEventLevel.Warning, " - {0}: {1}",
plugin.Name, plugin.Reason);
}
}
}
}
}
}
/// <summary>
@@ -563,4 +850,52 @@ namespace PepperDash.Essentials
Assembly = assembly;
}
}
/// <summary>
/// Represents a plugin that was found to be incompatible with .NET 8
/// </summary>
public class IncompatiblePlugin
{
[JsonProperty("name")]
public string Name { get; private set; }
[JsonProperty("reason")]
public string Reason { get; private set; }
[JsonProperty("triggeredBy")]
public string TriggeredBy { get; private set; }
public IncompatiblePlugin(string name, string reason, string triggeredBy = null)
{
Name = name;
Reason = reason;
TriggeredBy = triggeredBy ?? "Direct load";
}
/// <summary>
/// Updates the plugin that triggered this incompatibility
/// </summary>
/// <param name="triggeringPlugin">Name of the plugin that requires this incompatible plugin</param>
public void UpdateTriggeringPlugin(string triggeringPlugin)
{
if (!string.IsNullOrEmpty(triggeringPlugin))
{
TriggeredBy = triggeringPlugin;
}
}
}
/// <summary>
/// Attribute to explicitly mark a plugin as .NET 8 compatible
/// </summary>
[AttributeUsage(AttributeTargets.Assembly)]
public class Net8CompatibleAttribute : Attribute
{
public bool IsCompatible { get; }
public Net8CompatibleAttribute(bool isCompatible = true)
{
IsCompatible = isCompatible;
}
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
public class SomeWebSocketClass
{
private ClientWebSocket _webSocket;
public SomeWebSocketClass()
{
_webSocket = new ClientWebSocket();
}
public async Task ConnectAsync(Uri uri)
{
await _webSocket.ConnectAsync(uri, CancellationToken.None);
}
public async Task SendAsync(string message)
{
var buffer = System.Text.Encoding.UTF8.GetBytes(message);
await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
}
public async Task<string> ReceiveAsync()
{
var buffer = new byte[1024];
var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
return System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count);
}
public async Task CloseAsync()
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
}

View File

@@ -10,11 +10,14 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PepperDash.Essentials.Core.Web.RequestHandlers;
namespace PepperDash.Essentials.Core.Web.RequestHandlers
{
public class DebugSessionRequestHandler : WebApiBaseRequestHandler
{
private readonly DebugWebsocketSink _sink = new DebugWebsocketSink();
public DebugSessionRequestHandler()
: base(true)
{
@@ -43,21 +46,21 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
var port = 0;
if (!Debug.WebsocketSink.IsRunning)
if (!_sink.IsRunning)
{
Debug.LogMessage(LogEventLevel.Information, "Starting WS Server");
// Generate a random port within a specified range
port = new Random().Next(65435, 65535);
// Start the WS Server
Debug.WebsocketSink.StartServerAndSetPort(port);
_sink.StartServerAndSetPort(port);
Debug.SetWebSocketMinimumDebugLevel(Serilog.Events.LogEventLevel.Verbose);
}
var url = Debug.WebsocketSink.Url;
var url = _sink.Url;
object data = new
{
url = Debug.WebsocketSink.Url
url = _sink.Url
};
Debug.LogMessage(LogEventLevel.Information, "Debug Session URL: {0}", url);
@@ -84,7 +87,7 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
/// <param name="context"></param>
protected override void HandlePost(HttpCwsContext context)
{
Debug.WebsocketSink.StopServer();
_sink.StopServer();
context.Response.StatusCode = 200;
context.Response.StatusDescription = "OK";

View File

@@ -0,0 +1,42 @@
using System;
namespace PepperDash.Essentials.Core.Web.RequestHandlers
{
public class DebugWebsocketSink
{
private bool _isRunning;
private string _url;
public bool IsRunning => _isRunning;
public string Url => _url;
public void StartServerAndSetPort(int port)
{
try
{
_url = $"ws://localhost:{port}";
_isRunning = true;
// Implement actual server startup logic here
}
catch (Exception ex)
{
_isRunning = false;
throw new Exception($"Failed to start debug websocket server: {ex.Message}");
}
}
public void StopServer()
{
try
{
// Implement actual server shutdown logic here
_isRunning = false;
_url = null;
}
catch (Exception ex)
{
throw new Exception($"Failed to stop debug websocket server: {ex.Message}");
}
}
}
}

View File

@@ -4,7 +4,7 @@
<Configurations>Debug;Release;Debug 4.7.2</Configurations>
</PropertyGroup>
<PropertyGroup>
<TargetFrameworks>net472;net6</TargetFrameworks>
<TargetFramework>net8</TargetFramework>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<OutputPath>bin\$(Configuration)\</OutputPath>
<AssemblyName>Essentials Devices Common</AssemblyName>
@@ -29,7 +29,7 @@
<ProjectReference Include="..\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.20.66" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-451" />
<PackageReference Include="Crestron.SimplSharp.SDK.ProgramLibrary" Version="2.21.90" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-462" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,37 @@
using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
public class SomeOtherWebSocketClass
{
private ClientWebSocket _webSocket;
public SomeOtherWebSocketClass()
{
_webSocket = new ClientWebSocket();
}
public async Task ConnectAsync(Uri uri)
{
await _webSocket.ConnectAsync(uri, CancellationToken.None);
}
public async Task SendAsync(string message)
{
var buffer = System.Text.Encoding.UTF8.GetBytes(message);
await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
}
public async Task<string> ReceiveAsync()
{
var buffer = new byte[1024];
var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
return System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count);
}
public async Task CloseAsync()
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
}

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.Reflection;
using System.Reflection;
using Crestron.SimplSharpPro.DeviceSupport;
using Crestron.SimplSharp;
using PepperDash.Core;

View File

@@ -1,5 +1,4 @@
using Crestron.SimplSharp;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using System.Reflection;
using Crestron.SimplSharpPro;
@@ -28,7 +27,9 @@ namespace PepperDash.Essentials
public ControlSystem()
: base()
{
Thread.MaxNumberOfUserThreads = 400;
Global.ControlSystem = this;
DeviceManager.Initialize(this);
@@ -36,7 +37,7 @@ namespace PepperDash.Essentials
SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true;
Debug.SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? LogEventLevel.Warning : LogEventLevel.Verbose);
// AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
}
@@ -96,6 +97,9 @@ namespace PepperDash.Essentials
DeterminePlatform();
// Print .NET runtime version
Debug.LogMessage(LogEventLevel.Information, "Running on .NET runtime version: {0}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
if (Debug.DoNotLoadConfigOnNextBoot)
{
CrestronConsole.AddNewConsoleCommand(s => CrestronInvoke.BeginInvoke((o) => GoWithLoad()), "go", "Loads configuration file",
@@ -144,7 +148,7 @@ namespace PepperDash.Essentials
CrestronConsole.AddNewConsoleCommand(DeviceManager.GetRoutingPorts,
"getroutingports", "Reports all routing ports, if any. Requires a device key", ConsoleAccessLevelEnum.AccessOperator);
DeviceManager.AddDevice(new EssentialsWebApi("essentialsWebApi", "Essentials Web API"));
//DeviceManager.AddDevice(new EssentialsWebApi("essentialsWebApi", "Essentials Web API"));
if (!Debug.DoNotLoadConfigOnNextBoot)
{
@@ -173,7 +177,7 @@ namespace PepperDash.Essentials
var dirSeparator = Global.DirectorySeparator;
string directoryPrefix;
string directoryPrefix;
directoryPrefix = Directory.GetApplicationRootDirectory();
@@ -181,24 +185,10 @@ namespace PepperDash.Essentials
if (CrestronEnvironment.DevicePlatform != eDevicePlatform.Server) // Handles 3-series running Windows CE OS
{
string userFolder;
string nvramFolder;
bool is4series = false;
string userFolder = "user";
string nvramFolder = "nvram";
if (eCrestronSeries.Series4 == (Global.ProcessorSeries & eCrestronSeries.Series4)) // Handle 4-series
{
is4series = true;
// Set path to user/
userFolder = "user";
nvramFolder = "nvram";
}
else
{
userFolder = "User";
nvramFolder = "Nvram";
}
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on {processorSeries:l} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series");
Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{version:l} on {processorSeries:l} Appliance", Global.AssemblyVersion, "4-series");
//Debug.LogMessage(LogEventLevel.Information, "Starting Essentials v{0} on {1} Appliance", Global.AssemblyVersion, is4series ? "4-series" : "3-series");
// Check if User/ProgramX exists
@@ -360,6 +350,7 @@ namespace PepperDash.Essentials
/// <summary>
///
/// </summary>
void Load()
{
LoadDevices();
@@ -480,7 +471,16 @@ namespace PepperDash.Essentials
{
var room = Core.DeviceFactory.GetDevice(roomConfig);
DeviceManager.AddDevice(room);
if(room == null)
{
Debug.LogMessage(LogEventLevel.Information, "ERROR: Cannot load unknown room type '{roomType:l}', key '{roomKey:l}'.", roomConfig.Type, roomConfig.Key);
continue;
}
else
{
DeviceManager.AddDevice(room);
}
if (room is ICustomMobileControl)
{
continue;

View File

@@ -0,0 +1,40 @@
using System;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
namespace PepperDash.Essentials.Fusion
{
public class EssentialsHuddleSpaceFusionSystemControllerBase
{
private ClientWebSocket _webSocket;
public EssentialsHuddleSpaceFusionSystemControllerBase()
{
_webSocket = new ClientWebSocket();
}
public async Task ConnectAsync(Uri uri)
{
await _webSocket.ConnectAsync(uri, CancellationToken.None);
}
public async Task SendAsync(string message)
{
var buffer = System.Text.Encoding.UTF8.GetBytes(message);
await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
}
public async Task<string> ReceiveAsync()
{
var buffer = new byte[1024];
var result = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
return System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count);
}
public async Task CloseAsync()
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
}
}

View File

@@ -6,15 +6,18 @@
<PropertyGroup>
<RootNamespace>PepperDash.Essentials</RootNamespace>
<AssemblyName>PepperDashEssentials</AssemblyName>
<TargetFrameworks>net472;net6</TargetFrameworks>
<TargetFramework>net8</TargetFramework>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<OutputPath>bin\$(Configuration)\</OutputPath>
<OutputPath>$(ProjectDir)bin\$(Configuration)\</OutputPath>
<Title>PepperDash Essentials</Title>
<PackageId>PepperDashEssentials</PackageId>
<Version>2.0.0-local</Version>
<InformationalVersion>$(Version)</InformationalVersion>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
</PropertyGroup>
<PropertyGroup>
<ProjectType>Program</ProjectType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>
</PropertyGroup>
@@ -48,8 +51,8 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.20.66" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-451" />
<PackageReference Include="Crestron.SimplSharp.SDK.Program" Version="2.21.90" />
<PackageReference Include="PepperDashCore" Version="2.0.0-alpha-462" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj" />