diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..db9b9d8
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "husky": {
+ "version": "0.9.1",
+ "commands": [
+ "husky"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.github/scripts/GenerateVersionNumber.ps1 b/.github/scripts/GenerateVersionNumber.ps1
deleted file mode 100644
index 5d5a3ec..0000000
--- a/.github/scripts/GenerateVersionNumber.ps1
+++ /dev/null
@@ -1,45 +0,0 @@
-$latestVersions = $(git tag --merged origin/main)
-$latestVersion = [version]"0.0.0"
-Foreach ($version in $latestVersions) {
- Write-Host $version
- try {
- if (([version]$version) -ge $latestVersion) {
- $latestVersion = $version
- Write-Host "Setting latest version to: $latestVersion"
- }
- }
- catch {
- Write-Host "Unable to convert $($version). Skipping"
- continue;
- }
-}
-
-$newVersion = [version]$latestVersion
-$phase = ""
-$newVersionString = ""
-switch -regex ($Env:GITHUB_REF) {
- '^refs\/heads\/main*.' {
- $newVersionString = "{0}.{1}.{2}" -f $newVersion.Major, $newVersion.Minor, $newVersion.Build
- }
- '^refs\/heads\/feature\/*.' {
- $phase = 'alpha'
- $newVersionString = "{0}.{1}.{2}-{3}-{4}" -f $newVersion.Major, $newVersion.Minor, ($newVersion.Build + 1), $phase, $Env:GITHUB_RUN_NUMBER
- }
- '^refs\/heads\/release\/*.' {
- $splitRef = $Env:GITHUB_REF -split "/"
- $version = [version]($splitRef[-1] -replace "v", "")
- $phase = 'rc'
- $newVersionString = "{0}.{1}.{2}-{3}-{4}" -f $version.Major, $version.Minor, $version.Build, $phase, $Env:GITHUB_RUN_NUMBER
- }
- '^refs\/heads\/dev*.' {
- $phase = 'beta'
- $newVersionString = "{0}.{1}.{2}-{3}-{4}" -f $newVersion.Major, $newVersion.Minor, ($newVersion.Build + 1), $phase, $Env:GITHUB_RUN_NUMBER
- }
- '^refs\/heads\/hotfix\/*.' {
- $phase = 'hotfix'
- $newVersionString = "{0}.{1}.{2}-{3}-{4}" -f $newVersion.Major, $newVersion.Minor, ($newVersion.Build + 1), $phase, $Env:GITHUB_RUN_NUMBER
- }
-}
-
-
-Write-Output $newVersionString
diff --git a/.github/scripts/Update-ReadmeBadges.ps1 b/.github/scripts/Update-ReadmeBadges.ps1
new file mode 100644
index 0000000..72023a1
--- /dev/null
+++ b/.github/scripts/Update-ReadmeBadges.ps1
@@ -0,0 +1,114 @@
+<#
+.SYNOPSIS
+ Syncs the README .NET and PepperDash Essentials badges with the project files.
+.PARAMETER Check
+ Exit 1 if the README badges are stale instead of rewriting them.
+.PARAMETER Stage
+ git add README.md when it was changed.
+#>
+[CmdletBinding()]
+param(
+ [switch]$Check,
+ [switch]$Stage
+)
+
+$ErrorActionPreference = 'Stop'
+$root = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+$srcDir = Join-Path $root 'src'
+$readmePath = Join-Path $root 'README.md'
+$utf8 = New-Object System.Text.UTF8Encoding($false)
+
+function Fail($message) {
+ [Console]::Error.WriteLine("badges: $message")
+ exit 1
+}
+
+# Staging README.md adds its whole working-tree copy, and badges are computed from working-tree
+# sources, so refuse to run when any of those inputs differ from the index.
+if ($Stage) {
+ # Porcelain XY: a non-blank Y is a worktree change ('??' = untracked), which the index lacks.
+ $status = @(git -C $root status --porcelain --untracked-files=all -- README.md 'src/*.4Series.csproj' 'src/*Factory.cs')
+ if ($LASTEXITCODE) { Fail 'git status failed' }
+ $unstaged = @($status | Where-Object { $_ -match '^.[^ ]' })
+ if ($unstaged.Count -gt 0) {
+ Fail ("unstaged or untracked badge inputs ($(($unstaged | ForEach-Object { $_.Substring(3) }) -join ', ')); stage or stash them, then commit again")
+ }
+}
+
+# Target framework from the 4Series csproj
+$csprojFile = Get-ChildItem -Path $srcDir -Filter '*.4Series.csproj' | Select-Object -First 1
+if (-not $csprojFile) { Fail 'no *.4Series.csproj found in src/' }
+$csproj = [System.IO.File]::ReadAllText($csprojFile.FullName, $utf8)
+$tfmMatch = [regex]::Match($csproj, '\s*([^<\s]+)\s*')
+if (-not $tfmMatch.Success) { Fail "TargetFramework not found in $($csprojFile.Name)" }
+$tfm = $tfmMatch.Groups[1].Value
+
+# Minimum Essentials version: highest MinimumEssentialsFrameworkVersion across factories
+$minVersions = @()
+foreach ($file in Get-ChildItem -Path $srcDir -Filter '*Factory.cs') {
+ $text = [System.IO.File]::ReadAllText($file.FullName, $utf8)
+ $found = [regex]::Matches($text, '(?m)^\s*MinimumEssentialsFrameworkVersion\s*=\s*"(\d+(?:\.\d+)+)"\s*;')
+ foreach ($m in $found) { $minVersions += $m.Groups[1].Value }
+}
+if ($minVersions.Count -eq 0) { Fail 'MinimumEssentialsFrameworkVersion not found in src/*Factory.cs' }
+$minVersion = ($minVersions | Sort-Object { [version]$_ } | Select-Object -Last 1)
+
+# Warn when the NuGet reference and the factory minimum disagree
+$pkgMatch = [regex]::Match($csproj, '= v$minVersion)"
+
+if ($Stage) {
+ git -C $root add README.md
+ if ($LASTEXITCODE) { Fail 'git add README.md failed' }
+}
diff --git a/.github/scripts/UpdateAssemblyVersion.ps1 b/.github/scripts/UpdateAssemblyVersion.ps1
deleted file mode 100644
index e52b31e..0000000
--- a/.github/scripts/UpdateAssemblyVersion.ps1
+++ /dev/null
@@ -1,40 +0,0 @@
-function Update-SourceVersion {
- Param ([string]$Version)
- #$fullVersion = $Version
- $baseVersion = [regex]::Match($Version, "(\d+.\d+.\d+).*").captures.groups[1].value
- $NewAssemblyVersion = ‘AssemblyVersion("‘ + $baseVersion + ‘.*")’
- Write-Output "AssemblyVersion = $NewAssemblyVersion"
- $NewAssemblyInformationalVersion = ‘AssemblyInformationalVersion("‘ + $Version + ‘")’
- Write-Output "AssemblyInformationalVersion = $NewAssemblyInformationalVersion"
-
- foreach ($o in $input) {
- Write-output $o.FullName
- $TmpFile = $o.FullName + “.tmp”
- get-content $o.FullName |
- ForEach-Object {
- $_ -replace ‘AssemblyVersion\(".*"\)’, $NewAssemblyVersion } |
- ForEach-Object {
- $_ -replace ‘AssemblyInformationalVersion\(".*"\)’, $NewAssemblyInformationalVersion
- } > $TmpFile
- move-item $TmpFile $o.FullName -force
- }
-}
-
-function Update-AllAssemblyInfoFiles ( $version ) {
- foreach ($file in “AssemblyInfo.cs”, “AssemblyInfo.vb” ) {
- get-childitem -Path $Env:GITHUB_WORKSPACE -recurse | Where-Object { $_.Name -eq $file } | Update-SourceVersion $version ;
- }
-}
-
-# validate arguments
-$r = [System.Text.RegularExpressions.Regex]::Match($args[0], "\d+\.\d+\.\d+.*");
-if ($r.Success) {
- Write-Output "Updating Assembly Version to $args ...";
- Update-AllAssemblyInfoFiles $args[0];
-}
-else {
- Write-Output ” “;
- Write-Output “Error: Input version does not match x.y.z format!”
- Write-Output ” “;
- Write-Output "Unable to apply version to AssemblyInfo.cs files";
-}
diff --git a/.github/scripts/ZipBuildOutput.ps1 b/.github/scripts/ZipBuildOutput.ps1
deleted file mode 100644
index ca4a9a7..0000000
--- a/.github/scripts/ZipBuildOutput.ps1
+++ /dev/null
@@ -1,42 +0,0 @@
-# Uncomment these for local testing
-# $Env:GITHUB_WORKSPACE = "C:\Working Directories\PD\essentials"
-# $Env:SOLUTION_FILE = "PepperDashEssentials"
-# $Env:VERSION = "0.0.0-buildType-test"
-
-# Sets the root directory for the operation
-$destination = "$($Env:GITHUB_HOME)\output"
-New-Item -ItemType Directory -Force -Path ($destination)
-Get-ChildItem ($destination)
-$exclusions = @(git submodule foreach --quiet 'echo $name')
-# Trying to get any .json schema files (not currently working)
-# Gets any files with the listed extensions.
-Get-ChildItem -recurse -Path "$($Env:GITHUB_WORKSPACE)" -include "*.cplz" | ForEach-Object {
- $allowed = $true;
- # Exclude any files in submodules
- foreach ($exclude in $exclusions) {
- if ((Split-Path $_.FullName -Parent).contains("$($exclude)")) {
- $allowed = $false;
- break;
- }
- }
- if ($allowed) {
- Write-Host "allowing $($_)"
- $_;
- }
-} | Copy-Item -Destination ($destination) -Force
-Write-Host "Getting matching files..."
-# Get any files from the output folder that match the following extensions
-Get-ChildItem -Path $destination | Where-Object {($_.Extension -eq ".clz") -or ($_.Extension -eq ".cpz" -or ($_.Extension -eq ".cplz"))} | ForEach-Object {
- # Replace the extensions with dll and xml and create an array
- $filenames = @($($_ -replace "cpz|clz|cplz", "dll"), $($_ -replace "cpz|clz|cplz", "xml"))
- Write-Host "Filenames:"
- Write-Host $filenames
- if ($filenames.length -gt 0) {
- # Attempt to get the files and return them to the output directory
- Get-ChildItem -Recurse -Path "$($Env:GITHUB_WORKSPACE)" -include $filenames | Copy-Item -Destination ($destination) -Force
- }
-}
-Get-ChildItem -Path $destination\*.cplz | Rename-Item -NewName { "$($_.BaseName)-$($Env:VERSION)$($_.Extension)" }
-Compress-Archive -Path $destination -DestinationPath "$($Env:GITHUB_WORKSPACE)\$($Env:SOLUTION_FILE)-$($Env:VERSION).zip" -Force
-Write-Host "Output Contents post Zip"
-Get-ChildItem -Path $destination
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f218fa0..91861d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -357,3 +357,6 @@ MigrationBackup/
*.clz
*.projectinfo
*.suo
+
+# VS Code
+*.code-workspace
\ No newline at end of file
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 0000000..fd85d23
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,22 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+## husky task runner examples -------------------
+## Note : for local installation use 'dotnet' prefix. e.g. 'dotnet husky'
+
+## run all tasks
+#husky run
+
+### run all tasks with group: 'group-name'
+#husky run --group group-name
+
+## run task with name: 'task-name'
+#husky run --name task-name
+
+## pass hook arguments to task
+#husky run --args "$1" "$2"
+
+## or put your custom commands -------------------
+#echo 'Husky.Net is awesome!'
+
+dotnet husky run
diff --git a/.husky/task-runner.json b/.husky/task-runner.json
new file mode 100644
index 0000000..34b5d9c
--- /dev/null
+++ b/.husky/task-runner.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://alirezanet.github.io/Husky.Net/schema.json",
+ "tasks": [
+ {
+ "name": "update-readme-badges",
+ "command": "pwsh",
+ "args": [ "-NoProfile", "-File", ".github/scripts/Update-ReadmeBadges.ps1", "-Stage" ]
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1ac4b1c..e9f57a1 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,24 +1,4 @@
{
- "workbench.colorCustomizations": {
- "activityBar.activeBackground": "#8c57e5",
- "activityBar.background": "#8c57e5",
- "activityBar.foreground": "#e7e7e7",
- "activityBar.inactiveForeground": "#e7e7e799",
- "activityBarBadge.background": "#6e3411",
- "activityBarBadge.foreground": "#e7e7e7",
- "commandCenter.border": "#e7e7e799",
- "sash.hoverBorder": "#8c57e5",
- "statusBar.background": "#6e2bde",
- "statusBar.foreground": "#e7e7e7",
- "statusBarItem.hoverBackground": "#8c57e5",
- "statusBarItem.remoteBackground": "#6e2bde",
- "statusBarItem.remoteForeground": "#e7e7e7",
- "titleBar.activeBackground": "#6e2bde",
- "titleBar.activeForeground": "#e7e7e7",
- "titleBar.inactiveBackground": "#6e2bde99",
- "titleBar.inactiveForeground": "#e7e7e799"
- },
- "peacock.color": "#6e2bde",
"[csharp]": {
"editor.defaultFormatter": "ms-dotnettools.csharp",
"editor.formatOnSave": true,
@@ -29,4 +9,4 @@
"csharp.inlayHints.enableInlayHintsForLambdaParameterTypes": true,
"csharp.inlayHints.enableInlayHintsForTypes": true,
"dotnet.formatting.organizeImportsOnFormat": true
-}
\ No newline at end of file
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..49c88ed
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,24 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Install git hooks",
+ "type": "shell",
+ "command": "dotnet tool restore && dotnet husky install",
+ "options": {
+ "shell": {
+ "executable": "pwsh",
+ "args": ["-NoProfile", "-Command"]
+ }
+ },
+ "presentation": {
+ "reveal": "silent",
+ "close": true
+ },
+ "runOptions": {
+ "runOn": "folderOpen"
+ },
+ "problemMatcher": []
+ }
+ ]
+}
diff --git a/GetPackages.BAT b/GetPackages.BAT
deleted file mode 100644
index 9b6def3..0000000
--- a/GetPackages.BAT
+++ /dev/null
@@ -1,2 +0,0 @@
-@Echo ON
-nuget install .\packages.config -OutputDirectory .\packages -excludeVersion
diff --git a/README.md b/README.md
index 9d2df4d..d127c04 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,10 @@

+
+
+
+
+
# Essentials Plugin Template (c) 2025
## License
@@ -37,6 +42,49 @@ See the Task List in Visual Studio for a guide on how to start using the templat
For renaming instructions in particular, see the XML `remarks` tags on class definitions
+## Git Hooks (Husky.Net)
+
+This repo uses [Husky.Net](https://alirezanet.github.io/Husky.Net/) to run a `pre-commit` hook. The hook runs `.github/scripts/Update-ReadmeBadges.ps1`, which keeps the `.NET` and `PepperDash Essentials` badges at the top of this README in sync with the project:
+
+* `.NET` badge - `TargetFramework` in `src/*.4Series.csproj`
+* `PepperDash Essentials` badge - the highest `MinimumEssentialsFrameworkVersion` in `src/*Factory.cs`
+
+If the badges change, `README.md` is re-staged automatically so the update is part of your commit. A warning is printed if the `PepperDashEssentials` package version in the csproj differs from `MinimumEssentialsFrameworkVersion`.
+
+### Requirements
+
+* [.NET SDK](https://dotnet.microsoft.com/download) (provides `dotnet tool`)
+* [PowerShell 7+ (`pwsh`)](https://learn.microsoft.com/powershell/scripting/install/installing-powershell) on the `PATH` (Windows, macOS and Linux). Windows PowerShell 5.1 is not used.
+
+### Setup
+
+The hook is installed automatically the first time you do any of the following in a fresh clone:
+
+* Open the solution in Visual Studio, or run `dotnet restore` / `dotnet build`
+* Open the folder in VS Code and allow the "Install git hooks" automatic task
+
+To install it manually:
+
+```
+dotnet tool restore
+dotnet husky install
+```
+
+Verify with `git config core.hooksPath`, which should print `.husky`.
+
+### Usage
+
+* Commit as usual; the hook runs on every `git commit`.
+* Run it on demand: `dotnet husky run`
+* Check without modifying files (useful for CI): `pwsh -NoProfile -File .github/scripts/Update-ReadmeBadges.ps1 -Check`
+* The hook refuses to run if `README.md`, the `.4Series.csproj`, or a `*Factory.cs` file has unstaged or untracked changes, so a partial commit never picks up unrelated edits. Stage or stash them and commit again.
+* Skip the hook for a single commit: `git commit --no-verify`
+* Skip the automatic install on restore: set the `HUSKY=0` environment variable. It is also skipped when `CI=true`.
+
+### Renaming
+
+The script locates files by pattern (`*.4Series.csproj`, `*Factory.cs`), so renaming the project or factory classes does not require changes. Keep a `MinimumEssentialsFrameworkVersion = "x.y.z";` assignment in at least one factory, and keep the two badges in this README.
+
## Build Instructions (PepperDash Internal)
## Generating Nuget Package
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 3af6067..c920210 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -17,4 +17,9 @@
+
+
+
+
+
diff --git a/src/epi-make-model.3Series.csproj b/src/epi-make-model.3Series.csproj
deleted file mode 100644
index 03aa3ce..0000000
--- a/src/epi-make-model.3Series.csproj
+++ /dev/null
@@ -1,115 +0,0 @@
-
-
- Release
- AnyCPU
- 9.0.30729
- 2.0
- {9D249E47-8F95-4437-A6BB-563510287AD1}
- Library
- Properties
- PepperDash.Essentials.Plugin
- MakeModelPlugin
- {0B4745B0-194B-4BB6-8E21-E9057CA92300};{4D628B5B-2FBC-4AA6-8C16-197242AEB884};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- WindowsCE
- E2BECB1F-8C8C-41ba-B736-9BE7D946A398
- 5.0
- SmartDeviceProject1
- v3.5
- Windows CE
-
-
-
-
- .allowedReferenceRelatedFileExtensions
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE;
- prompt
- 4
- 512
- true
- true
- off
-
-
- .allowedReferenceRelatedFileExtensions
- none
- true
- bin\Release\
- prompt
- 4
- 512
- true
- true
- off
-
-
-
- False
- C:\ProgramData\Crestron\SDK\SSPDevices\Crestron.SimplSharpPro.UI.dll
-
-
- False
- ..\packages\PepperDashEssentials\lib\net35\Essentials Devices Common.dll
-
-
-
- False
- ..\packages\PepperDashEssentials\lib\net35\PepperDashEssentials.dll
-
-
- False
- ..\packages\PepperDashEssentials\lib\net35\PepperDash_Core.dll
-
-
- False
- ..\packages\PepperDashEssentials\lib\net35\PepperDash_Essentials_Core.dll
-
-
- False
- ..\packages\PepperDashEssentials\lib\net35\PepperDash_Essentials_DM.dll
-
-
- False
- C:\ProgramData\Crestron\SDK\SimplSharpCustomAttributesInterface.dll
- False
-
-
- False
- C:\ProgramData\Crestron\SDK\SimplSharpHelperInterface.dll
- False
-
-
- False
- C:\ProgramData\Crestron\SDK\SimplSharpNewtonsoft.dll
-
-
- False
- C:\ProgramData\Crestron\SDK\SimplSharpPro.exe
- False
-
-
- False
- C:\ProgramData\Crestron\SDK\SimplSharpReflectionInterface.dll
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rem S# Pro preparation will execute after these operations
-
-
\ No newline at end of file
diff --git a/src/epi-make-model.4Series.csproj b/src/epi-make-model.4Series.csproj
index 081ac42..baaca9e 100644
--- a/src/epi-make-model.4Series.csproj
+++ b/src/epi-make-model.4Series.csproj
@@ -9,7 +9,7 @@
PepperDash.Essentials.Plugin.Make.Model
PepperDash Technology
This software is a template for a PepperDash Essentials Plugin.
- Copyright 2025
+ Copyright 2026
1.0.0-local
true
$(Version)