├── .developer └── davepl │ ├── launch.json │ └── tasks.json ├── .gitattributes ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── COPYING.txt ├── Controllers └── LocationController.cs ├── Dockerfile ├── NightDriverServer.csproj ├── NightDriverServer.sln ├── Program.cs ├── Properties └── launchSettings.json ├── README.md ├── Server ├── Adler32.cs ├── Effects │ ├── BouncingBallEffect.cs │ ├── CabanaYardLight.cs │ ├── FireEffect.cs │ ├── FireworksEffect.cs │ ├── MeteorEffect.cs │ ├── PaletteEffect.cs │ ├── StarEffect.cs │ ├── TestEffect.cs │ └── TimeOfNightEffect.cs ├── FastLEDInterop.cs ├── Graphics.cs ├── LEDControllerChannel.cs ├── LEDEffect.cs ├── LightStrip.cs ├── Palette.cs ├── Program.cs ├── SiteController.cs ├── Statistics.cs ├── SunriseSunset.cs ├── ZLIBStream.cs ├── ZLibHeader.cs ├── favicon.ico └── index.html ├── Startup.cs ├── appsettings.Development.json ├── appsettings.json ├── make_docker.sh └── wwwroot ├── assets ├── datalist.json ├── index.html ├── scss │ └── material-dashboard │ │ └── variables │ │ ├── _code.scss │ │ ├── _drawer.scss │ │ ├── _layout.scss │ │ └── _tooltip.scss └── test.html ├── default.html ├── docs └── documentation.html ├── index.html └── template.html /.developer/davepl/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/bin/Debug/net5.0/NightDriverServer.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "integratedTerminal", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart" 18 | }, 19 | { 20 | "name": ".NET Core Attach", 21 | "type": "coreclr", 22 | "request": "attach" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /.developer/davepl/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | // Ask dotnet build to generate full paths for file names. 13 | "/property:GenerateFullPaths=true", 14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 15 | "/consoleloggerparameters:NoSummary" 16 | ], 17 | "group": "build", 18 | "presentation": { 19 | "reveal": "silent" 20 | }, 21 | "problemMatcher": "$msCompile" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ["**"] 6 | pull_request: 7 | branches: ["**"] 8 | workflow_dispatch: 9 | branches: ["**"] 10 | 11 | jobs: 12 | 13 | build: 14 | 15 | env: 16 | ENABLE_DOCKER_PUSH: ${{ github.repository_owner == 'PlummersSoftwareLLC' && github.ref == 'refs/heads/master' }} 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - name: Setup .NET 24 | uses: actions/setup-dotnet@v3 25 | with: 26 | dotnet-version: 6.0.x 27 | 28 | - name: Plain .NET restore and build 29 | run: | 30 | dotnet restore 31 | dotnet build --no-restore 32 | 33 | - name: Set up QEMU 34 | uses: docker/setup-qemu-action@v2 35 | 36 | - name: Set up Docker buildx 37 | uses: docker/setup-buildx-action@v2 38 | 39 | - name: Login to Docker Hub 40 | if: ${{ env.ENABLE_DOCKER_PUSH == 'true' }} 41 | uses: docker/login-action@v2 42 | with: 43 | username: ${{ secrets.DOCKER_USERNAME }} 44 | password: ${{ secrets.DOCKER_PASSWORD }} 45 | 46 | - name: Build amd64/arm64 Docker image 47 | uses: docker/build-push-action@v3 48 | with: 49 | tags: davepl/nightdriverserver:latest 50 | context: . 51 | platforms: linux/amd64,linux/arm64 52 | push: ${{ env.ENABLE_DOCKER_PUSH }} 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb 341 | 342 | .vscode/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/bin/Debug/net6.0/NightDriverServer.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "integratedTerminal", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart" 18 | }, 19 | { 20 | "name": ".NET Core Attach", 21 | "type": "coreclr", 22 | "request": "attach" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBorder": "#945bc4", 4 | "activityBar.activeBackground": "#65c89b", 5 | "activityBar.background": "#65c89b", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#945bc4", 9 | "activityBarBadge.foreground": "#e7e7e7", 10 | "commandCenter.border": "#15202b99", 11 | "sash.hoverBorder": "#65c89b", 12 | "statusBar.background": "#42b883", 13 | "statusBar.foreground": "#15202b", 14 | "statusBarItem.hoverBackground": "#359268", 15 | "statusBarItem.remoteBackground": "#42b883", 16 | "statusBarItem.remoteForeground": "#15202b", 17 | "titleBar.activeBackground": "#42b883", 18 | "titleBar.activeForeground": "#15202b", 19 | "titleBar.inactiveBackground": "#42b88399", 20 | "titleBar.inactiveForeground": "#15202b99" 21 | }, 22 | "peacock.remoteColor": "#42b883" 23 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | // Ask dotnet build to generate full paths for file names. 13 | "/property:GenerateFullPaths=true", 14 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 15 | "/consoleloggerparameters:NoSummary" 16 | ], 17 | "group": "build", 18 | "presentation": { 19 | "reveal": "silent" 20 | }, 21 | "problemMatcher": "$msCompile" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /Controllers/LocationController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace WebHost.Controllers 9 | { 10 | [ApiController] 11 | [Route("api/[controller]")] 12 | public class LocationController : ControllerBase 13 | { 14 | private readonly ILogger _logger; 15 | public LocationController(ILogger logger) 16 | { 17 | _logger = logger; 18 | } 19 | 20 | [HttpGet] 21 | public IEnumerable GetLocations() 22 | { 23 | return NightDriver.ConsoleApp.Locations; 24 | } 25 | 26 | [HttpGet("{id:int}")] 27 | public NightDriver.Location GetLocation(int id) 28 | { 29 | if (id < 0 || id > NightDriver.ConsoleApp.Locations.Length) 30 | throw new ArgumentOutOfRangeException("id"); 31 | 32 | return NightDriver.ConsoleApp.Locations[id]; 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #Stage 1 2 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 3 | WORKDIR /build 4 | COPY . . 5 | RUN dotnet restore 6 | RUN dotnet publish -c Release -o /docker --no-restore 7 | 8 | # Stage 2 9 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS final 10 | ENV TZ=America/Los_Angeles 11 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 12 | WORKDIR /docker 13 | COPY --from=build /docker . 14 | ENTRYPOINT ["dotnet", "NightDriverServer.dll"] 15 | -------------------------------------------------------------------------------- /NightDriverServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | b711dd49-063f-45a1-82e4-220fd7effbfa 6 | true 7 | True 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /NightDriverServer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30406.217 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NightDriverServer", "NightDriverServer.csproj", "{8EB8AFE9-C498-4960-B880-D66EB72148C0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {8EB8AFE9-C498-4960-B880-D66EB72148C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {8EB8AFE9-C498-4960-B880-D66EB72148C0}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {8EB8AFE9-C498-4960-B880-D66EB72148C0}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {8EB8AFE9-C498-4960-B880-D66EB72148C0}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {F600EE10-D84F-489A-913D-F6AB49A0EA57} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | using System.Threading; 11 | using System.IO; 12 | using NightDriver; 13 | 14 | namespace WebHost 15 | { 16 | public class Program 17 | { 18 | public static void Main(string[] args) 19 | { 20 | Thread ledControlThread = new Thread(NightDriver.ConsoleApp.Start); 21 | ledControlThread.IsBackground = true; 22 | 23 | var host = CreateHostBuilder(args).Build(); 24 | var hostTask = host.StartAsync(); 25 | 26 | ledControlThread.Start(); 27 | 28 | while (ledControlThread.IsAlive) 29 | Thread.Sleep(1000); 30 | 31 | host.StopAsync(); 32 | } 33 | 34 | public static IHostBuilder CreateHostBuilder(string[] args) => 35 | Host.CreateDefaultBuilder(args) 36 | .ConfigureWebHostDefaults(webBuilder => 37 | { 38 | webBuilder.UseStartup(); 39 | }); 40 | } 41 | } -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52979", 7 | "sslPort": 44305 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "weatherforecast", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "WebHost": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "index.html", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 28 | }, 29 | "WSL 2": { 30 | "commandName": "WSL2", 31 | "launchBrowser": true, 32 | "launchUrl": "https://localhost:5001/index.html", 33 | "environmentVariables": { 34 | "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000", 35 | "ASPNETCORE_ENVIRONMENT": "Development" 36 | }, 37 | "distributionName": "" 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NightDriverServer 2 | 3 | ## Purpose 4 | 5 | The purpose of the NightDriverServer project is to serve as a "demo" of how to send color data over WiFi to a NightDriverStrip instance on an ESP32. You must have the equivalent of the "LEDSTRIP" proejct running with wifi and incoming data enabled (which LEDSTRIP does by default). When run, the ESP32 will connect to WiFi and wait for TCP connections on port 49152. 6 | 7 | While this demo is in C#, any lanugage that can create a byte array and send it to a socket will work. I've done examples in C++, Python, and C# in the past. 8 | 9 | ## Packet Format 10 | 11 | The format for a packet of color data, a single frame, is: 12 | 13 | The function LightStrip::GetDataFrame is the one that puts together the packet and returns a byte array to be sent to the socket. 14 | 15 | ```csharp 16 | var data = GetPixelData(MainLEDs); 17 | return 18 | LEDInterop.CombineByteArrays(LEDInterop.WORDToBytes(WIFI_COMMAND_PIXELDATA64), 19 | LEDInterop.WORDToBytes((UInt16)Channel), // LED channel on ESP32 20 | LEDInterop.DWORDToBytes((UInt32)data.Length / 3), // Number of LEDs 21 | LEDInterop.ULONGToBytes(seconds), // Timestamp seconds (64 bit) 22 | LEDInterop.ULONGToBytes(uSeconds), // Timestmap microseconds (64 bit) 23 | data); // Color Data 24 | ``` 25 | 26 | The packet format, then, starts with a WORD magic cookie of WIFI_COMMAND_PIXELDATA64, which is 3. 27 | The next WORD is the LED channel (or 0 for 'all channels'.) 28 | The next DWORD is the number of LEDs being set 29 | The next ULONG (64 bits) is the timestamp (whole seconds) 30 | The next ULONG (64 bits) is the timestamp (microseconds) 31 | Then the raw color data in RGB format (one 3-byte RGB triplet per LED being sent) 32 | 33 | This demo sets the timestamp to be a second or two in the future, depending on how much RAM the NightDriverStrip has, so that each strip can hold its data until the timestamp comes due and then they all show it at the same time, which is how multiple strips stay in sync. 34 | 35 | ## Compressed Packets 36 | 37 | This data may optionally be compressed. In the function CompressFame, you can see where the above packet is wrapped in a compression packet. The format of a compressed packet is: 38 | 39 | First DWORD is the header tag: 0x44415645 (just a magic value) 40 | Second DWORD is compressed data length in bytes 41 | Third DWORD is the original data length in bytes 42 | Fourth DWORD is unused, set to 0x12345678 for now 43 | Then the bytes of the original color data packet in LZCompressed format 44 | 45 | You can see how the original packet is compressed in the Compress function, which uses a ZLIBStream to make the lzcompressed data in a manner that can be decompressed by the ESP32 that is receiving the data. 46 | 47 | ## How the Server Works 48 | 49 | The program creates an array of Location objects. A Location implements the GraphicsBase interface so you can call GetPixel and SetPixel and so on. 50 | 51 | The main function here is DrawAndEnqueueAll, which figures out which effect is currently running. It then calls that effect's DrawFrame call which in turn will draw into the Location. Once the drawing is complete it calls CompressAndEnqueueData on the controller object, which will put the data into a queue, one per location. 52 | 53 | Each Location has a peer thread running an LEDControllerChannel, and it is that controller that talks directly to the socket on the strip, sending a packer every frame. 54 | 55 | ## How to Cheat and Get Started 56 | 57 | None of this complexity is needed uness you have many locations. If you were just sending to a single strip, you could simply write a loop that sent frames to the strip 20 times per second. If you set the timestamps to 0, it should draw that frame immediately instead of buffering it. That's the quickest way to get something running in a new lanugage or project. 58 | -------------------------------------------------------------------------------- /Server/Adler32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ZLIB 4 | { 5 | public class Adler32 6 | { 7 | #region "Variables globales" 8 | private UInt32 a = 1; 9 | private UInt32 b = 0; 10 | private const int _base = 65521; 11 | private const int _nmax = 5550; 12 | private int pend = 0; 13 | #endregion 14 | #region "Metodos publicos" 15 | public void Update(byte data) 16 | { 17 | if (pend >= _nmax) updateModulus(); 18 | a += data; 19 | b += a; 20 | pend++; 21 | } 22 | public void Update(byte[] data) 23 | { 24 | Update(data, 0, data.Length); 25 | } 26 | public void Update(byte[] data, int offset, int length) 27 | { 28 | int nextJToComputeModulus = _nmax - pend; 29 | for (int j = 0; j < length; j++) { 30 | if (j == nextJToComputeModulus) { 31 | updateModulus(); 32 | nextJToComputeModulus = j + _nmax; 33 | } 34 | unchecked { 35 | a += data[j + offset]; 36 | } 37 | b += a; 38 | pend++; 39 | } 40 | } 41 | public void Reset() 42 | { 43 | a = 1; 44 | b = 0; 45 | pend = 0; 46 | } 47 | private void updateModulus() 48 | { 49 | a %= _base; 50 | b %= _base; 51 | pend = 0; 52 | } 53 | public UInt32 GetValue() 54 | { 55 | if (pend > 0) updateModulus(); 56 | return (b << 16) | a; 57 | } 58 | #endregion 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Server/Effects/BouncingBallEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using NightDriver; 5 | 6 | public class BouncingBallEffect : LEDEffect 7 | { 8 | public bool Reversed; 9 | public double Gravity = -9.81; 10 | public double StartHeight = 1.0; 11 | public uint BallSize = 1; 12 | 13 | double ImpactVelocityStart; 14 | public int BallCount = 3; 15 | 16 | double[] ClockTimeSinceLastBounce; 17 | double[] TimeSinceLastBounce; 18 | double[] Height; 19 | double[] ImpactVelocity; 20 | double[] Dampening; 21 | 22 | static readonly CRGB[] Colors = { CRGB.Red, CRGB.Blue, CRGB.Green, CRGB.Orange, CRGB.Cyan, CRGB.Yellow, CRGB.Purple }; 23 | 24 | // Start is called before the first frame update 25 | public BouncingBallEffect() 26 | { 27 | ImpactVelocityStart = Math.Sqrt(-2 * Gravity * StartHeight); 28 | 29 | ClockTimeSinceLastBounce = new double[BallCount]; 30 | TimeSinceLastBounce = new double[BallCount]; 31 | Height = new double[BallCount]; 32 | ImpactVelocity = new double[BallCount]; 33 | Dampening = new double[BallCount]; 34 | 35 | for (int i = 0; i < BallCount; i++) 36 | { 37 | Height[i] = StartHeight; 38 | ImpactVelocity[i] = ImpactVelocityStart; 39 | ClockTimeSinceLastBounce[i] = DateTime.UtcNow.Ticks / (double) TimeSpan.TicksPerSecond; 40 | Dampening[i] = 1.00f - i / Math.Pow(BallCount, 2); 41 | TimeSinceLastBounce[i] = 0.0f; 42 | } 43 | } 44 | 45 | protected override void Render(ILEDGraphics graphics) 46 | { 47 | graphics.FillSolid(CRGB.Black); 48 | 49 | for (uint i = 0; i < BallCount; i++) 50 | { 51 | TimeSinceLastBounce[i] = (DateTime.UtcNow.Ticks / (double) TimeSpan.TicksPerSecond - ClockTimeSinceLastBounce[i]) / 5; 52 | Height[i] = 0.5f * Gravity * Math.Pow(TimeSinceLastBounce[i], 2.0f) + ImpactVelocity[i] * TimeSinceLastBounce[i]; 53 | 54 | if (Height[i] < 0) 55 | { 56 | Height[i] = 0; 57 | ImpactVelocity[i] = Dampening[i] * ImpactVelocity[i]; 58 | ClockTimeSinceLastBounce[i] = DateTime.UtcNow.Ticks / (double) TimeSpan.TicksPerSecond; 59 | 60 | if (ImpactVelocity[i] < 0.1f) 61 | ImpactVelocity[i] = ImpactVelocityStart; 62 | } 63 | 64 | double position = Height[i] * (graphics.DotCount - 1) / StartHeight; 65 | 66 | if (Reversed) 67 | graphics.DrawPixels((uint)(graphics.DotCount - 1 - position), BallSize, Colors[i]); 68 | else 69 | graphics.DrawPixels((uint)(position), BallSize, Colors[i]); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /Server/Effects/CabanaYardLight.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using NightDriver; 5 | 6 | namespace NightDriver 7 | { 8 | public class CabanaYardLight : LEDEffect 9 | { 10 | // Update is called once per frame 11 | 12 | void FillRange(ILEDGraphics graphics, uint x, uint len, CRGB color) 13 | { 14 | for (uint i = x; i < x + len; i++) 15 | graphics.DrawPixel(i, 0, color); 16 | } 17 | 18 | protected override void Render(ILEDGraphics graphics) 19 | { 20 | CRGB color = CRGB.Incandescent; 21 | 22 | graphics.FillSolid(CRGB.Black); 23 | 24 | 25 | for (uint i = 0; i < graphics.DotCount; i += 16) 26 | graphics.DrawPixel(i, 0, CRGB.White.fadeToBlackBy(0.5f)); 27 | 28 | FillRange(graphics, 0, 5 * 144 - 1, color); 29 | FillRange(graphics, 11*144, 5 * 144 +54, color); 30 | FillRange(graphics, 19*144 + 117, 2 * 144-24, color); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Server/Effects/FireEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NightDriver; 3 | 4 | public class FireEffect : LEDEffect 5 | { 6 | public bool _Mirrored; // Should the flame be "mirrored" - drawn on both ends 7 | public bool _Reversed; // Only applicable when not mirrored, of course, flips it to other side 8 | public double _Cooling = 1000f; // Amount that each pixel fades through temp each frame 9 | public int _Sparks = 1; // Number of white-hot ignition sparks chances per frame 10 | public int _Drift = 1; // Number of drift passes per frame 11 | public int _SparkHeight = 12; // Height of region in which sparks can be created 12 | public bool _Afterburners = false; // A visually intense, white-hot flame (vs. nice campfire) 13 | public uint _Size = 0; // How big the drawing area is; will be repeated if strip is bigger 14 | public double _SparkProbability = 0.5; // Chance of a new spark 15 | double[] _Temperatures; // Internal table of cell temperatures 16 | 17 | public Palette _Palette; 18 | 19 | public uint _CellsPerLED = 1; 20 | 21 | public Random _rand = new Random(); 22 | 23 | public FireEffect(uint cSize, bool bMirrored, uint cellsPerLED = 1, Palette palette = null) 24 | { 25 | _CellsPerLED = cellsPerLED; 26 | 27 | _Size = cSize * _CellsPerLED; 28 | _Mirrored = bMirrored; 29 | _Palette = palette; 30 | } 31 | 32 | DateTime _lastUpdate = DateTime.UtcNow; 33 | 34 | protected virtual CRGB ConvertHeatToColor(double temp) 35 | { 36 | if (_Palette is null) 37 | return CRGB.GetBlackbodyHeatColor(temp); 38 | temp = Math.Min(1.0, temp); 39 | return _Palette[temp]; 40 | } 41 | 42 | protected override void Render(ILEDGraphics graphics) 43 | { 44 | if (_Temperatures == null) 45 | _Temperatures = new double[_Size]; 46 | 47 | _lastUpdate = DateTime.UtcNow; 48 | 49 | // Erase the drawing area 50 | graphics.FillSolid(CRGB.Black); 51 | 52 | double cooldown = _rand.NextDouble() * _Cooling * (DateTime.UtcNow - _lastUpdate).TotalSeconds; 53 | _lastUpdate = DateTime.UtcNow; 54 | 55 | for (int i = 0; i < _Temperatures.Length; i++) 56 | { 57 | if (cooldown > _Temperatures[i]) 58 | { 59 | _Temperatures[i] = 0; 60 | } 61 | else 62 | { 63 | _Temperatures[i] = _Temperatures[i] - (double) cooldown; 64 | } 65 | } 66 | 67 | // Heat from each cell drifts 'up' and diffuses a little 68 | 69 | for (int pass = 0; pass < _Drift; pass++) 70 | for (int k = _Temperatures.Length - 1; k >= 2; k--) 71 | _Temperatures[k] = (_Temperatures[k - 1] + _Temperatures[k - 2]) / 2.0f; 72 | 73 | // Randomly ignite new 'sparks' near the bottom 74 | 75 | for (int frame = 0; frame < _Sparks; frame++) 76 | { 77 | if (_rand.NextDouble() < _SparkProbability) 78 | { 79 | // NB: This randomly rolls over sometimes of course, and that's essential to the effect 80 | int y = (int)(_rand.NextDouble() * _SparkHeight); 81 | _Temperatures[y] = (_Temperatures[y] + _rand.NextDouble() * 0.4 + 0.06); 82 | 83 | if (!_Afterburners) 84 | while (_Temperatures[y] > 1.0) 85 | _Temperatures[y] -= 1.0f; 86 | else 87 | _Temperatures[y] = Math.Min(_Temperatures[y], 1.0f); 88 | } 89 | } 90 | 91 | // Hack for weird TV layout 92 | for (int j = _Temperatures.Length; j < graphics.DotCount; j++) 93 | { 94 | graphics.DrawPixel((uint) j, 0, ConvertHeatToColor(_Temperatures[j-(uint) _Temperatures.Length])); 95 | } 96 | 97 | if (_Mirrored) 98 | { 99 | uint len = _Size / _CellsPerLED; 100 | 101 | for (uint j = 0; j < len / 2; j+=1) 102 | { 103 | if (_Reversed) 104 | { 105 | graphics.DrawPixel(j, 0, ConvertHeatToColor(_Temperatures[j * _CellsPerLED])); 106 | graphics.DrawPixel((uint)(len - 1 - j), 0, ConvertHeatToColor(_Temperatures[j * _CellsPerLED])); 107 | 108 | } 109 | else 110 | { 111 | graphics.DrawPixel(((uint)(len - 1 - j)), 0, ConvertHeatToColor(_Temperatures[j * _CellsPerLED])); 112 | graphics.DrawPixel(j, 0, ConvertHeatToColor(_Temperatures[j * _CellsPerLED])); 113 | } 114 | } 115 | } 116 | else 117 | { 118 | for (uint j = 0; j < _Temperatures.Length; j+=_CellsPerLED) 119 | { 120 | uint len = _Size / _CellsPerLED; 121 | uint i = j / _CellsPerLED; 122 | 123 | if (_Reversed) 124 | graphics.DrawPixel(i, 0, ConvertHeatToColor(_Temperatures[j])); 125 | else 126 | graphics.DrawPixel((uint)(len - 1 - i), 0, ConvertHeatToColor(_Temperatures[j])); 127 | } 128 | } 129 | 130 | graphics.Blur(1); 131 | } 132 | } 133 | 134 | public class PaletteFire : FireEffect 135 | { 136 | protected Palette palette; 137 | 138 | public PaletteFire(uint cSize, bool bMirrored, Palette pal) : base(cSize, bMirrored) 139 | { 140 | palette = pal; 141 | } 142 | 143 | protected override CRGB ConvertHeatToColor(double temp) 144 | { 145 | return palette[Math.Min(1.0, temp)]; 146 | } 147 | } 148 | 149 | public class BlueFire : FireEffect 150 | { 151 | public BlueFire(uint cSize, bool bMirrored) : base(cSize, bMirrored) 152 | { 153 | } 154 | 155 | protected override CRGB ConvertHeatToColor(double temp) 156 | { 157 | temp = Math.Min(1.0f, temp); 158 | byte temperature = (byte)(255 * temp); 159 | byte t192 = (byte)Math.Round((temperature / 255.0f) * 191); 160 | 161 | byte heatramp = (byte)(t192 & 0x3F); 162 | heatramp <<= 2; 163 | 164 | if (t192 > 0x80) 165 | return new CRGB(heatramp, 255, 255); 166 | else if (t192 > 0x40) 167 | return new CRGB(0, heatramp, 255); 168 | else 169 | return new CRGB(0, 0, heatramp); 170 | } 171 | } 172 | 173 | -------------------------------------------------------------------------------- /Server/Effects/FireworksEffect.cs: -------------------------------------------------------------------------------- 1 | //+-------------------------------------------------------------------------- 2 | // 3 | // NightDriver - (c) 2018 Dave Plummer 4 | // 5 | // File: FireworksEffect.cs - Draw's little fireworks particle system 6 | // 7 | // Description: 8 | // 9 | // Part of NightDriver 10 | // 11 | // History: Oct-12-2018 davepl Created 12 | // Jun-06-2018 davepl C# rewrite from C++ 13 | // 14 | //--------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using NightDriver; 19 | 20 | // Fireworks Effect for Fourth of July 21 | 22 | public class FireworksEffect : LEDEffect 23 | { 24 | // Each particle int the particle system remembers its color, 25 | // birth time, postion, velocity, etc. 26 | 27 | public class Particle 28 | { 29 | public CRGB _starColor; 30 | public DateTime _birthTime; 31 | public DateTime _lastUpdate; 32 | public double _velocity; 33 | public double _position; 34 | 35 | public Random _rand = new Random(); 36 | 37 | public Particle(CRGB starColor, double pos, double maxSpeed) 38 | { 39 | _position = pos; 40 | _velocity = _rand.NextDouble() * maxSpeed * 2 - maxSpeed; 41 | _starColor = starColor; 42 | _birthTime = DateTime.UtcNow; 43 | _lastUpdate = DateTime.UtcNow; 44 | } 45 | 46 | public double Age 47 | { 48 | get 49 | { 50 | return (DateTime.UtcNow - _birthTime).TotalSeconds; 51 | } 52 | } 53 | 54 | // As the particle ages we actively fade its color and slow its speed 55 | 56 | public void Update() 57 | { 58 | double deltaTime = (DateTime.UtcNow - _lastUpdate).TotalSeconds; 59 | _position += _velocity * deltaTime; 60 | _lastUpdate = DateTime.UtcNow; 61 | 62 | _velocity -= (2 * _velocity * deltaTime); 63 | 64 | _starColor = _starColor.fadeToBlackBy((double)_rand.NextDouble() * 0.1f); 65 | } 66 | } 67 | 68 | // A Palette256 is just a way of picking a random color from the rainbow. If you can provide 69 | // a random color, you're set... this is just the easiest mechanism I had handy. 70 | 71 | protected Palette _Palette = new Palette(CRGB.Rainbow); 72 | 73 | public bool Blend = true; 74 | public double MaxSpeed = 375.0; // Max velocity in pixels per second 75 | public double NewParticleProbability = 1.0; // Odds of a new particle each pass 76 | public double ParticlePreignitonTime = 0.0; // How long to "wink" into existence 77 | public double ParticleIgnition = 0.2; // How long to "flash" white at birth 78 | public double ParticleHoldTime = 0.00; // Main lifecycle time 79 | public double ParticleFadeTime = 2.0; // Fade out time 80 | public double ParticleSize = 1.00; // Size of the particle (0 is min) 81 | 82 | protected Queue _Particles = new Queue(); // Keeps track of particles in FIFO 83 | 84 | private Random _random = new Random(); 85 | 86 | // All drawing is done in Render, which produces one frame by calling the draw methods on the supplied 87 | // graphics interface. As long as you support "Draw a pixel" you should be able to make it work with 88 | // whatever your're using... 89 | 90 | protected override void Render(ILEDGraphics graphics) 91 | { 92 | // Randomly create some new stars this frame; the number we create is tied to the size of the display 93 | // so that the display size can change and the "effect density" will stay the same 94 | 95 | for (int iPass = 0; iPass < Math.Max(5, graphics.DotCount / 50); iPass++) 96 | { 97 | if (_random.NextDouble() < NewParticleProbability * 0.005) 98 | { 99 | // Pick a random color and location. If you don't have FastLED palettes, all you need to do 100 | // here is generate a random color. 101 | 102 | uint iStartPos = (uint)(_random.NextDouble() * graphics.DotCount); 103 | CRGB color = CRGB.RandomSaturatedColor; // _Palette.ColorFromPalette(_random.Next(0, _Palette.FullSize), 1.0f, false); 104 | int c = _random.Next(10, 50); 105 | double multiplier = _random.NextDouble() * 3; 106 | 107 | for (int i = 1; i < c; i++) 108 | { 109 | Particle particle = new Particle(color, iStartPos, MaxSpeed * _random.NextDouble() * multiplier); 110 | _Particles.Enqueue(particle); 111 | } 112 | } 113 | } 114 | 115 | // In the degenerate case of particles not aging out for some reason, we need to set a pseudo-realistic upper 116 | // bound, and the very number of possible pixels seems like a reasonable one 117 | 118 | while (_Particles.Count > graphics.DotCount * 5) 119 | _Particles.Dequeue(); 120 | 121 | // Start out with an empty canvas 122 | 123 | graphics.FillSolid(CRGB.Black); 124 | 125 | foreach (Particle star in _Particles) 126 | { 127 | star.Update(); 128 | 129 | CRGB c = new CRGB(star._starColor); 130 | 131 | // If the star is brand new, it flashes white briefly. Otherwise it fades over time. 132 | 133 | double fade = 0.0f; 134 | if (star.Age > ParticlePreignitonTime && star.Age < ParticleIgnition + ParticlePreignitonTime) 135 | { 136 | c = CRGB.White; 137 | } 138 | else 139 | { 140 | // Figure out how much to fade and shrink the star based on its age relative to its lifetime 141 | 142 | double age = star.Age; 143 | if (age < ParticlePreignitonTime) 144 | fade = 1.0 - (age / ParticlePreignitonTime); 145 | else 146 | { 147 | age -= ParticlePreignitonTime; 148 | 149 | if (age < ParticleHoldTime + ParticleIgnition) 150 | fade = 0.0f; // Just born 151 | else if (age > ParticleHoldTime + ParticleIgnition + ParticleFadeTime) 152 | fade = 1.0f; // Black hole, all faded out 153 | else 154 | { 155 | age -= (ParticleHoldTime + ParticleIgnition); 156 | fade = (age / ParticleFadeTime); // Fading star 157 | } 158 | } 159 | c = c.fadeToBlackBy((double)fade); 160 | } 161 | ParticleSize = (1 - fade) * (graphics.DotCount / 500.0); 162 | ParticleSize = Math.Max(1, ParticleSize); 163 | graphics.DrawPixels(star._position, ParticleSize, c); 164 | 165 | } 166 | 167 | // Remove any particles who have completed their lifespan 168 | 169 | while (_Particles.Count > 0 && _Particles.Peek().Age > ParticleHoldTime + ParticleIgnition + ParticleFadeTime) 170 | _Particles.Dequeue(); 171 | } 172 | } 173 | 174 | -------------------------------------------------------------------------------- /Server/Effects/MeteorEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using NightDriver; 5 | 6 | public class Meteor 7 | { 8 | public double _hue; 9 | public double _pos; 10 | public bool _bGoingLeft; 11 | public double _speed; 12 | public double _meteorSize; 13 | public bool _bBounce; 14 | 15 | public void Reverse() 16 | { 17 | _bGoingLeft = !_bGoingLeft; 18 | } 19 | 20 | public Meteor(double hue, double pos, bool bGoingLeft, bool bBounce, double speed, double size) 21 | { 22 | _hue = hue; 23 | _pos = pos; 24 | _bGoingLeft = bGoingLeft; 25 | _speed = speed; 26 | _meteorSize = size; 27 | } 28 | 29 | public void Draw(ILEDGraphics graphics) 30 | { 31 | 32 | _pos = (_bGoingLeft) ? _pos - _speed : _pos + _speed; 33 | if (_pos < 0) 34 | _pos += graphics.DotCount; 35 | if (_pos >= graphics.DotCount) 36 | _pos -= graphics.DotCount; 37 | 38 | if (_bBounce) 39 | { 40 | if (_pos < _meteorSize) 41 | { 42 | _bGoingLeft = false; 43 | _pos = _meteorSize; 44 | } 45 | if (_pos >= graphics.DotCount) 46 | { 47 | _bGoingLeft = true; 48 | _pos = graphics.DotCount - 1; 49 | } 50 | } 51 | 52 | for (double j = 0; j < _meteorSize; j++) // Draw the meteor head 53 | { 54 | double x = (_pos - j); 55 | if (x < graphics.DotCount && x >= 0) 56 | { 57 | _hue = _hue + 0.50f; 58 | _hue %= 360; 59 | CRGB color = CRGB.HSV2RGB(_hue, 1.0, 0.8); 60 | graphics.DrawPixels(x, 1, color); 61 | } 62 | } 63 | } 64 | } 65 | 66 | // A meteor that zooms through the strip 67 | public class MeteorEffect : LEDEffect 68 | { 69 | protected ILEDGraphics _graphics; 70 | protected int _meteorCount; 71 | protected double _meteorTrailDecay; 72 | protected double _meteorSpeedMin; 73 | protected double _meteorSpeedMax; 74 | protected Meteor [] _Meteors; 75 | protected double _hueval; 76 | 77 | protected bool _bFirstDraw = true; 78 | 79 | public MeteorEffect(int dotCount, 80 | bool bBounce = true, 81 | int meteorCount = 4, 82 | int meteorSize = 4, 83 | double trailDecay = 1.0, 84 | double minSpeed = 0.5, 85 | double maxSpeed = 0.5) 86 | { 87 | meteorCount += 1; 88 | meteorCount /= 2; // Force an even number of meteors 89 | meteorCount *= 2; 90 | 91 | _hueval = 0; 92 | _meteorCount = meteorCount; 93 | _meteorTrailDecay = trailDecay; 94 | _meteorSpeedMin = minSpeed; 95 | _meteorSpeedMax = maxSpeed; 96 | 97 | _Meteors = new Meteor[meteorCount]; 98 | int halfCount = meteorCount / 2; 99 | 100 | for (int i = 0; i < halfCount; i++) 101 | { 102 | _hueval += 20; 103 | _hueval %= 360; 104 | 105 | _Meteors[i] = new Meteor(_hueval, 106 | i * dotCount / halfCount, 107 | true, 108 | bBounce, 109 | Utilities.RandomDouble(_meteorSpeedMin, _meteorSpeedMax), 110 | meteorSize); 111 | int j = halfCount + i; 112 | _Meteors[j] = new Meteor(_hueval, 113 | i * dotCount / halfCount, 114 | false, 115 | bBounce, 116 | Utilities.RandomDouble(_meteorSpeedMin, _meteorSpeedMax), 117 | meteorSize); 118 | 119 | } 120 | } 121 | 122 | // Update is called once per frame 123 | 124 | protected override void Render(ILEDGraphics graphics) 125 | { 126 | if (_bFirstDraw) 127 | { 128 | graphics.FillSolid(CRGB.Black); 129 | _bFirstDraw = false; 130 | } 131 | 132 | for (uint j = 0; j < graphics.DotCount; j++) 133 | { 134 | if ((_meteorTrailDecay == 0) || (Utilities.RandomByte() > 64)) 135 | { 136 | CRGB c = graphics.GetPixel(j, 0); 137 | //c.fadeToBlackBy(_meteorTrailDecay); 138 | c *= _meteorTrailDecay; 139 | graphics.DrawPixel(j, 0, c); 140 | } 141 | } 142 | 143 | for (int i = 0; i < _meteorCount; i++) 144 | if (null != _Meteors[i]) 145 | _Meteors[i].Draw(graphics); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /Server/Effects/PaletteEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using NightDriver; 5 | 6 | 7 | public class PaletteEffect : LEDEffect 8 | { 9 | private double _iPixel = 0; 10 | 11 | protected double _iColor; 12 | protected ILEDGraphics _graphics; 13 | protected DateTime _lastDraw = DateTime.UtcNow; 14 | 15 | public Palette _Palette = new Palette(CRGB.Rainbow); 16 | public double _LEDColorPerSecond = -50.0; 17 | public double _LEDScrollSpeed = 50.0; 18 | public double _Density = 1.0; 19 | public double _EveryNthDot = 25.0f; 20 | public uint _DotSize = 5; 21 | public bool _RampedColor = false; 22 | public double _Brightness = 1.0; 23 | public bool _Mirrored = false; 24 | 25 | public PaletteEffect(Palette palette) 26 | { 27 | _Palette = palette; 28 | _iColor = 0; 29 | } 30 | 31 | // Update is called once per frame 32 | 33 | protected override void Render(ILEDGraphics graphics) 34 | { 35 | graphics.FillSolid(CRGB.Black); 36 | 37 | double secondsElapsed = (DateTime.UtcNow - _lastDraw).TotalSeconds; 38 | _lastDraw = DateTime.UtcNow; 39 | 40 | double cPixelsToScroll = secondsElapsed * _LEDScrollSpeed; 41 | _iPixel += cPixelsToScroll; 42 | _iPixel %= graphics.DotCount; 43 | 44 | double cColorsToScroll = secondsElapsed * _LEDColorPerSecond; 45 | _iColor += cColorsToScroll / graphics.PixelsPerMeter; 46 | _iColor -= (long)_iColor; 47 | 48 | double iColor = _iColor; 49 | 50 | uint cLength = (_Mirrored ? graphics.DotCount / 2 : graphics.DotCount); 51 | 52 | for (double i = 0; i < cLength; i += _EveryNthDot) 53 | { 54 | int count = 0; 55 | for (uint j = 0; j < _DotSize && (i + j) < cLength; j++) 56 | { 57 | double iPixel = (i + j + _iPixel) % cLength; 58 | 59 | CRGB c = _Palette[iColor].fadeToBlackBy(1.0 - _Brightness); 60 | 61 | double cCenter = graphics.DotCount / 2.0; 62 | 63 | graphics.DrawPixels(iPixel + (_Mirrored ? cCenter : 0), 1, c); 64 | if (_Mirrored) 65 | graphics.DrawPixels(cCenter - iPixel, 1, c); 66 | count++; 67 | 68 | } 69 | // Avoid pixel 0 flicker as it scrolls by copying pixel 1 onto 0 70 | 71 | if (graphics.DotCount > 1) 72 | graphics.DrawPixel(0, graphics.GetPixel(1)); 73 | 74 | iColor += count * (_Density / graphics.PixelsPerMeter) * _EveryNthDot; 75 | } 76 | } 77 | 78 | } 79 | 80 | // PresentsEffect 81 | // 82 | // An effect I created for three decorative boxes of 24 LEDs each, it's a simple inner palette rainbow 83 | // but with a "sparkle" effect every so often on top of that 84 | 85 | class PresentsEffect : LEDEffect 86 | { 87 | PaletteEffect _InnerEffect = new PaletteEffect(Palette.SmoothRainbow) 88 | { 89 | _Density = 4, 90 | _EveryNthDot = 1, 91 | _DotSize = 1, 92 | _LEDColorPerSecond = 75, 93 | _LEDScrollSpeed = 0 94 | }; 95 | 96 | public PresentsEffect() 97 | { 98 | } 99 | 100 | protected override void Render(ILEDGraphics graphics) 101 | { 102 | _InnerEffect.DrawFrame(graphics); 103 | 104 | // We divide by 2 so that out flashing lasts two seconds, not one. 105 | 106 | if (DateTime.UtcNow.Second % 10 == 0) 107 | { 108 | var fraction = DateTime.UtcNow.Millisecond / 1000.0; 109 | var passes = (Math.Sin(fraction * Math.PI)) * graphics.DotCount / 5.0; 110 | //ConsoleApp.Stats.WriteLine(passes.ToString()); 111 | for (double i = 0; i < passes; i += 1) 112 | { 113 | graphics.DrawPixel((uint)(Utilities.RandomDouble() * graphics.DotCount), 0, CRGB.White); 114 | } 115 | } 116 | } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /Server/Effects/StarEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NightDriver; 4 | 5 | 6 | public class StarEffect : LEDEffect where StarType : BaseStar 7 | { 8 | protected Queue _Stars = new Queue(); 9 | 10 | private Random _random = new Random(DateTime.Now.Millisecond); 11 | private DateTime _lastBaseColorUpdate = DateTime.UtcNow; 12 | 13 | public Palette Palette = new Palette(CRGB.Rainbow); 14 | public bool Blend = false; 15 | public double MaxSpeed = 2.0f; 16 | public double NewStarProbability = 1; 17 | public double StarPreignitonTime = 0.5f; 18 | public double StarIgnition = 0.25f; 19 | public double StarHoldTime = 1.00f; 20 | public double StarFadeTime = 1.00f; 21 | public double StarSize = 1.00f; 22 | public double ColorSpeed = 0.0; 23 | public double BaseColorSpeed = 0.0; 24 | public bool RandomStartColor = false; 25 | public bool RandomStarColorSpeed = true; 26 | public int Direction = 0; 27 | public uint Alignment = 0; 28 | public bool RampedColor = false; 29 | private double CurrentStartColor = 0; 30 | 31 | 32 | protected override void Render(ILEDGraphics graphics) 33 | { 34 | double elapsed = (DateTime.UtcNow - _lastBaseColorUpdate).TotalSeconds; 35 | _lastBaseColorUpdate = DateTime.UtcNow; 36 | 37 | CurrentStartColor += BaseColorSpeed * elapsed * 16; 38 | 39 | for (int iPass = 0; iPass < graphics.DotCount / 10; iPass++) 40 | { 41 | if (_random.NextDouble() < NewStarProbability / 100.0) 42 | { 43 | double d = _random.NextDouble(); 44 | if (d == 0) 45 | _random = new Random(DateTime.Now.Millisecond); 46 | 47 | double pos = (d * graphics.DotCount); 48 | BaseStar star; 49 | double colorD = RandomStartColor ? _random.NextDouble() : CurrentStartColor; 50 | double speedD = RandomStarColorSpeed? _random.NextDouble() * ColorSpeed : ColorSpeed; 51 | 52 | // Create a star, which is done differently depending in which kind of star it is (hue vs palette). Should 53 | // probably convert this to a factory pattern that can produce any type of Star, but this is a quick hack 54 | // to test things that will no doubt hand around untilt he the third or fourth type enters the scene... 55 | 56 | if (typeof(StarType).IsAssignableFrom(typeof(ColorStar))) 57 | star = new ColorStar(pos, 58 | MaxSpeed, 59 | Direction, 60 | colorD, 61 | speedD); 62 | 63 | else if (typeof(StarType).IsAssignableFrom(typeof(AlignedPaletteStar))) 64 | star = new AlignedPaletteStar(pos, 65 | MaxSpeed, 66 | Direction, 67 | Palette, 68 | colorD, 69 | speedD, 70 | Alignment); 71 | 72 | else if (typeof(StarType).IsAssignableFrom(typeof(PaletteStar))) 73 | star = new PaletteStar(pos, 74 | MaxSpeed, 75 | Direction, 76 | Palette, 77 | colorD, 78 | speedD); 79 | else 80 | star = null; 81 | 82 | _Stars.Enqueue(star); 83 | } 84 | } 85 | 86 | while (_Stars.Count > graphics.DotCount) 87 | _Stars.Dequeue(); 88 | 89 | graphics.FillSolid(CRGB.Black); 90 | 91 | foreach (BaseStar star in _Stars) 92 | { 93 | star.Update(); 94 | 95 | CRGB c = new CRGB(star.StarColor); 96 | 97 | // If the star is brand new, it flashes white briefly. Otherwise it fades over time. 98 | double fade = 0.0f; 99 | double age = star.Age; 100 | 101 | double TimeToFadeOut = StarPreignitonTime + StarIgnition + StarHoldTime + StarFadeTime; 102 | double TimeToHold = StarPreignitonTime + StarIgnition + StarHoldTime; 103 | double TimeToIgnite = StarPreignitonTime + StarIgnition; 104 | double TimeToFadeIn = StarPreignitonTime; 105 | 106 | if (age < TimeToFadeIn) 107 | { 108 | // In FadeIn 109 | fade = 1.0 - (age / TimeToFadeIn); 110 | } 111 | else if (age < TimeToIgnite) 112 | { 113 | // In Ignition (white) 114 | c = CRGB.White; 115 | } 116 | else if (age < TimeToHold) 117 | { 118 | // In Hold 119 | fade = 0.0; 120 | } 121 | else if (age <= TimeToFadeOut) 122 | { 123 | // In FadeOut 124 | double fadeTime = age - TimeToHold; 125 | fade = fadeTime / StarFadeTime; 126 | } 127 | else 128 | { 129 | // Dark matter (past it's lifetime, waiting to be removed) 130 | c = CRGB.Black; 131 | } 132 | 133 | c = c.fadeToBlackBy((double)fade); 134 | 135 | if (!RampedColor) 136 | { 137 | graphics.DrawPixels(star._position, (uint)StarSize, c); 138 | } 139 | else 140 | { 141 | double half = StarSize / 2; 142 | 143 | CRGB c1; 144 | for (int j = 0; j < StarSize; j++) 145 | { 146 | double dx = Math.Abs(half - j); 147 | dx /= half; 148 | 149 | c1 = c.fadeToBlackBy(dx); 150 | 151 | if ((int)j == (int)half) 152 | c1 = c1.blendWith(CRGB.White, 0.75); 153 | 154 | graphics.DrawPixels(star._position + j, 1, c1); 155 | } 156 | } 157 | 158 | } 159 | while (_Stars.Count > 0 && _Stars.Peek().Age > StarPreignitonTime + StarIgnition + StarHoldTime + StarFadeTime) 160 | _Stars.Dequeue(); 161 | } 162 | } 163 | 164 | // BaseStar 165 | // 166 | // Star class that keeps track of the basics - position and color. Position is updated in the Update() loop but color 167 | // must be managed by the derived class or set externally. 168 | 169 | public class BaseStar 170 | { 171 | protected CRGB _starColor; 172 | public CRGB StarColor 173 | { 174 | get { return _starColor; } 175 | set { _starColor = value; } 176 | } 177 | 178 | protected Random _rand = new Random(); 179 | 180 | public double _position; 181 | public double _velocity; 182 | public DateTime _birthTime; 183 | public DateTime _lastUpdate; 184 | 185 | public double Age 186 | { 187 | get 188 | { 189 | return (DateTime.UtcNow - _birthTime).TotalSeconds; 190 | } 191 | } 192 | 193 | public BaseStar(double pos, double maxSpeed = 0, int direction = 0) 194 | { 195 | 196 | _starColor = CRGB.Red; 197 | _birthTime = DateTime.UtcNow; 198 | _velocity = _rand.NextDouble() * maxSpeed * 2 - maxSpeed; 199 | 200 | if (direction != 0) 201 | _velocity = Math.Abs(_velocity) * Math.Sign(direction); 202 | 203 | // If no speed, use an int-rounded slot so we don't span pixels 204 | 205 | _position = (maxSpeed == 0) ? (uint) pos : pos; 206 | 207 | _lastUpdate = DateTime.UtcNow; 208 | 209 | } 210 | 211 | public virtual void Update() 212 | { 213 | double deltaSeconds = (DateTime.UtcNow - _lastUpdate).TotalSeconds; 214 | 215 | _position += _velocity * deltaSeconds; 216 | _lastUpdate = DateTime.UtcNow; 217 | } 218 | } 219 | 220 | // PaletteStar 221 | // 222 | // A BaseStar derivation that keeps track of a palette and sweeps the star color through that palette 223 | 224 | public class PaletteStar : BaseStar 225 | { 226 | protected DateTime _lastColorChange; 227 | protected double _paletteSpeed; 228 | protected Palette _palette; 229 | protected double _paletteIndex; 230 | 231 | public PaletteStar(double pos, double maxSpeed, int direction, Palette palette, double paletteIndex, double paletteSpeed = 0.0) : base(pos, maxSpeed, direction) 232 | { 233 | _palette = palette; 234 | _lastUpdate = DateTime.UtcNow; 235 | _lastColorChange = DateTime.UtcNow; 236 | _paletteSpeed = paletteSpeed; 237 | _paletteIndex = paletteIndex; 238 | _palette.Blend = false; 239 | } 240 | 241 | public override void Update() 242 | { 243 | double deltaSeconds = (DateTime.UtcNow - _lastUpdate).TotalSeconds; 244 | 245 | base.Update(); // Resets _lastUpdate, so get value first 246 | 247 | _paletteIndex += _paletteSpeed * deltaSeconds; // 248 | _paletteIndex -= (long) _paletteIndex; // Just keep digits 249 | StarColor = _palette[_paletteIndex]; 250 | } 251 | } 252 | 253 | // AlignedPaletteStar 254 | // 255 | // A derivation of star that alawys stays aligned on some multiple for postion (like every 8th pixel) 256 | 257 | public class AlignedPaletteStar : PaletteStar 258 | { 259 | protected uint _alignment; 260 | 261 | public AlignedPaletteStar(double pos, double maxSpeed, int direction, Palette palette, double paletteIndex, double paletteSpeed = 0.0, uint alignment = 8) 262 | : base(pos, maxSpeed, direction, palette, paletteIndex, paletteSpeed) 263 | { 264 | _alignment = alignment; 265 | } 266 | 267 | public override void Update() 268 | { 269 | if (_alignment > 1) 270 | { 271 | long x = (int)_position; 272 | x = (x + (_alignment - 1)) / _alignment * _alignment; 273 | _position = x; 274 | } 275 | base.Update(); 276 | } 277 | } 278 | 279 | // ColorStar 280 | // 281 | // A derivation of BaseStar that keeps track of it's current hue and changes it over time 282 | 283 | public class ColorStar : BaseStar 284 | { 285 | protected double _hue; 286 | protected double _hueSpeed; 287 | 288 | public ColorStar(double pos, double maxSpeed, int direction, double startHue, double hueSpeed) : base(pos, maxSpeed, direction) 289 | { 290 | _hue = startHue; 291 | _hueSpeed = hueSpeed; 292 | StarColor = CRGB.HSV2RGB(startHue); 293 | } 294 | 295 | public override void Update() 296 | { 297 | double deltaSeconds = (DateTime.UtcNow - _lastUpdate).TotalSeconds; // Get the value BEFORE base class Update resets the timer 298 | 299 | base.Update(); 300 | _hue += deltaSeconds * _hueSpeed * 22.5; // Sixteen steps through the pallete, just like the palette star 301 | _hue %= 360; 302 | StarColor = CRGB.HSV2RGB(_hue); 303 | } 304 | } 305 | 306 | -------------------------------------------------------------------------------- /Server/Effects/TestEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using NightDriver; 5 | 6 | namespace NightDriver 7 | { 8 | public class SimpleColorFillEffect : LEDEffect 9 | { 10 | // Update is called once per frame 11 | 12 | protected CRGB _color; 13 | protected uint _everyNth; 14 | 15 | public SimpleColorFillEffect(CRGB color, uint everyNth = 10) 16 | { 17 | _everyNth = everyNth; 18 | _color = color; 19 | } 20 | 21 | protected override void Render(ILEDGraphics graphics) 22 | { 23 | graphics.FillSolid(CRGB.Black); 24 | for (uint i = 0; i < graphics.DotCount; i+=_everyNth) 25 | graphics.DrawPixel(i, _color); 26 | } 27 | }; 28 | 29 | 30 | public class RainbowEffect : LEDEffect 31 | { 32 | // Update is called once per frame 33 | protected double _deltaHue; 34 | protected double _startHue; 35 | protected double _hueSpeed; 36 | 37 | DateTime _lastDraw = DateTime.UtcNow; 38 | 39 | public RainbowEffect(double deltaHue = 0, double hueSpeed = 5) 40 | { 41 | _deltaHue = deltaHue; 42 | _startHue = 0; 43 | _hueSpeed = hueSpeed; 44 | } 45 | 46 | protected override void Render(ILEDGraphics graphics) 47 | { 48 | double delta = _hueSpeed * (double)(DateTime.UtcNow - _lastDraw).TotalSeconds; 49 | _lastDraw = DateTime.UtcNow; 50 | _startHue = (_startHue + delta); 51 | 52 | // BUGBUG It stymies me as to why one is modulus 360 and the other is 256! (davepl) 53 | 54 | CRGB color = CRGB.HSV2RGB(_startHue % 360); 55 | if (_deltaHue == 0.0) 56 | { 57 | graphics.FillSolid(color); 58 | } 59 | else 60 | { 61 | graphics.FillRainbow(_startHue % 360, _deltaHue); 62 | graphics.Blur(3); 63 | } 64 | //Console.WriteLine(delta.ToString() + ", : " + _startHue.ToString() + "r = " + color.r + " g = " + color.g + " b = " + color.b); 65 | } 66 | }; 67 | 68 | 69 | public class TestEffect : LEDEffect 70 | { 71 | private uint _startIndex; 72 | private uint _length; 73 | private CRGB _color; 74 | 75 | public TestEffect(uint startIndex, uint length, CRGB color) 76 | { 77 | _startIndex = startIndex; 78 | _length = length; 79 | _color = color; 80 | } 81 | 82 | DateTime _lastDraw = DateTime.UtcNow; 83 | 84 | // Update is called once per frame 85 | 86 | protected override void Render(ILEDGraphics graphics) 87 | { 88 | for (uint i = _startIndex; i < _startIndex + _length; i++) 89 | graphics.DrawPixel(i, _color); 90 | 91 | /* 92 | uint third = _length / 3; 93 | 94 | for (uint i = _startIndex; i < third + _startIndex; i++) 95 | graphics.DrawPixel(i, CRGB.Blue); 96 | 97 | for (uint i = third + _startIndex; i < third * 2 + _startIndex; i++) 98 | graphics.DrawPixel(i, CRGB.Red); 99 | 100 | for (uint i = third * 2 + _startIndex; i < _length + _startIndex; i++) 101 | graphics.DrawPixel(i, CRGB.Green); 102 | */ 103 | 104 | graphics.DrawPixel(_startIndex, CRGB.White); 105 | graphics.DrawPixel(_startIndex+1, CRGB.Black); 106 | graphics.DrawPixel(_startIndex+_length-1, CRGB.White); 107 | graphics.DrawPixel(_startIndex+_length-2, CRGB.Black); 108 | 109 | } 110 | } 111 | 112 | 113 | 114 | 115 | } -------------------------------------------------------------------------------- /Server/Effects/TimeOfNightEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NightDriver 4 | { 5 | public class TimeOfNightEffect : LEDEffect 6 | { 7 | int zone = -8; // Seattle time Zone 8 | double latitude = 47.6; // Seattle lat 9 | double longitude = -122.32; // Seattle lon 10 | bool dst = true; // Day Light Savings 11 | 12 | public TimeOfNightEffect() 13 | { 14 | } 15 | 16 | DateTime _lastPrint = DateTime.Now; 17 | 18 | protected override void Render(ILEDGraphics graphics) 19 | { 20 | var julianDate = SunriseSunset.calcJD(DateTime.Today); 21 | double sunRiseUTC = SunriseSunset.calcSunRiseUTC(julianDate, latitude, longitude); 22 | double sunSetUTC = SunriseSunset.calcSunSetUTC(julianDate, latitude, longitude); 23 | DateTime? sunRiseN = SunriseSunset.getDateTime(sunRiseUTC, zone, DateTime.Today, dst); 24 | DateTime? sunSetN = SunriseSunset.getDateTime(sunSetUTC, zone, DateTime.Today, dst); 25 | 26 | if (!sunRiseN.HasValue || !sunSetN.HasValue) 27 | { 28 | ConsoleApp.Stats.WriteLine("NO DATA calculated in TimeOfNightEffect"); 29 | return; 30 | } 31 | 32 | DateTime sunRise = sunRiseN.Value; 33 | DateTime sunSet = sunSetN.Value; 34 | 35 | double secondsOfDay = (sunSet - sunRise).TotalSeconds; 36 | double secondsOfNight = (sunRise + TimeSpan.FromDays(1) - sunSet).TotalSeconds; 37 | 38 | bool bIsDark; 39 | double fractionOfPeriod; 40 | 41 | if (DateTime.Now > sunSet) 42 | { 43 | // Nighttime - after sunset 44 | bIsDark = true; 45 | fractionOfPeriod = (DateTime.Now - sunSet).TotalSeconds / secondsOfNight; 46 | } 47 | else if (DateTime.Now < sunRise) 48 | { 49 | // Nighttime - before sunrise 50 | bIsDark = true; 51 | fractionOfPeriod = (DateTime.Now - (sunSet - TimeSpan.FromDays(1))).TotalSeconds / secondsOfNight; 52 | } 53 | else 54 | { 55 | // (DateTime.Now sunSet) 56 | bIsDark = false; 57 | fractionOfPeriod = (DateTime.Now - sunRise).TotalSeconds / secondsOfDay; 58 | } 59 | 60 | if (DateTime.Now - _lastPrint > TimeSpan.FromSeconds(1)) 61 | { 62 | _lastPrint = DateTime.Now; 63 | String s = String.Format("Dark: {0}, Fraction: {1}", bIsDark, fractionOfPeriod); 64 | ConsoleApp.Stats.WriteLine(s); 65 | } 66 | //Conso1eApp.Stats.WriteLine("Render"); 67 | graphics.FillSolid(CRGB.Black); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Server/FastLEDInterop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using ZLIB; 4 | using System.Linq; 5 | using System.Collections.Generic; 6 | using System.Collections.Concurrent; 7 | 8 | namespace NightDriver 9 | { 10 | public static class MathExtensions 11 | { 12 | public static decimal Map(this decimal value, decimal fromSource, decimal toSource, decimal fromTarget, decimal toTarget) 13 | { 14 | return (value - fromSource) / (toSource - fromSource) * (toTarget - fromTarget) + fromTarget; 15 | } 16 | } 17 | 18 | public static class QueueExtensions 19 | { 20 | public static IEnumerable DequeueChunk(this ConcurrentQueue queue, int chunkSize) 21 | { 22 | for (int i = 0; i < chunkSize && queue.Count > 0; i++) 23 | { 24 | T result; 25 | if (false == queue.TryDequeue(out result)) 26 | throw new Exception("Unable to Dequeue the data!"); 27 | yield return result; 28 | } 29 | } 30 | } 31 | 32 | // LEDInterop 33 | // 34 | // Functions fdor working directly with the color data in an LED string 35 | 36 | public static class LEDInterop 37 | { 38 | // scale8 - Given a value i, scales it down by scale/256th 39 | 40 | public static byte scale8(byte i, byte scale) 41 | { 42 | return (byte)(((ushort)i * (ushort)(scale)) >> 8); 43 | } 44 | 45 | public static byte scale8_video(byte i, byte scale) 46 | { 47 | byte j = (byte)((((int)i * (int)scale) >> 8) + ((i != 0 && scale != 0) ? 1 : 0)); 48 | return j; 49 | } 50 | 51 | // fill_solid - fills a rnage of LEDs with a given color value 52 | 53 | public static void fill_solid(CRGB[] leds, CRGB color) 54 | { 55 | for (int i = 0; i < leds.Length; i++) 56 | leds[i] = color; 57 | } 58 | 59 | // fill_rainbow - fills a range of LEDs rotating through a hue wheel 60 | 61 | public static void fill_rainbow(CRGB[] leds, byte initialHue, double deltaHue) 62 | { 63 | double hue = initialHue; 64 | for (int i = 0; i < leds.Length; i++, hue += deltaHue) 65 | leds[i] = CRGB.HSV2RGB(hue % 360, 1.0, 1.0); 66 | } 67 | 68 | // GetColorBytes - get the color data as a packaged up array of bytes 69 | 70 | /* 71 | public static byte[] GetColorBytes(CRGB[] leds) 72 | { 73 | byte[] data = new byte[leds.Length * 3]; 74 | for (int i = 0; i < leds.Length; i++) 75 | { 76 | data[i * 3] = leds[i].r; 77 | data[i * 3 + 1] = leds[i].g; 78 | data[i * 3 + 2] = leds[i].b; 79 | } 80 | return data; 81 | } 82 | */ 83 | 84 | // GetColorBytesAtOffset 85 | // 86 | // Reach into the main array of CRGBs and grab the color bytes for a strand 87 | 88 | public static byte [] GetColorBytesAtOffset(CRGB [] main, uint offset, uint length, bool bReversed = false, bool bRedGreenSwap = false) 89 | { 90 | byte[] data = new byte[length * 3]; 91 | for (int i = 0; i < length; i++) 92 | { 93 | if (bRedGreenSwap) 94 | { 95 | data[i * 3] = bReversed ? main[offset + length - 1 - i].r : main[offset + i].g; 96 | data[i * 3 + 1] = bReversed ? main[offset + length - 1 - i].g : main[offset + i].r; 97 | } 98 | else 99 | { 100 | data[i * 3] = bReversed ? main[offset + length - 1 - i].r : main[offset + i].r; 101 | data[i * 3 + 1] = bReversed ? main[offset + length - 1 - i].g : main[offset + i].g; 102 | } 103 | data[i * 3 + 2] = bReversed ? main[offset + length - 1 - i].b : main[offset+i].b; 104 | } 105 | return data; 106 | } 107 | 108 | // When a strip is physically reversed, it's quicker to extract the color data in the 109 | // reverse order in the first place than it is to extracr and reverse it in two steps, 110 | // so we provide this methiod to support that 111 | 112 | /* 113 | public static byte[] GetColorBytesReversed(CRGB[] leds) 114 | { 115 | byte[] data = new byte[leds.Length * 3]; 116 | for (int i = 0; i < leds.Length; i++) 117 | { 118 | data[i * 3] = leds[leds.Length - 1 - i].r; 119 | data[i * 3 + 1] = leds[leds.Length - 1 - i].g; 120 | data[i * 3 + 2] = leds[leds.Length - 1 - i].b; 121 | } 122 | return data; 123 | } 124 | */ 125 | // DWORD and WORD to Bytes - Flatten 16 and 32 bit values to memory 126 | 127 | public static byte[] ULONGToBytes(UInt64 input) 128 | { 129 | return new byte[8] 130 | { 131 | (byte)((input ) & 0xff), 132 | (byte)((input >> 8) & 0xff), 133 | (byte)((input >> 16) & 0xff), 134 | (byte)((input >> 24) & 0xff), 135 | (byte)((input >> 32) & 0xff), 136 | (byte)((input >> 40) & 0xff), 137 | (byte)((input >> 48) & 0xff), 138 | (byte)((input >> 56) & 0xff), 139 | }; 140 | } 141 | 142 | public static byte[] DWORDToBytes(UInt32 input) 143 | { 144 | return new byte[4] 145 | { 146 | (byte)((input ) & 0xff), 147 | (byte)((input >> 8) & 0xff), 148 | (byte)((input >> 16) & 0xff), 149 | (byte)((input >> 24) & 0xff), 150 | }; 151 | } 152 | 153 | public static byte[] WORDToBytes(UInt16 input) 154 | { 155 | return new byte[2] 156 | { 157 | (byte)((input ) & 0xff), 158 | (byte)((input >> 8) & 0xff), 159 | }; 160 | } 161 | 162 | // CombineByteArrays - Combine N arrays and returns them as one new big new one 163 | 164 | public static byte[] CombineByteArrays(params byte[][] arrays) 165 | { 166 | byte[] rv = new byte[arrays.Sum(a => a.Length)]; 167 | int offset = 0; 168 | foreach (byte[] array in arrays) 169 | { 170 | System.Buffer.BlockCopy(array, 0, rv, offset, array.Length); 171 | offset += array.Length; 172 | } 173 | return rv; 174 | } 175 | 176 | // CompressMemory 177 | // 178 | // Compress a buffer using ZLIB, return the compressed version of it as a ZLIB stream 179 | 180 | public static byte[] Compress(byte[] data) 181 | { 182 | using (var compressedStream = new MemoryStream()) 183 | using (var zipStream = new ZLIBStream(compressedStream, System.IO.Compression.CompressionLevel.Optimal)) 184 | { 185 | zipStream.Write(data, 0, data.Length); 186 | zipStream.Close(); 187 | return compressedStream.ToArray(); 188 | } 189 | } 190 | 191 | // DecompressMemory 192 | // 193 | // Expands a buffer using ZLib, returns the uncompressed version of it 194 | 195 | public static byte[] Decompress(byte[] input) 196 | { 197 | using (var inStream = new MemoryStream(input)) 198 | using (var bigStream = new ZLIBStream(inStream, System.IO.Compression.CompressionMode.Decompress)) 199 | using (var bigStreamOut = new MemoryStream()) 200 | { 201 | bigStream.CopyTo(bigStreamOut); 202 | return bigStreamOut.ToArray(); 203 | } 204 | } 205 | } 206 | } 207 | 208 | -------------------------------------------------------------------------------- /Server/LEDControllerChannel.cs: -------------------------------------------------------------------------------- 1 | //+-------------------------------------------------------------------------- 2 | // 3 | // NightDriver.Net - (c) 2019 Dave Plummer. All Rights Reserved. 4 | // 5 | // File: LEDControllerChannel.cs 6 | // 7 | // Description: 8 | // 9 | // Represents a specific channel on a particular strip and exposes the 10 | // GraphicsBase class for drawing directly on it. 11 | // 12 | // Each instance has a worker thread that manages keeping the socket 13 | // connected and sending it the data that has been queued up for it. 14 | // 15 | // History: Jun-15-2019 Davepl Created 16 | // 17 | //--------------------------------------------------------------------------- 18 | 19 | using System; 20 | using System.Collections.Concurrent; 21 | using System.Net; 22 | using System.Net.Sockets; 23 | using System.Linq; 24 | using System.Threading; 25 | using System.Collections.Generic; 26 | using System.Runtime.InteropServices; 27 | using System.IO; 28 | using System.Runtime.Serialization; 29 | using System.Runtime.Serialization.Formatters.Binary; 30 | 31 | // LEDControllerChannel 32 | // 33 | // Exposes ILEDGraphics via the GraphicsBase baseclass 34 | // Abstract until deriving class implements GetDataFrame() 35 | 36 | namespace NightDriver 37 | { 38 | 39 | [Serializable] 40 | public struct SocketResponse 41 | { 42 | public UInt32 size; 43 | public UInt32 flashVersion; 44 | public double currentClock; 45 | public double oldestPacket; 46 | public double newestPacket; 47 | public double brightness; 48 | public double wifiSignal; 49 | public UInt32 bufferSize; 50 | public UInt32 bufferPos; 51 | public UInt32 fpsDrawing; 52 | public UInt32 watts; 53 | 54 | 55 | public void Reset() 56 | { 57 | size = 0; 58 | flashVersion = 0; 59 | currentClock = 0; 60 | oldestPacket = 0; 61 | newestPacket = 0; 62 | brightness = 0; 63 | wifiSignal = 0; 64 | bufferSize = 0; 65 | bufferPos = 0; 66 | fpsDrawing = 0; 67 | watts = 0; 68 | } 69 | }; 70 | 71 | public class LEDControllerChannel 72 | { 73 | public string HostName; 74 | public string FriendlyName; 75 | public bool CompressData = true; 76 | public byte Channel = 0; 77 | public double Brightness = 1.0f; 78 | public uint Connects = 0; 79 | public uint Watts = 0; 80 | public bool RedGreenSwap = false; 81 | 82 | public Location Location; 83 | 84 | public SocketResponse Response; 85 | 86 | public int BatchSize = 1; 87 | public const double BatchTimeout = 1.00; 88 | 89 | private ConcurrentQueue DataQueue = new ConcurrentQueue(); 90 | 91 | private Thread _Worker; 92 | 93 | public int QueueDepth 94 | { 95 | get 96 | { 97 | return DataQueue.Count; 98 | } 99 | } 100 | public const int MaxQueueDepth = 99; 101 | 102 | public uint Offset 103 | { 104 | get; 105 | protected set; 106 | } 107 | 108 | public uint Width 109 | { 110 | get; 111 | protected set; 112 | } 113 | 114 | public uint Height 115 | { 116 | get; 117 | protected set; 118 | } 119 | 120 | protected LEDControllerChannel(string hostName, 121 | string friendlyName, 122 | uint width, 123 | uint height = 1, 124 | uint offset = 0, 125 | bool compressData = true, 126 | byte channel = 0, 127 | byte watts = 0, 128 | bool swapRedGreen = false, 129 | int batchSize = 1) 130 | { 131 | HostName = hostName; 132 | FriendlyName = friendlyName; 133 | Width = width; 134 | Height = height; 135 | Channel = channel; 136 | Offset = offset; 137 | Watts = watts; 138 | CompressData = compressData; 139 | RedGreenSwap = swapRedGreen; 140 | BatchSize = batchSize; 141 | 142 | _Worker = new Thread(WorkerConnectAndSendLoop); 143 | _Worker.Name = hostName + " Connect and Send Loop"; 144 | _Worker.IsBackground = true; 145 | _Worker.Priority = ThreadPriority.BelowNormal; 146 | _Worker.Start(); 147 | } 148 | 149 | 150 | internal bool HasSocket // Is there a socket at all yet? 151 | { 152 | get 153 | { 154 | ControllerSocket controllerSocket = ControllerSocketForHost(HostName); 155 | if (null == controllerSocket || controllerSocket._socket == null) 156 | return false; 157 | return true; 158 | } 159 | } 160 | 161 | internal bool ReadyForData // Is there a socket and is it connected to the chip? 162 | { 163 | get 164 | { 165 | ControllerSocket controllerSocket = ControllerSocketForHost(HostName); 166 | if (null == controllerSocket || 167 | controllerSocket._socket == null || 168 | !controllerSocket._socket.Connected || 169 | QueueDepth > MaxQueueDepth) 170 | return false; 171 | return true; 172 | } 173 | } 174 | 175 | internal uint MinimumSpareTime => (uint)_HostControllerSockets.Min(controller => controller.Value.BytesPerSecond); 176 | 177 | public uint BytesPerSecond 178 | { 179 | get 180 | { 181 | ControllerSocket controllerSocket = ControllerSocketForHost(HostName); 182 | if (null == controllerSocket || controllerSocket._socket == null) 183 | return 0; 184 | return controllerSocket.BytesPerSecond; 185 | } 186 | } 187 | 188 | public uint TotalBytesPerSecond => (uint)_HostControllerSockets.Sum(controller => controller.Value.BytesPerSecond); 189 | 190 | public ControllerSocket ControllerSocket 191 | { 192 | get 193 | { 194 | return ControllerSocketForHost(HostName); 195 | } 196 | } 197 | 198 | public static ControllerSocket ControllerSocketForHost(string host) 199 | { 200 | if (_HostControllerSockets.ContainsKey(host)) 201 | { 202 | _HostControllerSockets.TryGetValue(host, out ControllerSocket controller); 203 | return controller; 204 | } 205 | return null; 206 | } 207 | 208 | /* BUGBUG Could be abstract except for serialization */ 209 | 210 | protected virtual byte[] GetDataFrame(CRGB[] MainLEDs, DateTime timeStart) 211 | { 212 | throw new ApplicationException("Should never hit base class GetDataFrame"); 213 | } 214 | 215 | /* 216 | protected virtual byte[] GetClockFrame(DateTime timeStart) 217 | { 218 | // The timeOffset is how far in the future frames are generated for. If the chips have a 2 second buffer, you could 219 | // go up to 2 seconds, but I shoot for the middle of the buffer depth. 220 | 221 | double epoch = (DateTime.UtcNow.Ticks - DateTime.UnixEpoch.Ticks) / (double)TimeSpan.TicksPerSecond; 222 | ulong seconds = (ulong)epoch; // Whole part of time number (left of the decimal point) 223 | ulong uSeconds = (ulong)((epoch - (int)epoch) * 1000000); // Fractional part of time (right of the decimal point) 224 | 225 | return LEDInterop.CombineByteArrays(LEDInterop.WORDToBytes((UInt16)2), // Command, which is 2 for us 226 | LEDInterop.WORDToBytes((UInt16)0), // LED channel on ESP32 227 | LEDInterop.ULONGToBytes((UInt64)seconds), // Number of LEDs 228 | LEDInterop.ULONGToBytes((UInt64)uSeconds) // Timestamp seconds 229 | ); // Color Data 230 | } 231 | */ 232 | byte[] CompressFrame(byte[] data) 233 | { 234 | const int COMPRESSED_HEADER_TAG = 0x44415645; // Magic "DAVE" tag for compressed data - replaces size field 235 | byte[] compressedData = LEDInterop.Compress(data); 236 | byte[] compressedFrame = LEDInterop.CombineByteArrays(LEDInterop.DWORDToBytes((uint)COMPRESSED_HEADER_TAG), 237 | LEDInterop.DWORDToBytes((uint)compressedData.Length), 238 | LEDInterop.DWORDToBytes((uint)data.Length), 239 | LEDInterop.DWORDToBytes(0x12345678), 240 | compressedData); 241 | return compressedFrame; 242 | } 243 | 244 | DateTime _timeLastSend = DateTime.UtcNow; 245 | DateTime _lastBatchTime = DateTime.UtcNow; 246 | 247 | // _HostSockets 248 | // 249 | // We can only have one socket open per ESP32 chip per channel, so this concurrent dictionary keeps track of which sockets are 250 | // open to what chips so that the socket can be reused. 251 | 252 | static ConcurrentDictionary _HostControllerSockets = new ConcurrentDictionary(); 253 | 254 | private int _iPacketCount = 0; 255 | 256 | public bool CompressAndEnqueueData(CRGB[] MainLEDs, DateTime timeStart) 257 | { 258 | // If there is already a socket open, we will use that; otherwise, a new connection will be opened and if successful 259 | // it will be placed into the _HostSockets concurrent dictionary 260 | 261 | if (_HostControllerSockets.ContainsKey(HostName) == false && (DateTime.UtcNow - _timeLastSend).TotalSeconds < 2) 262 | { 263 | ConsoleApp.Stats.WriteLine("Too early to retry for " + HostName); 264 | return false; 265 | } 266 | _timeLastSend = DateTime.UtcNow; 267 | 268 | ControllerSocket controllerSocket = ControllerSocketForHost(HostName); 269 | if (null == controllerSocket) 270 | _HostControllerSockets[HostName] = controllerSocket; 271 | 272 | if (QueueDepth > MaxQueueDepth) 273 | { 274 | ConsoleApp.Stats.WriteLine("Queue full so dicarding frame for " + HostName); 275 | return false; 276 | } 277 | 278 | // Swap color bytes for strips that need it 279 | 280 | if (RedGreenSwap) 281 | { 282 | foreach (var led in MainLEDs) 283 | { 284 | var temp = led.r; 285 | led.r = led.g; 286 | led.g = temp; 287 | } 288 | } 289 | 290 | // Optionally compress the data, but when we do, if the compressed is larger, we send the original 291 | 292 | byte[] msgraw = GetDataFrame(MainLEDs, timeStart); 293 | byte[] msg = CompressData ? CompressFrame(msgraw) : msgraw; 294 | if (msg.Length >= msgraw.Length) 295 | { 296 | msg = msgraw; 297 | } 298 | 299 | DataQueue.Enqueue(msg); 300 | _iPacketCount++; 301 | return true; 302 | } 303 | 304 | bool ShouldSendBatch 305 | { 306 | get 307 | { 308 | if (Location is null) 309 | return false; 310 | 311 | if (DataQueue.Count() > Location.FramesPerSecond) // If a full second has accumulated 312 | return true; 313 | 314 | if (DataQueue.Any()) 315 | if ((DateTime.UtcNow - _lastBatchTime).TotalSeconds > BatchTimeout) 316 | return true; 317 | 318 | if (DataQueue.Count() >= BatchSize) 319 | return true; 320 | 321 | return false; 322 | } 323 | } 324 | // WorkerConnectAndSendLoop 325 | // 326 | // Every controller has a worker thread that sits and spins in a thread loop doing the work of connecting to 327 | // the chips, pulling data from the queues, and sending it off 328 | 329 | void WorkerConnectAndSendLoop() 330 | { 331 | // We delay-start a random fraction of a quarter second to stagger the workload so that the WiFi is a little more balanced 332 | 333 | for (; ; ) 334 | { 335 | ControllerSocket controllerSocket 336 | = _HostControllerSockets.GetOrAdd(HostName, (hostname) => 337 | { 338 | Connects++; 339 | //ConsoleApp.Stats.WriteLine("Connecting to " + HostName); 340 | return new ControllerSocket(hostname); 341 | }); 342 | 343 | 344 | if (QueueDepth >= MaxQueueDepth) 345 | { 346 | DataQueue.Clear(); 347 | ConsoleApp.Stats.WriteLine("Discarding data for jammed socket: " + HostName); 348 | ControllerSocket oldSocket; 349 | _HostControllerSockets.TryRemove(HostName, out oldSocket); 350 | continue; 351 | } 352 | 353 | 354 | if (false == controllerSocket.EnsureConnected()) 355 | { 356 | //ConsoleApp.Stats.WriteLine("Closing disconnected socket: " + HostName); 357 | ControllerSocket oldSocket; 358 | _HostControllerSockets.TryRemove(HostName, out oldSocket); 359 | continue; 360 | } 361 | 362 | // Compose a message which is a binary block of N (where N is up to Count) dequeue packets all 363 | // in a row, which is how the chips can actually process them 364 | 365 | if (ShouldSendBatch) 366 | { 367 | _lastBatchTime = DateTime.UtcNow; 368 | 369 | byte[] msg = LEDInterop.CombineByteArrays(DataQueue.DequeueChunk(DataQueue.Count()).ToArray()); 370 | if (msg.Length > 0) 371 | { 372 | try 373 | { 374 | uint bytesSent = 0; 375 | if (!controllerSocket.IsDead) 376 | bytesSent = controllerSocket.SendData(msg, ref Response); 377 | 378 | if (bytesSent != msg.Length) 379 | { 380 | ConsoleApp.Stats.WriteLine("Could not write all bytes so closing socket for " + HostName); 381 | ControllerSocket oldSocket; 382 | _HostControllerSockets.TryRemove(HostName, out oldSocket); 383 | } 384 | else 385 | { 386 | // Console.WriteLine("Sent " + bytesSent + " to " + HostName); 387 | double framesPerSecond = (double)((DateTime.UtcNow - _timeLastSend).TotalSeconds); 388 | } 389 | } 390 | catch (SocketException ex) 391 | { 392 | ConsoleApp.Stats.WriteLine("Exception writing to socket for " + HostName + ": " + ex.Message); 393 | ControllerSocket oldSocket; 394 | _HostControllerSockets.TryRemove(HostName, out oldSocket); 395 | } 396 | } 397 | } 398 | 399 | Thread.Sleep(10); 400 | } 401 | } 402 | } 403 | 404 | // ControllerSocket 405 | // 406 | // Wrapper for .Net Socket so that we can track the number of bytes sent and so on 407 | 408 | [Serializable] 409 | public class ControllerSocket 410 | { 411 | public Socket _socket; 412 | private IPAddress _ipAddress; 413 | private IPEndPoint _remoteEP; 414 | 415 | private DateTime LastDataFrameTime; 416 | 417 | private uint BytesSentSinceFrame = 0; 418 | public string HostName { get; set; } 419 | 420 | public bool IsDead { get; protected set; } = false; 421 | public string FirmwareVersion { get; set; } 422 | 423 | public uint BytesPerSecond 424 | { 425 | get 426 | { 427 | double d = (DateTime.UtcNow - LastDataFrameTime).TotalSeconds; 428 | if (d < 0.001) 429 | return 0; 430 | 431 | return (uint)(BytesSentSinceFrame / d); 432 | } 433 | } 434 | 435 | public ControllerSocket(string hostname) 436 | { 437 | var entry = Dns.GetHostByName(hostname); 438 | _ipAddress = entry.AddressList[0]; 439 | _remoteEP = new IPEndPoint(_ipAddress, 49152); 440 | return; 441 | 442 | // I also wrote the async version, but am staying sync above for simplicity right now 443 | // 444 | // HostName = hostname; 445 | // ConsoleApp.Stats.WriteLine("Fetching hostnamae for " + hostname); 446 | // _remoteEP = null; 447 | // Dns.BeginGetHostAddresses(HostName, OnDnsGetHostAddressesComplete, this); 448 | } 449 | 450 | private void OnDnsGetHostAddressesComplete(IAsyncResult result) 451 | { 452 | try 453 | { 454 | if (result.IsCompleted) 455 | { 456 | var This = (ControllerSocket)result.AsyncState; 457 | This._ipAddress = Dns.EndGetHostAddresses(result)[0]; 458 | This._remoteEP = new IPEndPoint(_ipAddress, 49152); 459 | ConsoleApp.Stats.WriteLine("Got IP of " + _remoteEP.Address.ToString() + " for " + This.HostName); 460 | } 461 | else 462 | IsDead = true; 463 | } 464 | catch (Exception) 465 | { 466 | ConsoleApp.Stats.WriteLine("DNS Exception: " + HostName); 467 | IsDead = true; 468 | } 469 | } 470 | 471 | // EnsureConnected 472 | // 473 | // If not already connected, initiates the connection so that perhaps next time we will ideally be connected 474 | 475 | public bool EnsureConnected() 476 | { 477 | if (IsDead == true) 478 | return false; 479 | 480 | if (_socket != null && _socket.Connected) 481 | return true; 482 | 483 | if (_remoteEP == null) 484 | return false; 485 | 486 | try 487 | { 488 | if (DateTime.UtcNow - LastDataFrameTime < TimeSpan.FromSeconds(1)) 489 | { 490 | ConsoleApp.Stats.WriteLine("Bailing connection as too early!"); 491 | return true; 492 | } 493 | LastDataFrameTime = DateTime.UtcNow; 494 | _socket = new Socket(_ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 495 | 496 | _socket.Connect(_remoteEP); 497 | 498 | BytesSentSinceFrame = 0; 499 | ConsoleApp.Stats.WriteLine("Connected to " + _remoteEP); 500 | 501 | return true; 502 | } 503 | catch (SocketException) 504 | { 505 | IsDead = true; 506 | return false; 507 | } 508 | } 509 | 510 | // SocketResponse 511 | // 512 | // The response structure sent back to us when we deliver a frame of data to the NightDriverStrip 513 | 514 | unsafe public uint SendData(byte[] data, ref SocketResponse response) 515 | { 516 | uint result = (uint)_socket.Send(data); 517 | if (result != data.Length) 518 | { 519 | IsDead = true; 520 | 521 | return result; 522 | } 523 | 524 | TimeSpan timeSinceLastSend = DateTime.UtcNow - LastDataFrameTime; 525 | if (timeSinceLastSend > TimeSpan.FromSeconds(10.0)) 526 | { 527 | LastDataFrameTime = DateTime.UtcNow; 528 | BytesSentSinceFrame = 0; 529 | } 530 | else 531 | { 532 | BytesSentSinceFrame += result; 533 | } 534 | 535 | DateTime startWaiting = DateTime.UtcNow; 536 | // Receive the response back from the socket we just sent to 537 | int cbToRead = sizeof(SocketResponse); 538 | byte[] buffer = new byte[cbToRead]; 539 | 540 | // Wait until there's enough data to process or we've waited 5 seconds with no result 541 | 542 | //while (DateTime.UtcNow - startWaiting > TimeSpan.FromSeconds(5) && _socket.Available < cbToRead) 543 | // Thread.Sleep(100); 544 | 545 | while (_socket.Available >= cbToRead) 546 | { 547 | var readBytes = _socket.Receive(buffer, cbToRead, SocketFlags.None); 548 | if (readBytes >= sizeof(SocketResponse) && buffer[0] >= sizeof(SocketResponse)) 549 | { 550 | GCHandle pinnedArray = GCHandle.Alloc(buffer, GCHandleType.Pinned); 551 | IntPtr pointer = pinnedArray.AddrOfPinnedObject(); 552 | response = Marshal.PtrToStructure(pointer); 553 | pinnedArray.Free(); 554 | } 555 | FirmwareVersion = "v" + response.flashVersion; 556 | } 557 | return result; 558 | } 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /Server/LEDEffect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using NightDriver; 5 | 6 | [Serializable] 7 | public class LEDEffect 8 | { 9 | public virtual string EffectName 10 | { 11 | get 12 | { 13 | return this.GetType().Name; 14 | } 15 | } 16 | 17 | protected virtual void Render(ILEDGraphics graphics) 18 | { 19 | // BUGBUG class and this methoi would be abstract except for serialization requiremets // BUGBUG What? What serialization? 20 | throw new ApplicationException("Render Base Class called - This is abstract"); 21 | } 22 | 23 | public void DrawFrame(ILEDGraphics graphics) 24 | { 25 | //lock(graphics) 26 | { 27 | Render(graphics); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Server/LightStrip.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using NightDriver; 4 | 5 | namespace NightDriver 6 | { 7 | public class LightStrip : LEDControllerChannel 8 | { 9 | public uint FramesPerBuffer = 21; // How many buffer frames the chips have 10 | 11 | public const double PercentBufferUse = 0.70; // How much of the buffer we should use up 12 | 13 | // The only attribute that a light strip adds is that it can be reversed, as you 14 | // could hand it from either end 15 | 16 | public bool Reversed { get; set; } = false; 17 | 18 | public double TimeOffset 19 | { 20 | get 21 | { 22 | if (0 == Location.FramesPerSecond) // No speed indication yet, can't guess at offset, assume 1 second for now 23 | return 1.0; 24 | 25 | double offset = (FramesPerBuffer * PercentBufferUse) / Location.FramesPerSecond; 26 | return offset; 27 | } 28 | } 29 | 30 | public LightStrip(string hostName, string friendlyName, bool compressData, uint width, uint height = 1, uint offset = 0, bool reversed = false, byte channel = 0, bool swapRedGreen = false, int batchSize = 1) 31 | : base(hostName, friendlyName, width, height, offset, compressData, channel, 0, swapRedGreen, batchSize) 32 | { 33 | Reversed = reversed; 34 | } 35 | 36 | private byte[] GetPixelData(CRGB [] LEDs) 37 | { 38 | return LEDInterop.GetColorBytesAtOffset(LEDs, Offset, Width * Height, Reversed, RedGreenSwap); 39 | } 40 | 41 | const UInt16 WIFI_COMMAND_PIXELDATA = 0; 42 | const UInt16 WIFI_COMMAND_VU = 1; 43 | const UInt16 WIFI_COMMAND_CLOCK = 2; 44 | const UInt16 WIFI_COMMAND_PIXELDATA64 = 3; 45 | 46 | protected override byte[] GetDataFrame(CRGB [] MainLEDs, DateTime timeStart) 47 | { 48 | // The old original code truncated 64 bit values down to 32, and we need to fix that, so it's a in a packet called PIXELDATA64 49 | // and is only sent to newer flashes taht support it. Otherwise we send the old original foramt. 50 | 51 | // The timeOffset is how far in the future frames are generated for. If the chips have a 2 second buffer, you could 52 | // go up to 2 seconds, but I shoot for the middle of the buffer depth. Right now it's calculated as using 53 | 54 | double epoch = (timeStart.Ticks - DateTime.UnixEpoch.Ticks + (TimeOffset * TimeSpan.TicksPerSecond)) / (double)TimeSpan.TicksPerSecond; 55 | 56 | // If the strip clock is within one minute of our clock, adjust the packet time by the amount that we 57 | // differ. This will cause more of the buffer to be used, and will help prevent cases where the buffer 58 | // gets stale because the clock is behind. 59 | 60 | if (Math.Abs(Response.currentClock - epoch) < 60.0) 61 | { 62 | //epoch += (epoch - Response.currentClock) * 0.5; 63 | } 64 | 65 | ulong seconds = (ulong)epoch; // Whole part of time number (left of the decimal point) 66 | ulong uSeconds = (ulong)((epoch - (int)epoch) * 1000000); // Fractional part of time (right of the decimal point) 67 | 68 | var data = GetPixelData(MainLEDs); 69 | return LEDInterop.CombineByteArrays(LEDInterop.WORDToBytes(WIFI_COMMAND_PIXELDATA64), // Offset, always zero for us 70 | LEDInterop.WORDToBytes((UInt16)Channel), // LED channel on ESP32 71 | LEDInterop.DWORDToBytes((UInt32)data.Length / 3), // Number of LEDs 72 | LEDInterop.ULONGToBytes(seconds), // Timestamp seconds (64 bit) 73 | LEDInterop.ULONGToBytes(uSeconds), // Timestmap microseconds (64 bit) 74 | data); // Color Data 75 | 76 | } 77 | }; 78 | } 79 | 80 | -------------------------------------------------------------------------------- /Server/Palette.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using NightDriver; 6 | 7 | 8 | // Palette 9 | // 10 | // Returns the indexed color from the main palette; interpolates N colors from the X provided 11 | 12 | public class Palette 13 | { 14 | protected readonly CRGB[] colorEntries; 15 | public bool Blend { get; set; } = true; 16 | 17 | public int OriginalSize 18 | { 19 | get 20 | { 21 | return colorEntries.Length; 22 | } 23 | } 24 | 25 | public virtual CRGB this[double d] 26 | { 27 | get 28 | { 29 | while (d < 0) 30 | d += 1.0; 31 | 32 | d -= ((long)d); // Wrap around to 0-1 scale so that 3.4 -> 0.4, for example 33 | 34 | double fracPerColor = 1.0 / colorEntries.Length; // How much, on 0-1 scale, each provided color take up (8 colors -> .125) 35 | double indexD = d / fracPerColor; // Convert from 0-1 to 0-N (so if we have 8 colors, .275 becomes color # 2.2). 36 | int index = (int)(d / fracPerColor) % colorEntries.Length; // The integer portion, which will be the base color (color #2 in this example) 37 | double fraction = indexD - index; // How much of the next ordinal color to blend with (0.2 of the color to the right) 38 | 39 | // Find the first integral color 40 | 41 | CRGB color1 = colorEntries[index]; 42 | if (Blend == false) 43 | return color1; 44 | 45 | // Blend the colors. Account for wrap-around so we get a smooth transition all the way around the color wheel 46 | 47 | CRGB color2 = colorEntries[(index + 1) % colorEntries.Length]; 48 | CRGB colorOut = color1.blendWith(color2, 1 - fraction); 49 | 50 | return colorOut; 51 | } 52 | } 53 | 54 | public Palette(CRGB [] colors) 55 | { 56 | colorEntries = colors; 57 | } 58 | 59 | static Palette _Rainbow = new Palette(CRGB.Rainbow); 60 | public static Palette Rainbow 61 | { 62 | get 63 | { 64 | return _Rainbow; 65 | } 66 | } 67 | 68 | static GaussianPalette _SmoothRainbow = new GaussianPalette(CRGB.Rainbow2); 69 | public static GaussianPalette SmoothRainbow 70 | { 71 | get 72 | { 73 | return _SmoothRainbow; 74 | } 75 | } 76 | 77 | static GaussianPalette _SteppedRainbow = new GaussianPalette(CRGB.Rainbow2) { Blend = false }; 78 | public static GaussianPalette SteppedRainbow 79 | { 80 | get 81 | { 82 | return _SteppedRainbow; 83 | } 84 | } 85 | } 86 | 87 | // GaussianPalette 88 | // 89 | // Goes one step further by blending with up to 5 colors using a 1D Gassian weighted kernel 90 | 91 | public class GaussianPalette : Palette 92 | { 93 | protected double _Smoothing = 0.0; 94 | public double [] _Factors = new double[] { 0.06136, 0.24477, 0.38774, 0.24477, 0.06136 }; 95 | 96 | public GaussianPalette(CRGB[] colors) : base(colors) 97 | { 98 | _Smoothing = 1.0 / colors.Length; 99 | } 100 | 101 | public override CRGB this[double d] 102 | { 103 | get 104 | { 105 | double s = _Smoothing / OriginalSize; 106 | 107 | double red = base[d - s * 2].r * _Factors[0] + 108 | base[d - s ].r * _Factors[1] + 109 | base[d ].r * _Factors[2] + 110 | base[d + s ].r * _Factors[3] + 111 | base[d + s * 2].r * _Factors[4]; 112 | 113 | double green = base[d - s * 2].g * _Factors[0] + 114 | base[d - s ].g * _Factors[1] + 115 | base[d ].g * _Factors[2] + 116 | base[d + s ].g * _Factors[3] + 117 | base[d + s * 2].g * _Factors[4]; 118 | 119 | double blue = base[d - s * 2].b * _Factors[0] + 120 | base[d - s ].b * _Factors[1] + 121 | base[d ].b * _Factors[2] + 122 | base[d + s ].b * _Factors[3] + 123 | base[d + s * 2].b * _Factors[4]; 124 | 125 | return new CRGB((byte)red, (byte)green, (byte)blue); 126 | } 127 | } 128 | } 129 | 130 | -------------------------------------------------------------------------------- /Server/Program.cs: -------------------------------------------------------------------------------- 1 | //+-------------------------------------------------------------------------- 2 | // 3 | // NightDriver - (c) 2018 Dave Plummer. All Rights Reserved. 4 | // 5 | // File: NightDriver - Exterior LED Wireless Control 6 | // 7 | // Description: 8 | // 9 | // Draws remotely on LED strips via WiFi 10 | // 11 | // History: Oct-12-2018 davepl Created 12 | // Jun-06-2018 davepl C# rewrite from C++ 13 | // 14 | //--------------------------------------------------------------------------- 15 | 16 | using System; 17 | using System.Collections.Concurrent; 18 | using System.Collections.Generic; 19 | using System.Runtime.InteropServices; 20 | using System.Threading; 21 | 22 | namespace NightDriver 23 | { 24 | internal class ConsoleApp 25 | { 26 | private static bool bShouldExit = false; 27 | public static Statistics Stats = new Statistics(); 28 | 29 | // REVIEW - Best was I can find at the moment to conirm whether a console is present. 30 | // If not, we might be under Docker, etc, so don't try to use the console 31 | 32 | private static bool _AlreadyFailedConsole = false; 33 | 34 | internal static bool SystemHasConsole 35 | { 36 | get 37 | { 38 | try 39 | { 40 | if (_AlreadyFailedConsole) 41 | return false; 42 | 43 | if (Console.WindowHeight > 0 && Console.WindowWidth > 0) 44 | return true; 45 | } 46 | catch (Exception) 47 | { 48 | _AlreadyFailedConsole = true; 49 | return false; 50 | } 51 | return false; 52 | } 53 | } 54 | 55 | // Main 56 | // 57 | // Application main loop - starts worker threads 58 | 59 | internal static Location [] g_AllSites = 60 | { 61 | new ChristmasPresents { FramesPerSecond = 30 }, 62 | new ChristmasTruck { FramesPerSecond = 45 }, 63 | new Pillars() { FramesPerSecond = 30 }, 64 | 65 | new Cabana() { FramesPerSecond = 28 }, 66 | 67 | new Bench() { FramesPerSecond = 28 }, 68 | 69 | new TV() { FramesPerSecond = 30 }, 70 | new Demo() { FramesPerSecond = 30 }, 71 | new ShopCupboards() { FramesPerSecond = 20 }, 72 | 73 | new ShopSouthWindows1() { FramesPerSecond = 2 }, 74 | new ShopSouthWindows2() { FramesPerSecond = 2 }, 75 | new ShopSouthWindows3() { FramesPerSecond = 2 }, 76 | }; 77 | 78 | public static Location[] Locations 79 | { 80 | get 81 | { 82 | return g_AllSites; 83 | } 84 | } 85 | 86 | 87 | protected static void myCancelKeyPressHandler(object sender, ConsoleCancelEventArgs args) 88 | { 89 | Stats.WriteLine($" Key pressed: {args.SpecialKey}"); 90 | Stats.WriteLine($" Cancel property: {args.Cancel}"); 91 | 92 | // Set the Cancel property to true to prevent the process from terminating. 93 | Console.WriteLine("Setting the bShouldExit property to true..."); 94 | bShouldExit = true; 95 | args.Cancel = false; 96 | } 97 | 98 | internal static void Start() 99 | { 100 | // Establish an event handler to process key press events. 101 | if (SystemHasConsole) 102 | Console.CancelKeyPress += new ConsoleCancelEventHandler(myCancelKeyPressHandler); 103 | 104 | DateTime lastStats = DateTime.UtcNow - TimeSpan.FromDays(1); 105 | 106 | foreach (var site in g_AllSites) 107 | site.StartWorkerThread(); 108 | 109 | while (!bShouldExit) 110 | { 111 | double d = (DateTime.UtcNow - lastStats).TotalMilliseconds; 112 | // Don't update the stats more than ever 100ms 113 | if (d >= 60) 114 | { 115 | lastStats = DateTime.UtcNow; 116 | 117 | if (SystemHasConsole) 118 | { 119 | Stats.Render(g_AllSites); 120 | 121 | // If user presses a command key like 'c' to clear, handle it here 122 | 123 | try 124 | { 125 | if (Console.KeyAvailable) 126 | { 127 | ConsoleKeyInfo cki = Console.ReadKey(); 128 | if (cki.KeyChar == 'c') 129 | Console.Clear(); 130 | } 131 | } 132 | catch 133 | { 134 | } 135 | } 136 | } 137 | else 138 | { 139 | Thread.Sleep(60 - (int)d); 140 | } 141 | } 142 | Stats.WriteLine("Exiting!"); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Server/Statistics.cs: -------------------------------------------------------------------------------- 1 |  //+-------------------------------------------------------------------------- 2 | // NightDriver - (c) 2019 Dave Plummer. All Rights Reserved. 3 | // 4 | // File: Statistics.cs 5 | // 6 | // Description: 7 | // 8 | // 9 | // 10 | // History: 6/29/2019 Davepl Created 11 | // 12 | //----------------------------------------------------------------------------- 13 | 14 | using System; 15 | using System.Collections.Generic; 16 | using System.Linq; 17 | 18 | namespace NightDriver 19 | { 20 | public class Statistics 21 | { 22 | public uint SpareMilisecondsPerFrame = 0; 23 | 24 | protected List> textLines = new List>(); 25 | 26 | static void moveto_xy(int x, int y) 27 | { 28 | if (x >= 0 && x < Console.WindowWidth && y >= 0 && y < Console.WindowHeight) 29 | Console.SetCursorPosition(x, y); 30 | } 31 | 32 | static void printf_xy(int x, int y, string format, params object[] args) 33 | { 34 | moveto_xy(x, y); 35 | Console.Write(string.Format(format, args)); 36 | ParkCursor(); 37 | } 38 | 39 | public Statistics() 40 | { 41 | if (ConsoleApp.SystemHasConsole) 42 | { 43 | Console.Clear(); 44 | Console.CursorVisible = false; 45 | ParkCursor(); 46 | } 47 | } 48 | 49 | const int ColumnWidth = 16; 50 | const int ColumnHeight = 17; 51 | const int BufferTop = 36; 52 | const int topMargin = 3; 53 | const int bottomMargin = 0; 54 | 55 | // ParkCursor 56 | // 57 | // Leave the cursor at a known x,y position in case any stray output comes out of somewhere 58 | 59 | public static void ParkCursor() 60 | { 61 | Console.SetCursorPosition(2, BufferTop + topMargin); 62 | } 63 | 64 | // When someone wants to write raw text, we queue it into a queue and print it at the end 65 | 66 | public void WriteLine(string text) 67 | { 68 | lock (textLines) 69 | { 70 | textLines.Add(new Tuple(text, DateTime.Now)); 71 | while (textLines.Count > 50) 72 | textLines.RemoveAt(0); 73 | } 74 | } 75 | 76 | string Spaces => string.Concat(Enumerable.Repeat(" ", Console.WindowWidth)); 77 | string Dashes => string.Concat(Enumerable.Repeat("-", Console.WindowWidth)); 78 | static string Filled = string.Concat(Enumerable.Repeat("█", 132)); 79 | static string Empty = string.Concat(Enumerable.Repeat("░", 132)); 80 | 81 | public static string Bar(double ratio, int maxLen) 82 | { 83 | ratio = Math.Clamp(ratio, 0, 1); 84 | return (ratio > 0 ? Filled.Substring(0, (int)(ratio * maxLen)) : "") + Empty.Substring(0, Math.Max(0, maxLen - (int)(ratio * maxLen))); 85 | } 86 | 87 | public void Render(Location[] sites) 88 | { 89 | // Detect no console and return without trying to render anything 90 | 91 | if (Console.WindowWidth == 0 || Console.WindowHeight == 0) 92 | return; 93 | 94 | var allControllers = sites.SelectMany(item => item.LightStrips).Distinct().ToList(); 95 | printf_xy(27, 0, "NIGHTDRIVER LED CONTROL"); 96 | printf_xy(5, 1, "Version 2020.8.17 - Time: {0:F2} - {1}", Utilities.UnixSeconds(), DateTime.Now); 97 | printf_xy(0, 2, Dashes); 98 | 99 | printf_xy(0, 0 + topMargin, "Slot"); 100 | printf_xy(0, 1 + topMargin, "Name"); 101 | printf_xy(0, 2 + topMargin, "Host"); 102 | printf_xy(0, 3 + topMargin, "Socket"); 103 | printf_xy(0, 4 + topMargin, "WiFi"); 104 | printf_xy(0, 5 + topMargin, "Status"); 105 | printf_xy(0, 6 + topMargin, "Bytes/Sec"); 106 | printf_xy(0, 7 + topMargin, "Clock"); 107 | printf_xy(0, 8 + topMargin, "Buffer"); 108 | printf_xy(0, 9 + topMargin, "Pwr/Brite"); 109 | printf_xy(0,10 + topMargin, "gFPS"); 110 | printf_xy(0,11 + topMargin, "Connects"); 111 | printf_xy(0,12 + topMargin, "Queue Depth"); 112 | printf_xy(0,13 + topMargin, "Offset [FPS]"); 113 | printf_xy(0,14 + topMargin, "Effect Mode"); 114 | 115 | uint totalBytes = 0; 116 | int x, y = 0; 117 | 118 | double epoch = (DateTime.UtcNow.Ticks - DateTime.UnixEpoch.Ticks) / (double)TimeSpan.TicksPerSecond; 119 | 120 | for (int i = 0; i < allControllers.Count; i++) 121 | { 122 | var myController = LEDControllerChannel.ControllerSocketForHost(allControllers[i].HostName); 123 | int slot = i + 1; 124 | x = (slot * ColumnWidth) % Console.WindowWidth; 125 | y = (slot * ColumnWidth) / Console.WindowWidth * ColumnHeight; 126 | 127 | var ver = allControllers[slot - 1].Response.flashVersion > 0 ? 128 | "v" + allControllers[slot - 1].Response.flashVersion.ToString() + " " : "---"; 129 | 130 | Console.ForegroundColor = ConsoleColor.Gray; 131 | printf_xy(x, y + topMargin, slot.ToString() + " " + ver); 132 | Console.ForegroundColor = ConsoleColor.White; 133 | printf_xy(x, y + 1 + topMargin, allControllers[slot - 1].FriendlyName); 134 | Console.ForegroundColor = ConsoleColor.Gray; 135 | printf_xy(x, y + 2 + topMargin, allControllers[slot - 1].HostName); 136 | Console.ForegroundColor = allControllers[slot - 1].HasSocket ? ConsoleColor.Gray : ConsoleColor.Yellow; 137 | printf_xy(x, y + 3 + topMargin, allControllers[slot - 1].HasSocket ? "Open " : "Closed"); 138 | printf_xy(x, y + 4 + topMargin, allControllers[slot - 1].Response.wifiSignal.ToString()); 139 | Console.ForegroundColor = allControllers[slot - 1].ReadyForData ? ConsoleColor.Green : ConsoleColor.Red; 140 | printf_xy(x, y + 5 + topMargin, allControllers[slot - 1].ReadyForData ? "Ready " : "Not Ready"); 141 | Console.ForegroundColor = ConsoleColor.Gray; 142 | printf_xy(x, y + 6 + topMargin, Spaces.Substring(0, ColumnWidth)); 143 | var bps = allControllers[slot - 1].ReadyForData ? allControllers[slot - 1].BytesPerSecond.ToString() : "----"; 144 | printf_xy(x, y + 6 + topMargin, bps); 145 | totalBytes += allControllers[slot - 1].BytesPerSecond; 146 | 147 | var clock = allControllers[slot - 1].Response.currentClock > 8 ? (allControllers[slot - 1].Response.currentClock - epoch).ToString("F2") : "UNSET"; 148 | printf_xy(x, y + 7 + topMargin, Spaces.Substring(0, ColumnWidth)); 149 | printf_xy(x, y + 7 + topMargin, clock); 150 | //printf_xy(x, y + 8 + topMargin, allControllers[slot - 1].Response.bufferPos.ToString()+"/"+ allControllers[slot - 1].Response.bufferSize.ToString() + " "); 151 | printf_xy(x, y + 8 + topMargin, Bar((allControllers[slot - 1].Response.bufferPos+1) / (double) allControllers[slot - 1].Response.bufferSize, ColumnWidth - 3)); 152 | printf_xy(x, y + 9 + topMargin, allControllers[slot - 1].Response.watts.ToString() + "W " + allControllers[slot - 1].Response.brightness.ToString("F0") + "%"); 153 | printf_xy(x, y + 10 + topMargin, allControllers[slot - 1].Response.fpsDrawing.ToString()); 154 | printf_xy(x, y + 11 + topMargin, allControllers[slot - 1].Connects.ToString() + " "); 155 | printf_xy(x, y + 12 + topMargin, allControllers[slot - 1].QueueDepth.ToString() + " "); 156 | if (allControllers[slot - 1].Location != null) 157 | { 158 | printf_xy(x, y + 13 + topMargin, allControllers[slot - 1].TimeOffset.ToString("F2") + " [" + allControllers[slot - 1].Location.FramesPerSecond + "]"); 159 | printf_xy(x, y + 14 + topMargin, allControllers[slot - 1].Location.CurrentEffectName.Left(ColumnWidth-1)); 160 | } 161 | } 162 | 163 | int topLine = y + ColumnHeight + 2; 164 | 165 | printf_xy(0, topLine, Dashes); 166 | printf_xy(0, topLine + 3, Dashes); 167 | 168 | lock (textLines) 169 | { 170 | for (int line = Console.WindowHeight - bottomMargin; line >= topLine + 1; line--) 171 | { 172 | const int leftTextMargin = 15; 173 | int yLine = line - topLine; 174 | if (textLines.Count > yLine) 175 | { 176 | string text = textLines[yLine].Item1; 177 | DateTime timeStamp = textLines[yLine].Item2; 178 | 179 | printf_xy(0, line + topMargin, "[" + timeStamp.ToString("s.f") + "]"); 180 | printf_xy(leftTextMargin, line + topMargin, text.Left(Console.WindowWidth - leftTextMargin)); // Print new line 181 | printf_xy(leftTextMargin + text.Length, line + topMargin, Spaces.Substring(0, Math.Max(0, Console.WindowWidth - leftTextMargin - text.Length))); // Clear old line 182 | } 183 | } 184 | } 185 | 186 | printf_xy(0, topLine + 1, "Total Bytes Sent : " + totalBytes.ToString() + " "); 187 | double ratio = totalBytes / 200000.0; 188 | if (ratio > 1) 189 | ratio = 1; 190 | int leftMargin = 26; 191 | int maxLen = Math.Min(132, Console.WindowWidth) - leftMargin; 192 | string bar = Bar(ratio, maxLen); 193 | printf_xy(leftMargin, topLine + 1, bar); 194 | 195 | printf_xy(0, topLine + 2, "Miliseconds Spare: " + Location.MinimumSpareTime.ToString() + " "); 196 | double ratio2 = Location.MinimumSpareTime / 40.0; 197 | if (ratio2 > 1) 198 | ratio2 = 1; 199 | string bar2 = Bar(ratio2, maxLen); 200 | printf_xy(leftMargin, topLine + 2, bar2); 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Server/SunriseSunset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | // http://pointofint.blogspot.com/2014/06/sunrise-and-sunset-in-c.html 6 | 7 | public class SunriseSunset 8 | { 9 | //*********************************************************************/ 10 | 11 | // Convert radian angle to degrees 12 | 13 | static public double radToDeg(double angleRad) 14 | { 15 | return (180.0 * angleRad / Math.PI); 16 | } 17 | 18 | //*********************************************************************/ 19 | 20 | // Convert degree angle to radians 21 | 22 | static public double degToRad(double angleDeg) 23 | { 24 | return (Math.PI * angleDeg / 180.0); 25 | } 26 | 27 | 28 | 29 | /// 30 | /// calcJD 31 | /// Julian day from calendar day 32 | /// 33 | /// 4 digit year 34 | /// January = 1 35 | /// 1 - 31 36 | /// The Julian day corresponding to the date 37 | /// Number is returned for start of day. Fractional days should be added later. 38 | 39 | static public double calcJD(int year, int month, int day) 40 | { 41 | if (month <= 2) 42 | { 43 | year -= 1; 44 | month += 12; 45 | } 46 | double A = Math.Floor(year / 100.0); 47 | double B = 2 - A + Math.Floor(A / 4); 48 | 49 | double JD = Math.Floor(365.25 * (year + 4716)) + Math.Floor(30.6001 * (month + 1)) + day + B - 1524.5; 50 | return JD; 51 | } 52 | 53 | static public double calcJD(DateTime date) 54 | { 55 | return calcJD(date.Year, date.Month, date.Day); 56 | } 57 | 58 | //***********************************************************************/ 59 | //* Name: calcTimeJulianCent 60 | //* Type: Function 61 | //* Purpose: convert Julian Day to centuries since J2000.0. 62 | //* Arguments: 63 | //* jd : the Julian Day to convert 64 | //* Return value: 65 | //* the T value corresponding to the Julian Day 66 | //***********************************************************************/ 67 | 68 | static public double calcTimeJulianCent(double jd) 69 | { 70 | double T = (jd - 2451545.0) / 36525.0; 71 | return T; 72 | } 73 | 74 | 75 | //***********************************************************************/ 76 | //* Name: calcJDFromJulianCent 77 | //* Type: Function 78 | //* Purpose: convert centuries since J2000.0 to Julian Day. 79 | //* Arguments: 80 | //* t : number of Julian centuries since J2000.0 81 | //* Return value: 82 | //* the Julian Day corresponding to the t value 83 | //***********************************************************************/ 84 | 85 | static public double calcJDFromJulianCent(double t) 86 | { 87 | double JD = t * 36525.0 + 2451545.0; 88 | return JD; 89 | } 90 | 91 | 92 | //***********************************************************************/ 93 | //* Name: calGeomMeanLongSun 94 | //* Type: Function 95 | //* Purpose: calculate the Geometric Mean Longitude of the Sun 96 | //* Arguments: 97 | //* t : number of Julian centuries since J2000.0 98 | //* Return value: 99 | //* the Geometric Mean Longitude of the Sun in degrees 100 | //***********************************************************************/ 101 | 102 | static public double calcGeomMeanLongSun(double t) 103 | { 104 | double L0 = 280.46646 + t * (36000.76983 + 0.0003032 * t); 105 | while (L0 > 360.0) 106 | { 107 | L0 -= 360.0; 108 | } 109 | while (L0 < 0.0) 110 | { 111 | L0 += 360.0; 112 | } 113 | return L0; // in degrees 114 | } 115 | 116 | 117 | //***********************************************************************/ 118 | //* Name: calGeomAnomalySun 119 | //* Type: Function 120 | //* Purpose: calculate the Geometric Mean Anomaly of the Sun 121 | //* Arguments: 122 | //* t : number of Julian centuries since J2000.0 123 | //* Return value: 124 | //* the Geometric Mean Anomaly of the Sun in degrees 125 | //***********************************************************************/ 126 | 127 | static public double calcGeomMeanAnomalySun(double t) 128 | { 129 | double M = 357.52911 + t * (35999.05029 - 0.0001537 * t); 130 | return M; // in degrees 131 | } 132 | 133 | //***********************************************************************/ 134 | //* Name: calcEccentricityEarthOrbit 135 | //* Type: Function 136 | //* Purpose: calculate the eccentricity of earth's orbit 137 | //* Arguments: 138 | //* t : number of Julian centuries since J2000.0 139 | //* Return value: 140 | //* the unitless eccentricity 141 | //***********************************************************************/ 142 | 143 | 144 | static public double calcEccentricityEarthOrbit(double t) 145 | { 146 | double e = 0.016708634 - t * (0.000042037 + 0.0000001267 * t); 147 | return e; // unitless 148 | } 149 | 150 | //***********************************************************************/ 151 | //* Name: calcSunEqOfCenter 152 | //* Type: Function 153 | //* Purpose: calculate the equation of center for the sun 154 | //* Arguments: 155 | //* t : number of Julian centuries since J2000.0 156 | //* Return value: 157 | //* in degrees 158 | //***********************************************************************/ 159 | 160 | 161 | static public double calcSunEqOfCenter(double t) 162 | { 163 | double m = calcGeomMeanAnomalySun(t); 164 | 165 | double mrad = degToRad(m); 166 | double sinm = Math.Sin(mrad); 167 | double sin2m = Math.Sin(mrad + mrad); 168 | double sin3m = Math.Sin(mrad + mrad + mrad); 169 | 170 | double C = sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289; 171 | return C; // in degrees 172 | } 173 | 174 | //***********************************************************************/ 175 | //* Name: calcSunTrueLong 176 | //* Type: Function 177 | //* Purpose: calculate the true longitude of the sun 178 | //* Arguments: 179 | //* t : number of Julian centuries since J2000.0 180 | //* Return value: 181 | //* sun's true longitude in degrees 182 | //***********************************************************************/ 183 | 184 | 185 | static public double calcSunTrueLong(double t) 186 | { 187 | double l0 = calcGeomMeanLongSun(t); 188 | double c = calcSunEqOfCenter(t); 189 | 190 | double O = l0 + c; 191 | return O; // in degrees 192 | } 193 | 194 | //***********************************************************************/ 195 | //* Name: calcSunTrueAnomaly 196 | //* Type: Function 197 | //* Purpose: calculate the true anamoly of the sun 198 | //* Arguments: 199 | //* t : number of Julian centuries since J2000.0 200 | //* Return value: 201 | //* sun's true anamoly in degrees 202 | //***********************************************************************/ 203 | 204 | static public double calcSunTrueAnomaly(double t) 205 | { 206 | double m = calcGeomMeanAnomalySun(t); 207 | double c = calcSunEqOfCenter(t); 208 | 209 | double v = m + c; 210 | return v; // in degrees 211 | } 212 | 213 | //***********************************************************************/ 214 | //* Name: calcSunRadVector 215 | //* Type: Function 216 | //* Purpose: calculate the distance to the sun in AU 217 | //* Arguments: 218 | //* t : number of Julian centuries since J2000.0 219 | //* Return value: 220 | //* sun radius vector in AUs 221 | //***********************************************************************/ 222 | 223 | static public double calcSunRadVector(double t) 224 | { 225 | double v = calcSunTrueAnomaly(t); 226 | double e = calcEccentricityEarthOrbit(t); 227 | 228 | double R = (1.000001018 * (1 - e * e)) / (1 + e * Math.Cos(degToRad(v))); 229 | return R; // in AUs 230 | } 231 | 232 | //***********************************************************************/ 233 | //* Name: calcSunApparentLong 234 | //* Type: Function 235 | //* Purpose: calculate the apparent longitude of the sun 236 | //* Arguments: 237 | //* t : number of Julian centuries since J2000.0 238 | //* Return value: 239 | //* sun's apparent longitude in degrees 240 | //***********************************************************************/ 241 | 242 | static public double calcSunApparentLong(double t) 243 | { 244 | double o = calcSunTrueLong(t); 245 | 246 | double omega = 125.04 - 1934.136 * t; 247 | double lambda = o - 0.00569 - 0.00478 * Math.Sin(degToRad(omega)); 248 | return lambda; // in degrees 249 | } 250 | 251 | //***********************************************************************/ 252 | //* Name: calcMeanObliquityOfEcliptic 253 | //* Type: Function 254 | //* Purpose: calculate the mean obliquity of the ecliptic 255 | //* Arguments: 256 | //* t : number of Julian centuries since J2000.0 257 | //* Return value: 258 | //* mean obliquity in degrees 259 | //***********************************************************************/ 260 | 261 | static public double calcMeanObliquityOfEcliptic(double t) 262 | { 263 | double seconds = 21.448 - t * (46.8150 + t * (0.00059 - t * (0.001813))); 264 | double e0 = 23.0 + (26.0 + (seconds / 60.0)) / 60.0; 265 | return e0; // in degrees 266 | } 267 | 268 | //***********************************************************************/ 269 | //* Name: calcObliquityCorrection 270 | //* Type: Function 271 | //* Purpose: calculate the corrected obliquity of the ecliptic 272 | //* Arguments: 273 | //* t : number of Julian centuries since J2000.0 274 | //* Return value: 275 | //* corrected obliquity in degrees 276 | //***********************************************************************/ 277 | 278 | static public double calcObliquityCorrection(double t) 279 | { 280 | double e0 = calcMeanObliquityOfEcliptic(t); 281 | 282 | double omega = 125.04 - 1934.136 * t; 283 | double e = e0 + 0.00256 * Math.Cos(degToRad(omega)); 284 | return e; // in degrees 285 | } 286 | 287 | //***********************************************************************/ 288 | //* Name: calcSunRtAscension 289 | //* Type: Function 290 | //* Purpose: calculate the right ascension of the sun 291 | //* Arguments: 292 | //* t : number of Julian centuries since J2000.0 293 | //* Return value: 294 | //* sun's right ascension in degrees 295 | //***********************************************************************/ 296 | 297 | static public double calcSunRtAscension(double t) 298 | { 299 | double e = calcObliquityCorrection(t); 300 | double lambda = calcSunApparentLong(t); 301 | 302 | double tananum = (Math.Cos(degToRad(e)) * Math.Sin(degToRad(lambda))); 303 | double tanadenom = (Math.Cos(degToRad(lambda))); 304 | double alpha = radToDeg(Math.Atan2(tananum, tanadenom)); 305 | return alpha; // in degrees 306 | } 307 | 308 | //***********************************************************************/ 309 | //* Name: calcSunDeclination 310 | //* Type: Function 311 | //* Purpose: calculate the declination of the sun 312 | //* Arguments: 313 | //* t : number of Julian centuries since J2000.0 314 | //* Return value: 315 | //* sun's declination in degrees 316 | //***********************************************************************/ 317 | 318 | static public double calcSunDeclination(double t) 319 | { 320 | double e = calcObliquityCorrection(t); 321 | double lambda = calcSunApparentLong(t); 322 | 323 | double sint = Math.Sin(degToRad(e)) * Math.Sin(degToRad(lambda)); 324 | double theta = radToDeg(Math.Asin(sint)); 325 | return theta; // in degrees 326 | } 327 | 328 | //***********************************************************************/ 329 | //* Name: calcEquationOfTime 330 | //* Type: Function 331 | //* Purpose: calculate the difference between true solar time and mean 332 | //* solar time 333 | //* Arguments: 334 | //* t : number of Julian centuries since J2000.0 335 | //* Return value: 336 | //* equation of time in minutes of time 337 | //***********************************************************************/ 338 | 339 | static public double calcEquationOfTime(double t) 340 | { 341 | double epsilon = calcObliquityCorrection(t); 342 | double l0 = calcGeomMeanLongSun(t); 343 | double e = calcEccentricityEarthOrbit(t); 344 | double m = calcGeomMeanAnomalySun(t); 345 | 346 | double y = Math.Tan(degToRad(epsilon) / 2.0); 347 | y *= y; 348 | 349 | double sin2l0 = Math.Sin(2.0 * degToRad(l0)); 350 | double sinm = Math.Sin(degToRad(m)); 351 | double cos2l0 = Math.Cos(2.0 * degToRad(l0)); 352 | double sin4l0 = Math.Sin(4.0 * degToRad(l0)); 353 | double sin2m = Math.Sin(2.0 * degToRad(m)); 354 | 355 | double Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 356 | - 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m; 357 | 358 | return radToDeg(Etime) * 4.0; // in minutes of time 359 | } 360 | 361 | //***********************************************************************/ 362 | //* Name: calcHourAngleSunrise 363 | //* Type: Function 364 | //* Purpose: calculate the hour angle of the sun at sunrise for the 365 | //* latitude 366 | //* Arguments: 367 | //* lat : latitude of observer in degrees 368 | //* solarDec : declination angle of sun in degrees 369 | //* Return value: 370 | //* hour angle of sunrise in radians 371 | //***********************************************************************/ 372 | 373 | static public double calcHourAngleSunrise(double lat, double solarDec) 374 | { 375 | double latRad = degToRad(lat); 376 | double sdRad = degToRad(solarDec); 377 | 378 | double HAarg = (Math.Cos(degToRad(90.833)) / (Math.Cos(latRad) * Math.Cos(sdRad)) - Math.Tan(latRad) * Math.Tan(sdRad)); 379 | 380 | double HA = (Math.Acos(Math.Cos(degToRad(90.833)) / (Math.Cos(latRad) * Math.Cos(sdRad)) - Math.Tan(latRad) * Math.Tan(sdRad))); 381 | 382 | return HA; // in radians 383 | } 384 | 385 | //***********************************************************************/ 386 | //* Name: calcHourAngleSunset 387 | //* Type: Function 388 | //* Purpose: calculate the hour angle of the sun at sunset for the 389 | //* latitude 390 | //* Arguments: 391 | //* lat : latitude of observer in degrees 392 | //* solarDec : declination angle of sun in degrees 393 | //* Return value: 394 | //* hour angle of sunset in radians 395 | //***********************************************************************/ 396 | 397 | static public double calcHourAngleSunset(double lat, double solarDec) 398 | { 399 | double latRad = degToRad(lat); 400 | double sdRad = degToRad(solarDec); 401 | 402 | double HAarg = (Math.Cos(degToRad(90.833)) / (Math.Cos(latRad) * Math.Cos(sdRad)) - Math.Tan(latRad) * Math.Tan(sdRad)); 403 | 404 | double HA = (Math.Acos(Math.Cos(degToRad(90.833)) / (Math.Cos(latRad) * Math.Cos(sdRad)) - Math.Tan(latRad) * Math.Tan(sdRad))); 405 | 406 | return -HA; // in radians 407 | } 408 | 409 | 410 | //***********************************************************************/ 411 | //* Name: calcSunriseUTC 412 | //* Type: Function 413 | //* Purpose: calculate the Universal Coordinated Time (UTC) of sunrise 414 | //* for the given day at the given location on earth 415 | //* Arguments: 416 | //* JD : julian day 417 | //* latitude : latitude of observer in degrees 418 | //* longitude : longitude of observer in degrees 419 | //* Return value: 420 | //* time in minutes from zero Z 421 | //***********************************************************************/ 422 | 423 | [Obsolete("calcSunriseUTCWithFraction is deprecated, please use calcSunRiseUTC instead.", true)] 424 | static public double calcSunriseUTC(double JD, double latitude, double longitude) 425 | { 426 | return calcSunRiseUTCWithFraction(JD, latitude, longitude); 427 | } 428 | 429 | [Obsolete("calcSunRiseUTCWithFraction is deprecated because noo work yet :), please use calcSunRiseUTC instead.")] 430 | static public double calcSunRiseUTCWithFraction(double JD, double latitude, double longitude) 431 | { 432 | //TODO: this method don't work!!! I have to fix it 433 | double t = calcTimeJulianCent(JD); 434 | /* 435 | 436 | // *** Find the time of solar noon at the location, and use 437 | // that declination. This is better than start of the 438 | // Julian day 439 | double noonmin = calcSolNoonUTC(t, longitude); 440 | double tnoon = calcTimeJulianCent(JD + noonmin / 1440.0); 441 | // *** First pass to approximate sunrise (using solar noon) 442 | double eqTime = calcEquationOfTime(tnoon); 443 | double solarDec = calcSunDeclination(tnoon); 444 | double hourAngle = calcHourAngleSunrise(latitude, solarDec); 445 | double delta = longitude - radToDeg(hourAngle); 446 | double timeDiff = 4 * delta; // in minutes of time 447 | 448 | double timeUTC = calcSunRiseUTC(JD, latitude, longitude); // 720 + timeDiff - eqTime; // in minutes 449 | */ 450 | 451 | double eqTime = 0; 452 | double solarDec = 0; 453 | double hourAngle = 0; 454 | double delta = 0; 455 | double timeDiff = 0; 456 | double timeUTC = calcSunRiseUTC(JD, latitude, longitude); 457 | // alert("eqTime = " + eqTime + "\nsolarDec = " + solarDec + "\ntimeUTC = " + timeUTC); 458 | 459 | // *** Second pass includes fractional jday in gamma calc 460 | //this is the good function for calculate the time UTC of the sunRise 461 | //var t = calcTimeJulianCent(JD); 462 | //var eqTime = calcEquationOfTime(t); 463 | //var solarDec = calcSunDeclination(t); 464 | //var hourAngle = calcHourAngleSunrise(latitude, solarDec); 465 | //hourAngle = -hourAngle; 466 | //var delta = longitude + radToDeg(hourAngle); 467 | //var timeUTC = 720 - (4.0 * delta) - eqTime; // in minutes 468 | 469 | double newt = calcTimeJulianCent(calcJDFromJulianCent(t) + timeUTC / 1440.0); 470 | eqTime = calcEquationOfTime(newt); 471 | solarDec = calcSunDeclination(newt); 472 | hourAngle = calcHourAngleSunrise(latitude, solarDec); 473 | hourAngle = -hourAngle; 474 | delta = longitude - radToDeg(hourAngle); 475 | timeDiff = 4 * delta; 476 | timeUTC = 720 + timeDiff - eqTime; // in minutes 477 | 478 | // alert("eqTime = " + eqTime + "\nsolarDec = " + solarDec + "\ntimeUTC = " + timeUTC); 479 | 480 | return timeUTC; 481 | } 482 | 483 | //***********************************************************************/ 484 | //* Name: calcSolNoonUTC 485 | //* Type: Function 486 | //* Purpose: calculate the Universal Coordinated Time (UTC) of solar 487 | //* noon for the given day at the given location on earth 488 | //* Arguments: 489 | //* t : number of Julian centuries since J2000.0 490 | //* longitude : longitude of observer in degrees 491 | //* Return value: 492 | //* time in minutes from zero Z 493 | //***********************************************************************/ 494 | 495 | static public double calcSolNoonUTC(double t, double longitude) 496 | { 497 | // First pass uses approximate solar noon to calculate eqtime 498 | double tnoon = calcTimeJulianCent(calcJDFromJulianCent(t) + longitude / 360.0); 499 | double eqTime = calcEquationOfTime(tnoon); 500 | double solNoonUTC = 720 + (longitude * 4) - eqTime; // min 501 | 502 | double newt = calcTimeJulianCent(calcJDFromJulianCent(t) - 0.5 + solNoonUTC / 1440.0); 503 | 504 | eqTime = calcEquationOfTime(newt); 505 | // double solarNoonDec = calcSunDeclination(newt); 506 | solNoonUTC = 720 + (longitude * 4) - eqTime; // min 507 | 508 | return solNoonUTC; 509 | } 510 | 511 | //***********************************************************************/ 512 | //* Name: calcSunsetUTC 513 | //* Type: Function 514 | //* Purpose: calculate the Universal Coordinated Time (UTC) of sunset 515 | //* for the given day at the given location on earth 516 | //* Arguments: 517 | //* JD : julian day 518 | //* latitude : latitude of observer in degrees 519 | //* longitude : longitude of observer in degrees 520 | //* Return value: 521 | //* time in minutes from zero Z 522 | //***********************************************************************/ 523 | 524 | static public double calcSunSetUTC(double JD, double latitude, double longitude) 525 | { 526 | var t = calcTimeJulianCent(JD); 527 | var eqTime = calcEquationOfTime(t); 528 | var solarDec = calcSunDeclination(t); 529 | var hourAngle = calcHourAngleSunrise(latitude, solarDec); 530 | hourAngle = -hourAngle; 531 | var delta = longitude + radToDeg(hourAngle); 532 | var timeUTC = 720 - (4.0 * delta) - eqTime; // in minutes 533 | return timeUTC; 534 | } 535 | 536 | static public double calcSunRiseUTC(double JD, double latitude, double longitude) 537 | { 538 | var t = calcTimeJulianCent(JD); 539 | var eqTime = calcEquationOfTime(t); 540 | var solarDec = calcSunDeclination(t); 541 | var hourAngle = calcHourAngleSunrise(latitude, solarDec); 542 | var delta = longitude + radToDeg(hourAngle); 543 | var timeUTC = 720 - (4.0 * delta) - eqTime; // in minutes 544 | return timeUTC; 545 | } 546 | 547 | static public string getTimeString(double time, int timezone, double JD, bool dst) 548 | { 549 | var timeLocal = time + (timezone * 60.0); 550 | var riseT = calcTimeJulianCent(JD + time / 1440.0); 551 | timeLocal += ((dst) ? 60.0 : 0.0); 552 | return getTimeString(timeLocal); 553 | } 554 | 555 | static public DateTime? getDateTime(double time, int timezone, DateTime date, bool dst) 556 | { 557 | double JD = calcJD(date); 558 | var timeLocal = time + (timezone * 60.0); 559 | var riseT = calcTimeJulianCent(JD + time / 1440.0); 560 | timeLocal += ((dst) ? 60.0 : 0.0); 561 | return getDateTime(timeLocal, date); 562 | } 563 | 564 | static private string getTimeString(double minutes) 565 | { 566 | 567 | string output = ""; 568 | 569 | if ((minutes >= 0) && (minutes < 1440)) 570 | { 571 | var floatHour = minutes / 60.0; 572 | var hour = Math.Floor(floatHour); 573 | var floatMinute = 60.0 * (floatHour - Math.Floor(floatHour)); 574 | var minute = Math.Floor(floatMinute); 575 | var floatSec = 60.0 * (floatMinute - Math.Floor(floatMinute)); 576 | var second = Math.Floor(floatSec + 0.5); 577 | if (second > 59) 578 | { 579 | second = 0; 580 | minute += 1; 581 | } 582 | if ((second >= 30)) minute++; 583 | if (minute > 59) 584 | { 585 | minute = 0; 586 | hour += 1; 587 | } 588 | output = String.Format("{0} : {1}", hour, minute); 589 | } 590 | else 591 | { 592 | return "error"; 593 | } 594 | 595 | return output; 596 | } 597 | 598 | static private DateTime? getDateTime(double minutes, DateTime date) 599 | { 600 | 601 | DateTime? retVal = null; 602 | 603 | if ((minutes >= 0) && (minutes < 1440)) 604 | { 605 | var floatHour = minutes / 60.0; 606 | var hour = Math.Floor(floatHour); 607 | var floatMinute = 60.0 * (floatHour - Math.Floor(floatHour)); 608 | var minute = Math.Floor(floatMinute); 609 | var floatSec = 60.0 * (floatMinute - Math.Floor(floatMinute)); 610 | var second = Math.Floor(floatSec + 0.5); 611 | if (second > 59) 612 | { 613 | second = 0; 614 | minute += 1; 615 | } 616 | if ((second >= 30)) minute++; 617 | if (minute > 59) 618 | { 619 | minute = 0; 620 | hour += 1; 621 | } 622 | return new DateTime(date.Year, date.Month, date.Day, (int)hour, (int)minute, (int)second); 623 | } 624 | else 625 | { 626 | return retVal; 627 | } 628 | } 629 | } 630 | -------------------------------------------------------------------------------- /Server/ZLIBStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | 5 | namespace ZLIB 6 | { 7 | public sealed class ZLIBStream : Stream 8 | { 9 | #region "Variables globales" 10 | private CompressionMode mCompressionMode = CompressionMode.Compress; 11 | private CompressionLevel mCompressionLevel = CompressionLevel.NoCompression; 12 | private bool mLeaveOpen = false; 13 | private Adler32 adler32 = new Adler32(); 14 | private DeflateStream mDeflateStream; 15 | private Stream mRawStream; 16 | private bool mClosed = false; 17 | private byte[] mCRC = null; 18 | #endregion 19 | #region "Constructores" 20 | /// 21 | /// Inicializa una nueva instancia de la clase ZLIBStream usando la secuencia y nivel de compresión especificados. 22 | /// 23 | /// Secuencia que se va a comprimir 24 | /// Nivel de compresión 25 | public ZLIBStream(Stream stream, CompressionLevel compressionLevel) : this(stream, compressionLevel, false) 26 | { 27 | } 28 | /// 29 | /// Inicializa una nueva instancia de la clase ZLIBStream usando la secuencia y modo de compresión especificados. 30 | /// 31 | /// Secuencia que se va a comprimir o descomprimir 32 | /// Modo de compresión 33 | public ZLIBStream(Stream stream, CompressionMode compressionMode) : this(stream, compressionMode, false) 34 | { 35 | } 36 | /// 37 | /// Inicializa una nueva instancia de la clase ZLIBStream usando la secuencia y nivel de compresión especificados y, opcionalmente, deja la secuencia abierta. 38 | /// 39 | /// Secuencia que se va a comprimir 40 | /// Nivel de compresión 41 | /// Indica si se debe de dejar la secuencia abierta después de comprimir la secuencia 42 | public ZLIBStream(Stream stream, CompressionLevel compressionLevel, bool leaveOpen) 43 | { 44 | this.mCompressionMode = CompressionMode.Compress; 45 | this.mCompressionLevel = compressionLevel; 46 | this.mLeaveOpen = leaveOpen; 47 | this.mRawStream = stream; 48 | this.InicializarStream(); 49 | } 50 | /// 51 | /// Inicializa una nueva instancia de la clase ZLIBStream usando la secuencia y modo de compresión especificados y, opcionalmente, deja la secuencia abierta. 52 | /// 53 | /// Secuencia que se va a comprimir o descomprimir 54 | /// Modo de compresión 55 | /// Indica si se debe de dejar la secuencia abierta después de comprimir o descomprimir la secuencia 56 | public ZLIBStream(Stream stream, CompressionMode compressionMode, bool leaveOpen) 57 | { 58 | this.mCompressionMode = compressionMode; 59 | this.mCompressionLevel = CompressionLevel.Fastest; 60 | this.mLeaveOpen = leaveOpen; 61 | this.mRawStream = stream; 62 | this.InicializarStream(); 63 | } 64 | #endregion 65 | #region "Propiedades sobreescritas" 66 | public override bool CanRead 67 | { 68 | get 69 | { 70 | return ((this.mCompressionMode == CompressionMode.Decompress) && (this.mClosed != true)); 71 | } 72 | } 73 | public override bool CanWrite 74 | { 75 | get 76 | { 77 | return ((this.mCompressionMode == CompressionMode.Compress) && (this.mClosed != true)); 78 | } 79 | } 80 | public override bool CanSeek 81 | { 82 | get 83 | { 84 | return false; 85 | } 86 | } 87 | public override long Length 88 | { 89 | get 90 | { 91 | throw new NotImplementedException(); 92 | } 93 | } 94 | public override long Position 95 | { 96 | get 97 | { 98 | throw new NotImplementedException(); 99 | } 100 | set 101 | { 102 | throw new NotImplementedException(); 103 | } 104 | } 105 | #endregion 106 | #region "Metodos sobreescritos" 107 | public override int ReadByte() 108 | { 109 | int result = 0; 110 | 111 | if (this.CanRead == true) 112 | { 113 | result = this.mDeflateStream.ReadByte(); 114 | 115 | //Comprobamos si se ha llegado al final del stream 116 | if (result == -1) 117 | { 118 | this.ReadCRC(); 119 | } 120 | else 121 | { 122 | this.adler32.Update(Convert.ToByte(result)); 123 | } 124 | } 125 | else 126 | { 127 | throw new InvalidOperationException(); 128 | } 129 | 130 | return result; 131 | } 132 | 133 | public override int Read(byte[] buffer, int offset, int count) 134 | { 135 | int result = 0; 136 | 137 | if (this.CanRead == true) 138 | { 139 | result = this.mDeflateStream.Read(buffer, offset, count); 140 | 141 | //Comprobamos si hemos llegado al final del stream 142 | if ((result < 1) && (count > 0)) 143 | { 144 | this.ReadCRC(); 145 | } 146 | else 147 | { 148 | this.adler32.Update(buffer, offset, result); 149 | } 150 | } 151 | else 152 | { 153 | throw new InvalidOperationException(); 154 | } 155 | 156 | return result; 157 | } 158 | 159 | public override void WriteByte(byte value) 160 | { 161 | if (this.CanWrite == true) 162 | { 163 | this.mDeflateStream.WriteByte(value); 164 | this.adler32.Update(value); 165 | } 166 | else 167 | { 168 | throw new InvalidOperationException(); 169 | } 170 | } 171 | 172 | public override void Write(byte[] buffer, int offset, int count) 173 | { 174 | if (this.CanWrite == true) 175 | { 176 | this.mDeflateStream.Write(buffer, offset, count); 177 | this.adler32.Update(buffer, offset, count); 178 | } 179 | else 180 | { 181 | throw new InvalidOperationException(); 182 | } 183 | } 184 | 185 | public override void Close() 186 | { 187 | if (this.mClosed == false) 188 | { 189 | this.mClosed = true; 190 | if (this.mCompressionMode == CompressionMode.Compress) 191 | { 192 | this.Flush(); 193 | this.mDeflateStream.Close(); 194 | 195 | this.mCRC = BitConverter.GetBytes(adler32.GetValue()); 196 | 197 | if (BitConverter.IsLittleEndian == true) 198 | { 199 | Array.Reverse(this.mCRC); 200 | } 201 | 202 | this.mRawStream.Write(this.mCRC, 0, this.mCRC.Length); 203 | } 204 | else 205 | { 206 | this.mDeflateStream.Close(); 207 | if (this.mCRC == null) 208 | { 209 | this.ReadCRC(); 210 | } 211 | } 212 | 213 | if (this.mLeaveOpen == false) 214 | { 215 | this.mRawStream.Close(); 216 | } 217 | } 218 | else 219 | { 220 | //throw new InvalidOperationException("Stream already closed"); 221 | } 222 | } 223 | 224 | public override void Flush() 225 | { 226 | if (this.mDeflateStream != null) 227 | { 228 | this.mDeflateStream.Flush(); 229 | } 230 | } 231 | 232 | public override long Seek(long offset, SeekOrigin origin) 233 | { 234 | throw new NotImplementedException(); 235 | } 236 | 237 | public override void SetLength(long value) 238 | { 239 | throw new NotImplementedException(); 240 | } 241 | #endregion 242 | #region "Metodos publicos" 243 | /// 244 | /// Comprueba si el stream esta en formato ZLib 245 | /// 246 | /// Stream a comprobar 247 | /// Retorna True en caso de que el stream sea en formato ZLib y False en caso contrario u error 248 | public static bool IsZLibStream(Stream stream) 249 | { 250 | bool bResult = false; 251 | int CMF = 0; 252 | int Flag = 0; 253 | ZLibHeader header; 254 | 255 | //Comprobamos si la secuencia esta en la posición 0, de no ser así, lanzamos una excepción 256 | if (stream.Position != 0) 257 | { 258 | throw new ArgumentOutOfRangeException("Sequence must be at position 0"); 259 | } 260 | 261 | //Comprobamos si podemos realizar la lectura de los dos bytes que conforman la cabecera 262 | if (stream.CanRead == true) 263 | { 264 | CMF = stream.ReadByte(); 265 | Flag = stream.ReadByte(); 266 | try 267 | { 268 | header = ZLibHeader.DecodeHeader(CMF, Flag); 269 | bResult = header.IsSupportedZLibStream; 270 | } 271 | catch 272 | { 273 | //Nada 274 | } 275 | } 276 | 277 | return bResult; 278 | } 279 | /// 280 | /// Lee los últimos 4 bytes del stream ya que es donde está el CRC 281 | /// 282 | private void ReadCRC() 283 | { 284 | this.mCRC = new byte[4]; 285 | this.mRawStream.Seek(-4, SeekOrigin.End); 286 | if (this.mRawStream.Read(this.mCRC, 0, 4) < 4) 287 | { 288 | throw new EndOfStreamException(); 289 | } 290 | 291 | if (BitConverter.IsLittleEndian == true) 292 | { 293 | Array.Reverse(this.mCRC); 294 | } 295 | 296 | uint crcAdler = this.adler32.GetValue(); 297 | uint crcStream = BitConverter.ToUInt32(this.mCRC, 0); 298 | 299 | if (crcStream != crcAdler) 300 | { 301 | throw new Exception("CRC mismatch"); 302 | } 303 | } 304 | #endregion 305 | #region "Metodos privados" 306 | /// 307 | /// Inicializa el stream 308 | /// 309 | private void InicializarStream() 310 | { 311 | switch (this.mCompressionMode) 312 | { 313 | case CompressionMode.Compress: 314 | { 315 | this.InicializarZLibHeader(); 316 | this.mDeflateStream = new DeflateStream(this.mRawStream, this.mCompressionLevel, true); 317 | break; 318 | } 319 | case CompressionMode.Decompress: 320 | { 321 | if (ZLIBStream.IsZLibStream(this.mRawStream) == false) 322 | { 323 | throw new InvalidDataException(); 324 | } 325 | this.mDeflateStream = new DeflateStream(this.mRawStream, CompressionMode.Decompress, true); 326 | break; 327 | } 328 | } 329 | } 330 | /// 331 | /// Inicializa el encabezado del stream en formato ZLib 332 | /// 333 | private void InicializarZLibHeader() 334 | { 335 | byte[] bytesHeader; 336 | 337 | //Establecemos la configuración de la cabecera 338 | ZLibHeader header = new ZLibHeader(); 339 | 340 | header.CompressionMethod = 8; //Deflate 341 | header.CompressionInfo = 7; 342 | 343 | header.FDict = false; //Sin diccionario 344 | switch (this.mCompressionLevel) 345 | { 346 | case CompressionLevel.NoCompression: 347 | { 348 | header.FLevel = FLevel.Faster; 349 | break; 350 | } 351 | case CompressionLevel.Fastest: 352 | { 353 | header.FLevel = FLevel.Default; 354 | break; 355 | } 356 | case CompressionLevel.Optimal: 357 | { 358 | header.FLevel = FLevel.Optimal; 359 | break; 360 | } 361 | } 362 | 363 | bytesHeader = header.EncodeZlibHeader(); 364 | 365 | this.mRawStream.WriteByte(bytesHeader[0]); 366 | this.mRawStream.WriteByte(bytesHeader[1]); 367 | } 368 | #endregion 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /Server/ZLibHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ZLIB 4 | { 5 | public enum FLevel 6 | { 7 | Faster = 0, 8 | Fast = 1, 9 | Default = 2, 10 | Optimal = 3, 11 | } 12 | public sealed class ZLibHeader 13 | { 14 | #region "Variables globales" 15 | private bool mIsSupportedZLibStream; 16 | private byte mCompressionMethod; //CMF 0-3 17 | private byte mCompressionInfo; //CMF 4-7 18 | private byte mFCheck; //Flag 0-4 (Check bits for CMF and FLG) 19 | private bool mFDict; //Flag 5 (Preset dictionary) 20 | private FLevel mFLevel; //Flag 6-7 (Compression level) 21 | #endregion 22 | #region "Propiedades" 23 | public bool IsSupportedZLibStream 24 | { 25 | get 26 | { 27 | return this.mIsSupportedZLibStream; 28 | } 29 | set 30 | { 31 | this.mIsSupportedZLibStream = value; 32 | } 33 | } 34 | public byte CompressionMethod 35 | { 36 | get 37 | { 38 | return this.mCompressionMethod; 39 | } 40 | set 41 | { 42 | if (value > 15) 43 | { 44 | throw new ArgumentOutOfRangeException("Argument cannot be greater than 15"); 45 | } 46 | this.mCompressionMethod = value; 47 | } 48 | } 49 | public byte CompressionInfo 50 | { 51 | get 52 | { 53 | return this.mCompressionInfo; 54 | } 55 | set 56 | { 57 | if (value > 15) 58 | { 59 | throw new ArgumentOutOfRangeException("Argument cannot be greater than 15"); 60 | } 61 | this.mCompressionInfo = value; 62 | } 63 | } 64 | public byte FCheck 65 | { 66 | get 67 | { 68 | return this.mFCheck; 69 | } 70 | set 71 | { 72 | if (value > 31) 73 | { 74 | throw new ArgumentOutOfRangeException("Argument cannot be greater than 31"); 75 | } 76 | this.mFCheck = value; 77 | } 78 | } 79 | public bool FDict 80 | { 81 | get 82 | { 83 | return this.mFDict; 84 | } 85 | set 86 | { 87 | this.mFDict = value; 88 | } 89 | } 90 | public FLevel FLevel 91 | { 92 | get 93 | { 94 | return this.mFLevel; 95 | } 96 | set 97 | { 98 | this.mFLevel = value; 99 | } 100 | } 101 | #endregion 102 | #region "Constructor" 103 | public ZLibHeader() 104 | { 105 | 106 | } 107 | #endregion 108 | #region "Metodos privados" 109 | private void RefreshFCheck() 110 | { 111 | byte byteFLG = 0x00; 112 | 113 | byteFLG = (byte)(Convert.ToByte(this.FLevel) << 1); 114 | byteFLG |= Convert.ToByte(this.FDict); 115 | 116 | this.FCheck = Convert.ToByte(31 - Convert.ToByte((this.GetCMF() * 256 + byteFLG) % 31)); 117 | } 118 | private byte GetCMF() 119 | { 120 | byte byteCMF = 0x00; 121 | 122 | byteCMF = (byte)(this.CompressionInfo << 4); 123 | byteCMF |= (byte)(this.CompressionMethod); 124 | 125 | return byteCMF; 126 | } 127 | private byte GetFLG() 128 | { 129 | byte byteFLG = 0x00; 130 | 131 | byteFLG = (byte)(Convert.ToByte(this.FLevel) << 6); 132 | byteFLG |= (byte)(Convert.ToByte(this.FDict) << 5); 133 | byteFLG |= this.FCheck; 134 | 135 | return byteFLG; 136 | } 137 | #endregion 138 | #region "Metodos publicos" 139 | public byte[] EncodeZlibHeader() 140 | { 141 | byte[] result = new byte[2]; 142 | 143 | this.RefreshFCheck(); 144 | 145 | result[0] = this.GetCMF(); 146 | result[1] = this.GetFLG(); 147 | 148 | return result; 149 | } 150 | #endregion 151 | #region "Metodos estáticos" 152 | public static ZLibHeader DecodeHeader(int pCMF, int pFlag) 153 | { 154 | ZLibHeader result = new ZLibHeader(); 155 | 156 | //Ensure that parameters are bytes 157 | pCMF = pCMF & 0x0FF; 158 | pFlag = pFlag & 0x0FF; 159 | 160 | //Decode bytes 161 | result.CompressionInfo = Convert.ToByte((pCMF & 0xF0) >> 4); 162 | result.CompressionMethod = Convert.ToByte(pCMF & 0x0F); 163 | 164 | result.FCheck = Convert.ToByte(pFlag & 0x1F); 165 | result.FDict = Convert.ToBoolean(Convert.ToByte((pFlag & 0x20) >> 5)); 166 | result.FLevel = (FLevel)Convert.ToByte((pFlag & 0xC0) >> 6); 167 | 168 | result.IsSupportedZLibStream = (result.CompressionMethod == 8) && (result.CompressionInfo == 7) && (((pCMF * 256 + pFlag) % 31 == 0)) && (result.FDict == false); 169 | 170 | return result; 171 | } 172 | #endregion 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Server/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlummersSoftwareLLC/NightDriverServer/909e8ee5d4deaa01eb833dbf6b8e8eefba1618f4/Server/favicon.ico -------------------------------------------------------------------------------- /Server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flat UI Template 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | 24 |
25 | 26 | 27 |
28 | Lorem ipsum dolor sit amet, consecatur adsipicing aut vit ghut jut, 29 | Lorem ipsum dolor sit amet, consecatur adsipicing aut vit ghut jut, 30 | Lorem ipsum dolor sit amet, consecatur adsipicing aut vit ghut jut, 31 | Lorem ipsum dolor sit amet, consecatur adsipicing aut vit ghut jut, 32 | 33 | Lorem ipsum dolor sit amet, consecatur adsipicing aut vit ghut jut, 34 | Lorem ipsum dolor sit amet, consecatur adsipicing aut vit ghut jut, 35 | Lorem ipsum dolor sit amet, consecatur adsipicing aut vit ghut jut, 36 | Lorem ipsum dolor sit amet, consecatur adsipicing aut vit ghut jut, 37 | 38 |
39 |
40 |
41 |
42 |

Hello, world!

43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | using Microsoft.Extensions.Logging; 13 | 14 | namespace WebHost 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | services.AddControllers(); 29 | services.AddCors(options => 30 | { 31 | options.AddDefaultPolicy( 32 | builder => 33 | { 34 | builder.AllowAnyOrigin(); 35 | }); 36 | }); 37 | 38 | services.AddMvc(); 39 | services.AddOptions(); 40 | } 41 | 42 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 43 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 44 | { 45 | if (env.IsDevelopment()) 46 | { 47 | app.UseDeveloperExceptionPage(); 48 | } 49 | 50 | app.UseHttpsRedirection(); 51 | app.UseDefaultFiles(); 52 | app.UseStaticFiles(); 53 | app.UseRouting(); 54 | app.UseAuthorization(); 55 | app.UseCors(); 56 | 57 | app.UseEndpoints(endpoints => 58 | { 59 | endpoints.MapControllers(); 60 | }); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /make_docker.sh: -------------------------------------------------------------------------------- 1 | git pull 2 | echo "Enter the password for davepl\'s Docker Hub:" 3 | docker login -u davepl 4 | 5 | # Build and push a multi-arch image for amd64 and arm64 6 | docker buildx build -t davepl/nightdriverserver:latest --platform linux/amd64,linux/arm64 --push . 7 | 8 | 9 | -------------------------------------------------------------------------------- /wwwroot/assets/datalist.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Nome": "", 4 | "Cognome": "", 5 | "DataN": "0000-00-00", 6 | "Provincia": "", 7 | "Comune": "", 8 | "CAP": "", 9 | "Indirizzo": "", 10 | "Fisso": "", 11 | "Mobile": "", 12 | "Note": "" 13 | }, 14 | { 15 | "Nome": "Federico", 16 | "Cognome": "Lupieri", 17 | "DataN": "2015-09-16", 18 | "Provincia": "", 19 | "Comune": "", 20 | "CAP": "34170", 21 | "Indirizzo": "Via Ascoli 1", 22 | "Fisso": "00112233445566", 23 | "Mobile": "00112233445566", 24 | "Note": "Vediamo se funziona questo" 25 | } 26 | ]; 27 | 28 | -------------------------------------------------------------------------------- /wwwroot/assets/index.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Material Dashboard by Creative Tim 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 |
40 | 110 |
111 | 112 | 176 | 177 |
178 |
179 |
180 | 181 |
182 |
183 |
184 |
185 | input 186 |
187 |

Locations

188 |

4

189 |
190 | 195 |
196 |
197 | 198 |
199 |
200 |
201 |
202 | wifi 203 |
204 |

Active Strips

205 |

16 206 |

207 |
208 | 213 |
214 |
215 | 216 | 217 |
218 |
219 |
220 |
221 | grade 222 |
223 |

Effects

224 |

20

225 |
226 | 231 |
232 |
233 | 234 |
235 |
236 |
237 |
238 | cast 239 |
240 |

Data Rate

241 |

20k

242 |
243 | 248 |
249 |
250 | 251 |
252 |
253 | 254 |
255 |
256 |
257 |
258 |
259 |
260 |

Daily Sales

261 |

262 | 55% increase in today sales.

263 |
264 | 269 |
270 |
271 | 272 |
273 |
274 |
275 |
276 |
277 |
278 |

Email Subscriptions

279 |

Last Campaign Performance

280 |
281 | 286 |
287 |
288 | 289 |
290 | 291 |
292 | 293 |
294 | 295 | 296 |
297 |
298 |
299 |
300 |

Active Strips

301 |
302 | 303 |
304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 |
HostNameSocketStatusBytesStatus
318 | 319 |
320 |
321 |
322 |
323 |
324 |
325 | 326 | 361 |
362 |
363 |
364 | 430 |
431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 487 | 488 | 489 | 490 | 491 | -------------------------------------------------------------------------------- /wwwroot/assets/scss/material-dashboard/variables/_code.scss: -------------------------------------------------------------------------------- 1 | // Code 2 | 3 | $code-bg: $grey-200 !default; // #f7f7f9 !default; 4 | -------------------------------------------------------------------------------- /wwwroot/assets/scss/material-dashboard/variables/_drawer.scss: -------------------------------------------------------------------------------- 1 | // Drawer 2 | 3 | // Sizing 4 | $bmd-drawer-x-size: 240px !default; 5 | $bmd-drawer-y-size: 100px !default; 6 | -------------------------------------------------------------------------------- /wwwroot/assets/scss/material-dashboard/variables/_layout.scss: -------------------------------------------------------------------------------- 1 | // Layout variables - evidently nothing to see here...remove now? 2 | -------------------------------------------------------------------------------- /wwwroot/assets/scss/material-dashboard/variables/_tooltip.scss: -------------------------------------------------------------------------------- 1 | $tooltip-bg: rgba($grey-700, .9); 2 | -------------------------------------------------------------------------------- /wwwroot/assets/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
NomeCognomeData NascitaProvinciaComuneCAPIndirizzoFissoCellulareNote
28 | 29 | 30 | 31 | 51 | 52 | -------------------------------------------------------------------------------- /wwwroot/default.html: -------------------------------------------------------------------------------- 1 | This is default in wwwroot 2 | -------------------------------------------------------------------------------- /wwwroot/docs/documentation.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Components Documentation - Material Dashboard by Creative Tim 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 66 | 67 | 82 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 331 | 332 | 333 | 334 | -------------------------------------------------------------------------------- /wwwroot/index.html: -------------------------------------------------------------------------------- 1 | This is index in wwwroot 2 | -------------------------------------------------------------------------------- /wwwroot/template.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | Hello, world! 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 60 |
61 | 62 | 85 | 86 |
87 |
88 | 89 |
90 |
91 | 112 |
113 |
114 | 115 | 116 | 117 | --------------------------------------------------------------------------------