├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── codeql.yml │ ├── nuget.yaml │ └── test-report.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Documents └── HID_output.xlsx ├── LICENSE ├── PackNuget.ps1 ├── README.md └── src ├── Directory.Build.props ├── Sleddog.Blink1.ExplicitTests ├── Blink1ConnectorTests.cs ├── Blink1Fixture.cs ├── Blink1Mk2Fixture.cs ├── Blink1Mk2Tests.cs ├── Blink1Tests.cs ├── BlinkHardwareScannerAttribute.cs ├── RequireBlink1HardwareAttribute.cs ├── RequireBlink1Mk2HardwareAttribute.cs ├── RequireBlinkHardwareAttribute.cs ├── RequireNoBlinkHardwareAttribute.cs ├── Sleddog.Blink1.ExplicitTests.csproj └── packages.lock.json ├── Sleddog.Blink1.Tests ├── Blink1DurationTests.cs ├── Colors │ ├── ColorGeneratorTests.cs │ ├── GammaCorrectorTests.cs │ └── HSLTests.cs ├── Sleddog.Blink1.Tests.csproj └── packages.lock.json ├── Sleddog.Blink1.sln ├── Sleddog.Blink1.sln.DotSettings └── Sleddog.Blink1 ├── Blink1.cs ├── Blink1Connector.cs ├── Blink1Identifier.cs ├── Blink1Mk2.cs ├── Blink1Preset.cs ├── Colors ├── ColorGenerator.cs ├── GammaCorrector.cs └── HSL.cs ├── Commands ├── DisableInactivityModeCommand.cs ├── EnableInactivityModeCommand.cs ├── FadeToColorCommand.cs ├── PlayPresetCommand.cs ├── ReadPlaybackStateQuery.cs ├── ReadPresetQuery.cs ├── SavePresetsCommand.cs ├── SetColorCommand.cs ├── SetPresetCommand.cs ├── StopPresetCommand.cs └── VersionQuery.cs ├── IBlink1.cs ├── IBlink1Mk2.cs ├── Internal ├── Blink1CommandBus.cs ├── Blink1Commands.cs ├── Blink1Duration.cs ├── Interfaces │ ├── IBlink1Command.cs │ ├── IBlink1MultiCommand.cs │ └── IBlink1Query.cs └── ObservableExt.cs ├── LEDPosition.cs ├── PlaybackStatus.cs ├── Sleddog.Blink1.csproj ├── hidapi.dll └── packages.lock.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ps1 text 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/src" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | # Check for updates to GitHub Actions every week 16 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | #env: 13 | # NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Setup .NET 23 | uses: actions/setup-dotnet@v4 24 | with: 25 | dotnet-version: 9.0.x 26 | #cache: true 27 | 28 | - name: Restore dependencies 29 | working-directory: ./src 30 | run: dotnet restore 31 | 32 | - name: Build 33 | working-directory: ./src 34 | run: dotnet build --no-restore 35 | 36 | - name: Test 37 | working-directory: ./src 38 | run: dotnet test --no-build --no-restore --verbosity normal --logger "trx;LogFileName=test-results.trx" Sleddog.Blink1.Tests 39 | 40 | - uses: actions/upload-artifact@v4 41 | if: success() || failure() 42 | with: 43 | name: test-results 44 | path: '**/*.trx' 45 | retention-days: 1 -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '43 20 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | - name: Setup .NET 44 | uses: actions/setup-dotnet@v4 45 | with: 46 | dotnet-version: 9.0.x 47 | 48 | 49 | # Initializes the CodeQL tools for scanning. 50 | - name: Initialize CodeQL 51 | uses: github/codeql-action/init@v3 52 | with: 53 | languages: ${{ matrix.language }} 54 | queries: security-and-quality 55 | # If you wish to specify custom queries, you can do so here or in a config file. 56 | # By default, queries listed here will override any specified in a config file. 57 | # Prefix the list here with "+" to use these queries and those in the config file. 58 | 59 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 60 | # queries: security-extended,security-and-quality 61 | 62 | 63 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 64 | # If this step fails, then you should remove it and run the build manually (see below) 65 | - name: Autobuild 66 | uses: github/codeql-action/autobuild@v3 67 | 68 | # ℹ️ Command-line programs to run using the OS shell. 69 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 70 | 71 | # If the Autobuild fails above, remove it and uncomment the following three lines. 72 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 73 | 74 | # - run: | 75 | # echo "Run, Build Application using script" 76 | # ./location_of_script_within_repo/buildscript.sh 77 | 78 | - name: Perform CodeQL Analysis 79 | uses: github/codeql-action/analyze@v3 80 | with: 81 | category: "/language:${{matrix.language}}" 82 | -------------------------------------------------------------------------------- /.github/workflows/nuget.yaml: -------------------------------------------------------------------------------- 1 | name: 'NuGet' 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | env: 12 | BUILD_CONFIG: 'Release' 13 | 14 | steps: 15 | # - name: Dump env 16 | # run: env | sort 17 | # - name: Dump GitHub context 18 | # env: 19 | # GITHUB_CONTEXT: ${{ toJson(github) }} 20 | # run: echo "$GITHUB_CONTEXT" 21 | - uses: actions/checkout@v4 22 | - name: Setup .NET 23 | uses: actions/setup-dotnet@v4 24 | with: 25 | dotnet-version: 8.0.x 26 | - name: Set VERSION variable from tag 27 | run: | 28 | assemblyversion=$(echo ${{ github.event.release.tag_name }} | cut -d- -f1 | cut -dv -f2) 29 | echo "ASSEMBLYVERSION=${assemblyversion}" >> $GITHUB_ENV 30 | version=$(echo ${{ github.event.release.tag_name }} | cut -dv -f2) 31 | echo "VERSION=${version}" >> $GITHUB_ENV 32 | - name: Build 33 | working-directory: ./src 34 | run: dotnet build --configuration Release /p:AssemblyVersion=${ASSEMBLYVERSION} /p:Version=${VERSION} 35 | - name: Test 36 | working-directory: ./src 37 | run: dotnet test --no-build 38 | - name: Pack 39 | working-directory: ./src 40 | run: dotnet pack --configuration Release /p:Version=${VERSION} --no-build --output . 41 | - uses: actions/upload-artifact@v4 42 | if: success() || failure() 43 | with: 44 | name: nuget-artifact 45 | path: '**/*.nupkg' 46 | retention-days: 1 47 | -------------------------------------------------------------------------------- /.github/workflows/test-report.yaml: -------------------------------------------------------------------------------- 1 | name: 'Test Report' 2 | on: 3 | workflow_run: 4 | workflows: ['CI'] 5 | types: 6 | - completed 7 | jobs: 8 | report: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Download test report file 12 | uses: actions/download-artifact@v4 13 | with: 14 | github-token: ${{ secrets.GH_DOWNLOAD_TOKEN }} 15 | pattern: test-results* 16 | run-id: ${{ github.event.workflow_run.id }} 17 | - name: Generate test report 18 | uses: dorny/test-reporter@v2 19 | with: 20 | #artifact: test-results 21 | name: dotNET Tests 22 | path: '**/*.trx' 23 | reporter: dotnet-trx 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ignore thumbnails created by windows 4 | Thumbs.db 5 | #Ignore files build by Visual Studio 6 | *.obj 7 | *.exe 8 | *.pdb 9 | *.user 10 | *.aps 11 | *.pch 12 | *.vspscc 13 | *_i.c 14 | *_p.c 15 | *.ncb 16 | *.suo 17 | *.tlb 18 | *.tlh 19 | *.bak 20 | *.cache 21 | *.ilk 22 | *.log 23 | [Bb]in 24 | [Dd]ebug*/ 25 | *.lib 26 | *.sbr 27 | obj/ 28 | [Rr]elease*/ 29 | .vs/ 30 | .vscode/ 31 | 32 | #Subversion files 33 | .svn 34 | 35 | #ReSharper 36 | _ReSharper*/ 37 | *.resharper 38 | [Tt]est[Rr]esult* 39 | 40 | #DotCover 41 | *.DotSettings 42 | 43 | #NuGet 44 | packages/ 45 | *.nupkg 46 | 47 | #NCrunch 48 | *ncrunch* 49 | *crunch*.local.xml 50 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at musher@sleddog.dk. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /Documents/HID_output.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleddogSoftwareDevelopment/blink1/036ee2e7cac69dcfbc910a9b277e5911f6627899/Documents/HID_output.xlsx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Stefan Daugaard Poulsen 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /PackNuget.ps1: -------------------------------------------------------------------------------- 1 | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue .\Build 2 | 3 | New-Item -ItemType Directory Build 4 | 5 | .\Nuget.exe pack -OutputDirectory .\Build .\src\Sleddog.Blink1\Sleddog.Blink1.csproj -Prop Configuration=Release 6 | 7 | .\Nuget.exe push -Source https://www.nuget.org/api/v2/package .\Build\*.nupkg 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Blink 2 | ===== 3 | 4 | .NET framework for [Blink(1)](http://thingm.com/products/blink-1.html) 5 | 6 | Ideas from the originial implementation of [ManagedBlink1](https://github.com/todbot/blink1/), but based on direct HID interaction instead of using the supplied C++ dll. 7 | 8 | Supports more features than the official library, with a focus on abstracting hardware knowledge away. 9 | 10 | ![Build badge...](https://github.com/SleddogSoftwareDevelopment/blink1/actions/workflows/ci.yml/badge.svg) 11 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | latest 4 | true 5 | all 6 | low 7 | true 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/Blink1ConnectorTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Xunit.Abstractions; 3 | 4 | namespace Sleddog.Blink1.ExplicitTests 5 | { 6 | public class Blink1ConnectorTests 7 | { 8 | private readonly ITestOutputHelper output; 9 | public Blink1ConnectorTests(ITestOutputHelper output) 10 | { 11 | this.output = output; 12 | } 13 | 14 | [RequireBlinkHardware] 15 | public void ScanFindsDevices() 16 | { 17 | var devices = Blink1Connector.Scan(); 18 | 19 | foreach(var device in devices) 20 | { 21 | output.WriteLine(device.SerialNumber); 22 | } 23 | 24 | Assert.NotEmpty(devices); 25 | } 26 | 27 | [RequireNoBlinkHardware] 28 | public void ScanWithNoDevicesFindsNone() 29 | { 30 | var devices = Blink1Connector.Scan(); 31 | 32 | Assert.Empty(devices); 33 | } 34 | 35 | [RequireBlinkHardware] 36 | public void ConnectToSpecificDevice() 37 | { 38 | var serialNumber = "0x20002BCE"; 39 | 40 | var device = Blink1Connector.Connect(serialNumber); 41 | 42 | Assert.NotNull(device); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/Blink1Fixture.cs: -------------------------------------------------------------------------------- 1 | namespace Sleddog.Blink1.ExplicitTests 2 | { 3 | public class Blink1Fixture : IDisposable 4 | { 5 | public Blink1Fixture() 6 | { 7 | var firstDevice = Blink1Connector.Scan().FirstOrDefault(b => !(b is IBlink1Mk2)); 8 | 9 | if(firstDevice != null) 10 | Device = firstDevice; 11 | } 12 | 13 | public IBlink1? Device { get; } 14 | 15 | public void Dispose() 16 | { 17 | Device?.Dispose(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/Blink1Mk2Fixture.cs: -------------------------------------------------------------------------------- 1 | namespace Sleddog.Blink1.ExplicitTests 2 | { 3 | public class Blink1Mk2Fixture : IDisposable 4 | { 5 | public Blink1Mk2Fixture() 6 | { 7 | var firstDevice = Blink1Connector.Scan().FirstOrDefault(b => b is IBlink1Mk2); 8 | 9 | if(firstDevice != null) 10 | Device = (IBlink1Mk2) firstDevice; 11 | } 12 | 13 | public IBlink1Mk2? Device { get; } 14 | 15 | public void Dispose() 16 | { 17 | Device?.Dispose(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/Blink1Mk2Tests.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Xunit; 3 | using Xunit.Abstractions; 4 | 5 | namespace Sleddog.Blink1.ExplicitTests 6 | { 7 | public class Blink1Mk2Tests : IClassFixture 8 | { 9 | private const string LowestSerialNumber = "0x20001000"; 10 | private const string HighestSerialNumber = "0x20003710"; 11 | 12 | private readonly IBlink1Mk2 blink1; 13 | private readonly ITestOutputHelper output; 14 | 15 | public Blink1Mk2Tests(Blink1Mk2Fixture fixture, ITestOutputHelper output) 16 | { 17 | blink1 = fixture.Device; 18 | this.output = output; 19 | } 20 | 21 | [RequireBlink1Mk2Hardware] 22 | public void SetAllPatterns() 23 | { 24 | blink1.Save(new Blink1Preset(Color.Cyan, TimeSpan.FromSeconds(5)), 0); 25 | blink1.Save(new Blink1Preset(Color.DarkCyan, TimeSpan.FromSeconds(5)), 1); 26 | blink1.Save(new Blink1Preset(Color.CadetBlue, TimeSpan.FromSeconds(5)), 2); 27 | blink1.Save(new Blink1Preset(Color.SteelBlue, TimeSpan.FromSeconds(5)), 3); 28 | blink1.Save(new Blink1Preset(Color.DodgerBlue, TimeSpan.FromSeconds(5)), 4); 29 | blink1.Save(new Blink1Preset(Color.MediumBlue, TimeSpan.FromSeconds(5)), 5); 30 | blink1.Save(new Blink1Preset(Color.DarkBlue, TimeSpan.FromSeconds(5)), 6); 31 | blink1.Save(new Blink1Preset(Color.Green, TimeSpan.FromSeconds(5)), 7); 32 | blink1.Save(new Blink1Preset(Color.SeaGreen, TimeSpan.FromSeconds(5)), 8); 33 | blink1.Save(new Blink1Preset(Color.MediumSeaGreen, TimeSpan.FromSeconds(5)), 9); 34 | blink1.Save(new Blink1Preset(Color.SpringGreen, TimeSpan.FromSeconds(5)), 10); 35 | blink1.Save(new Blink1Preset(Color.LightGreen, TimeSpan.FromSeconds(5)), 11); 36 | } 37 | 38 | [RequireBlink1Mk2Hardware] 39 | public void ReadSerialReadsValidSerialNumber() 40 | { 41 | var actual = blink1.SerialNumber; 42 | 43 | output.WriteLine($"Found serial: {actual}"); 44 | 45 | Assert.InRange(actual, LowestSerialNumber, HighestSerialNumber); 46 | } 47 | 48 | [RequireBlink1Mk2Hardware] 49 | public void ReadPresetReturnsValidPreset() 50 | { 51 | var actual = blink1.ReadPreset(0); 52 | 53 | Assert.NotNull(actual); 54 | } 55 | 56 | [RequireBlink1Mk2Hardware(Skip = "Current issue with color comparison, but it is right")] 57 | public void SavePresetWritesToDevice() 58 | { 59 | var expected = new Blink1Preset(Color.FromArgb(255, 50, 100, 200), TimeSpan.FromSeconds(1.5)); 60 | 61 | blink1.EnableGamma = false; 62 | 63 | blink1.Save(expected, 0); 64 | 65 | var actual = blink1.ReadPreset(0); 66 | 67 | Assert.Equal(expected, actual); 68 | } 69 | 70 | [RequireBlink1Mk2Hardware] 71 | public void SetColor() 72 | { 73 | var actual = blink1.Set(Color.Blue); 74 | 75 | Assert.True(actual); 76 | } 77 | 78 | [RequireBlink1Mk2Hardware] 79 | public void ShowColor() 80 | { 81 | var showColorTime = TimeSpan.FromSeconds(2); 82 | 83 | var actual = blink1.Show(Color.Chartreuse, showColorTime); 84 | 85 | Thread.Sleep(showColorTime); 86 | 87 | Assert.True(actual); 88 | } 89 | 90 | [RequireBlink1Mk2Hardware] 91 | public void FadeToColor() 92 | { 93 | var fadeDuration = TimeSpan.FromSeconds(2); 94 | 95 | var actual = blink1.Fade(Color.Red, fadeDuration); 96 | 97 | Thread.Sleep(fadeDuration); 98 | 99 | Assert.True(actual); 100 | } 101 | 102 | [RequireBlink1Mk2Hardware] 103 | public void SetPreset0AndPlayIt() 104 | { 105 | var presetDuration = TimeSpan.FromSeconds(2); 106 | 107 | var preset = new Blink1Preset(Color.DarkGoldenrod, presetDuration); 108 | 109 | blink1.Save(preset, 0); 110 | 111 | blink1.Play(0); 112 | 113 | Thread.Sleep(presetDuration); 114 | 115 | blink1.Pause(); 116 | } 117 | 118 | [RequireBlink1Mk2Hardware] 119 | public void PlayPreset() 120 | { 121 | blink1.Play(0, 11, 0); 122 | } 123 | 124 | [RequireBlink1Mk2Hardware] 125 | public void PoliceBlinking() 126 | { 127 | for (var i = 0; i < 20; i++) 128 | { 129 | blink1.Fade(Color.Blue, TimeSpan.FromMilliseconds(25), (LEDPosition)(i % 2)); 130 | blink1.Fade(Color.Red, TimeSpan.FromMilliseconds(25), (LEDPosition)(i % 2 + 1)); 131 | 132 | Thread.Sleep(200); 133 | } 134 | 135 | blink1.Set(Color.Black); 136 | } 137 | 138 | [RequireBlink1Mk2Hardware] 139 | public void TurnOff() 140 | { 141 | blink1.Set(Color.Black); 142 | } 143 | 144 | [RequireBlink1Mk2Hardware] 145 | public void EnableInactivityMode() 146 | { 147 | blink1.EnableInactivityMode(TimeSpan.FromMilliseconds(50)); 148 | 149 | Thread.Sleep(TimeSpan.FromMilliseconds(150)); 150 | 151 | blink1.DisableInactivityMode(); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/Blink1Tests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestPlatform.Utilities; 2 | using System; 3 | using System.Drawing; 4 | using System.Threading; 5 | using Xunit; 6 | using Xunit.Abstractions; 7 | 8 | namespace Sleddog.Blink1.ExplicitTests 9 | { 10 | public class Blink1Tests : IClassFixture 11 | { 12 | private const string LowestSerialNumber = "0x1A001000"; 13 | private const string HighestSerialNumber = "0x1A002FFF"; 14 | 15 | private readonly IBlink1 blink1; 16 | private readonly ITestOutputHelper output; 17 | 18 | public Blink1Tests(Blink1Fixture fixture, ITestOutputHelper output) 19 | { 20 | blink1 = fixture.Device; 21 | this.output = output; 22 | } 23 | 24 | [RequireBlink1Hardware] 25 | public void SetAllPatterns() 26 | { 27 | blink1.Save(new Blink1Preset(Color.Cyan, TimeSpan.FromSeconds(5)), 0); 28 | blink1.Save(new Blink1Preset(Color.DarkCyan, TimeSpan.FromSeconds(5)), 1); 29 | blink1.Save(new Blink1Preset(Color.CadetBlue, TimeSpan.FromSeconds(5)), 2); 30 | blink1.Save(new Blink1Preset(Color.SteelBlue, TimeSpan.FromSeconds(5)), 3); 31 | blink1.Save(new Blink1Preset(Color.DodgerBlue, TimeSpan.FromSeconds(5)), 4); 32 | blink1.Save(new Blink1Preset(Color.MediumBlue, TimeSpan.FromSeconds(5)), 5); 33 | blink1.Save(new Blink1Preset(Color.DarkBlue, TimeSpan.FromSeconds(5)), 6); 34 | blink1.Save(new Blink1Preset(Color.Green, TimeSpan.FromSeconds(5)), 7); 35 | blink1.Save(new Blink1Preset(Color.SeaGreen, TimeSpan.FromSeconds(5)), 8); 36 | blink1.Save(new Blink1Preset(Color.MediumSeaGreen, TimeSpan.FromSeconds(5)), 9); 37 | blink1.Save(new Blink1Preset(Color.SpringGreen, TimeSpan.FromSeconds(5)), 10); 38 | blink1.Save(new Blink1Preset(Color.LightGreen, TimeSpan.FromSeconds(5)), 11); 39 | } 40 | 41 | [RequireBlink1Hardware] 42 | public void ReadSerialReadsValidSerialNumber() 43 | { 44 | var actual = blink1.SerialNumber; 45 | 46 | output.WriteLine($"Found serial: {actual}"); 47 | 48 | Assert.InRange(actual, LowestSerialNumber, HighestSerialNumber); 49 | } 50 | 51 | [RequireBlink1Hardware] 52 | public void ReadPresetReturnsValidPreset() 53 | { 54 | var actual = blink1.ReadPreset(0); 55 | 56 | Assert.NotNull(actual); 57 | } 58 | 59 | [RequireBlink1Hardware] 60 | public void SavePresetWritesToDevice() 61 | { 62 | var expected = new Blink1Preset(Color.DarkSlateGray, TimeSpan.FromSeconds(1.5)); 63 | 64 | blink1.EnableGamma = false; 65 | 66 | blink1.Save(expected, 0); 67 | 68 | var actual = blink1.ReadPreset(0); 69 | 70 | Assert.Equal(expected, actual); 71 | } 72 | 73 | [RequireBlink1Hardware] 74 | public void SetColor() 75 | { 76 | var actual = blink1.Set(Color.Blue); 77 | 78 | Assert.True(actual); 79 | } 80 | 81 | [RequireBlink1Hardware] 82 | public void ShowColor() 83 | { 84 | var showColorTime = TimeSpan.FromSeconds(2); 85 | 86 | var actual = blink1.Show(Color.Chartreuse, showColorTime); 87 | 88 | Thread.Sleep(showColorTime); 89 | 90 | Assert.True(actual); 91 | } 92 | 93 | [RequireBlink1Hardware] 94 | public void FadeToColor() 95 | { 96 | var fadeDuration = TimeSpan.FromSeconds(2); 97 | 98 | var actual = blink1.Fade(Color.Red, fadeDuration); 99 | 100 | Thread.Sleep(fadeDuration); 101 | 102 | Assert.True(actual); 103 | } 104 | 105 | [RequireBlink1Hardware] 106 | public void SetPreset0AndPlayIt() 107 | { 108 | var presetDuration = TimeSpan.FromSeconds(2); 109 | 110 | var preset = new Blink1Preset(Color.DarkGoldenrod, presetDuration); 111 | 112 | blink1.Save(preset, 0); 113 | 114 | blink1.Play(0); 115 | 116 | Thread.Sleep(presetDuration); 117 | 118 | blink1.Pause(); 119 | } 120 | 121 | [RequireBlink1Hardware] 122 | public void PlayPreset() 123 | { 124 | blink1.Play(0); 125 | 126 | Thread.Sleep(TimeSpan.FromSeconds(5)); 127 | 128 | blink1.Pause(); 129 | } 130 | 131 | [RequireBlink1Hardware] 132 | public void TurnOff() 133 | { 134 | blink1.Set(Color.Black); 135 | } 136 | 137 | [RequireBlink1Hardware] 138 | public void EnableInactivityMode() 139 | { 140 | blink1.EnableInactivityMode(TimeSpan.FromMilliseconds(50)); 141 | 142 | Thread.Sleep(TimeSpan.FromMilliseconds(150)); 143 | 144 | blink1.DisableInactivityMode(); 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/BlinkHardwareScannerAttribute.cs: -------------------------------------------------------------------------------- 1 | using HidApi; 2 | using Xunit; 3 | 4 | namespace Sleddog.Blink1.ExplicitTests 5 | { 6 | public abstract class BlinkHardwareScannerAttribute : FactAttribute 7 | { 8 | private const int VendorId = 0x27B8; 9 | private const int ProductId = 0x01ED; 10 | 11 | protected IEnumerable devices; 12 | 13 | protected BlinkHardwareScannerAttribute() 14 | { 15 | devices = Hid.Enumerate(VendorId, ProductId); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/RequireBlink1HardwareAttribute.cs: -------------------------------------------------------------------------------- 1 | using HidApi; 2 | 3 | namespace Sleddog.Blink1.ExplicitTests 4 | { 5 | public class RequireBlink1HardwareAttribute : RequireBlinkHardwareAttribute 6 | { 7 | public RequireBlink1HardwareAttribute() 8 | { 9 | var blink1Devices = (from d in devices where IsDeviceWithinBlink1Range(d) select d).ToArray(); 10 | 11 | if(!blink1Devices.Any()) 12 | { 13 | Skip = "No Blink1 units connected"; 14 | } 15 | } 16 | 17 | private bool IsDeviceWithinBlink1Range(DeviceInfo deviceInfo) 18 | { 19 | return deviceInfo.SerialNumber[0] >= 0x31; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/RequireBlink1Mk2HardwareAttribute.cs: -------------------------------------------------------------------------------- 1 | using HidApi; 2 | 3 | namespace Sleddog.Blink1.ExplicitTests 4 | { 5 | public class RequireBlink1Mk2HardwareAttribute : RequireBlinkHardwareAttribute 6 | { 7 | public RequireBlink1Mk2HardwareAttribute() 8 | { 9 | var blink1Devices = (from d in devices where IsDeviceWithinBlink1mk2Range(d) select d).ToArray(); 10 | 11 | if (!blink1Devices.Any()) 12 | { 13 | Skip = "No Blink1mk2 units connected"; 14 | } 15 | } 16 | 17 | private bool IsDeviceWithinBlink1mk2Range(DeviceInfo deviceInfo) 18 | { 19 | return deviceInfo.SerialNumber[0] >= 0x32; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/RequireBlinkHardwareAttribute.cs: -------------------------------------------------------------------------------- 1 | using HidApi; 2 | 3 | namespace Sleddog.Blink1.ExplicitTests 4 | { 5 | public class RequireBlinkHardwareAttribute : BlinkHardwareScannerAttribute 6 | { 7 | public RequireBlinkHardwareAttribute() 8 | { 9 | if (!devices.Any()) 10 | Skip = "No Blink1 devices connected"; 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/RequireNoBlinkHardwareAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Sleddog.Blink1.ExplicitTests 2 | { 3 | public class RequireNoBlinkHardwareAttribute : BlinkHardwareScannerAttribute 4 | { 5 | public RequireNoBlinkHardwareAttribute() 6 | { 7 | if (devices.Any()) 8 | { 9 | Skip = "Blink1 devices connected"; 10 | } 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/Sleddog.Blink1.ExplicitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.ExplicitTests/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net9.0": { 5 | "Microsoft.NET.Test.Sdk": { 6 | "type": "Direct", 7 | "requested": "[17.13.0, )", 8 | "resolved": "17.13.0", 9 | "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", 10 | "dependencies": { 11 | "Microsoft.CodeCoverage": "17.13.0", 12 | "Microsoft.TestPlatform.TestHost": "17.13.0" 13 | } 14 | }, 15 | "xunit": { 16 | "type": "Direct", 17 | "requested": "[2.9.3, )", 18 | "resolved": "2.9.3", 19 | "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", 20 | "dependencies": { 21 | "xunit.analyzers": "1.18.0", 22 | "xunit.assert": "2.9.3", 23 | "xunit.core": "[2.9.3]" 24 | } 25 | }, 26 | "xunit.runner.visualstudio": { 27 | "type": "Direct", 28 | "requested": "[3.1.0, )", 29 | "resolved": "3.1.0", 30 | "contentHash": "K9O9TOzugqOo4LJ87uuq1VG8RAqGp20Ng85Wx932oT5LNBkIgeeGYubVW5UMnOOTanFNbGavmbuYrJr4INzSwg==" 31 | }, 32 | "HidApi.Net": { 33 | "type": "Transitive", 34 | "resolved": "1.1.0", 35 | "contentHash": "PrIvZYB8zPWahCPNiapJ9r2e1GC8wPgjlX8W2H+39wSDuDzKG3t5cMzM0SrupT4Cws4x95C8PkAzRdEoglMo0Q==", 36 | "dependencies": { 37 | "WCharT.Net": "0.1.2" 38 | } 39 | }, 40 | "Microsoft.CodeCoverage": { 41 | "type": "Transitive", 42 | "resolved": "17.13.0", 43 | "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" 44 | }, 45 | "Microsoft.TestPlatform.ObjectModel": { 46 | "type": "Transitive", 47 | "resolved": "17.13.0", 48 | "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", 49 | "dependencies": { 50 | "System.Reflection.Metadata": "1.6.0" 51 | } 52 | }, 53 | "Microsoft.TestPlatform.TestHost": { 54 | "type": "Transitive", 55 | "resolved": "17.13.0", 56 | "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", 57 | "dependencies": { 58 | "Microsoft.TestPlatform.ObjectModel": "17.13.0", 59 | "Newtonsoft.Json": "13.0.1" 60 | } 61 | }, 62 | "Newtonsoft.Json": { 63 | "type": "Transitive", 64 | "resolved": "13.0.1", 65 | "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" 66 | }, 67 | "System.Reactive": { 68 | "type": "Transitive", 69 | "resolved": "6.0.1", 70 | "contentHash": "rHaWtKDwCi9qJ3ObKo8LHPMuuwv33YbmQi7TcUK1C264V3MFnOr5Im7QgCTdLniztP3GJyeiSg5x8NqYJFqRmg==" 71 | }, 72 | "System.Reflection.Metadata": { 73 | "type": "Transitive", 74 | "resolved": "1.6.0", 75 | "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" 76 | }, 77 | "WCharT.Net": { 78 | "type": "Transitive", 79 | "resolved": "0.1.2", 80 | "contentHash": "WAGMmSxbejfwy2Po750WzyCgE5+o8B//2oUbLgfgOiWRU3U3uwd1f3OG//oyjfln4NLEuLVUp80vqiv+R5JD+w==" 81 | }, 82 | "xunit.abstractions": { 83 | "type": "Transitive", 84 | "resolved": "2.0.3", 85 | "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" 86 | }, 87 | "xunit.analyzers": { 88 | "type": "Transitive", 89 | "resolved": "1.18.0", 90 | "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" 91 | }, 92 | "xunit.assert": { 93 | "type": "Transitive", 94 | "resolved": "2.9.3", 95 | "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" 96 | }, 97 | "xunit.core": { 98 | "type": "Transitive", 99 | "resolved": "2.9.3", 100 | "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", 101 | "dependencies": { 102 | "xunit.extensibility.core": "[2.9.3]", 103 | "xunit.extensibility.execution": "[2.9.3]" 104 | } 105 | }, 106 | "xunit.extensibility.core": { 107 | "type": "Transitive", 108 | "resolved": "2.9.3", 109 | "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", 110 | "dependencies": { 111 | "xunit.abstractions": "2.0.3" 112 | } 113 | }, 114 | "xunit.extensibility.execution": { 115 | "type": "Transitive", 116 | "resolved": "2.9.3", 117 | "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", 118 | "dependencies": { 119 | "xunit.extensibility.core": "[2.9.3]" 120 | } 121 | }, 122 | "sleddog.blink1": { 123 | "type": "Project", 124 | "dependencies": { 125 | "HidApi.Net": "[1.1.0, )", 126 | "System.Reactive": "[6.0.1, )" 127 | } 128 | } 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1.Tests/Blink1DurationTests.cs: -------------------------------------------------------------------------------- 1 | using Sleddog.Blink1.Internal; 2 | using Xunit; 3 | 4 | namespace Sleddog.Blink1.Tests 5 | { 6 | public class Blink1DurationTests 7 | { 8 | private static readonly Random Random = new Random(); 9 | 10 | [Theory, MemberData(nameof(HighTestData))] 11 | public void HighIsSetCorrectlyFromTimeSpanCtorInput(uint timeInMilliseconds, byte expected) 12 | { 13 | var ts = TimeSpan.FromMilliseconds(timeInMilliseconds); 14 | 15 | var sut = new Blink1Duration(ts); 16 | 17 | var actual = sut.High; 18 | 19 | Assert.Equal(expected, actual); 20 | } 21 | 22 | [Theory, MemberData(nameof(LowTestData))] 23 | public void LowIsSetCorrectlyFromTimeSpanCtorInput(uint timeInMilliseconds, byte expected) 24 | { 25 | var ts = TimeSpan.FromMilliseconds(timeInMilliseconds); 26 | 27 | var sut = new Blink1Duration(ts); 28 | 29 | var actual = sut.Low; 30 | 31 | Assert.Equal(expected, actual); 32 | } 33 | 34 | [Theory, MemberData(nameof(ImplicitTestData))] 35 | public void ImplicitConversionToTimeSpan(uint timeInMilliseconds, uint expected) 36 | { 37 | var ts = TimeSpan.FromMilliseconds(timeInMilliseconds); 38 | 39 | var sut = new Blink1Duration(ts); 40 | 41 | TimeSpan actual = sut; 42 | 43 | Assert.Equal(expected, actual.TotalMilliseconds); 44 | } 45 | 46 | public static IEnumerable HighTestData 47 | { 48 | get 49 | { 50 | yield return new object[] {0u, new byte()}; 51 | 52 | foreach (var val in GenerateSampleData()) 53 | { 54 | var expected = Convert.ToByte(CalculateOutcomeValue(val) >> 8); 55 | 56 | yield return new object[] {val, expected}; 57 | } 58 | } 59 | } 60 | 61 | public static IEnumerable LowTestData 62 | { 63 | get 64 | { 65 | yield return new object[] {0u, new byte()}; 66 | 67 | foreach (var val in GenerateSampleData()) 68 | { 69 | var expected = Convert.ToByte(CalculateOutcomeValue(val) & 0xFF); 70 | 71 | yield return new object[] {val, expected}; 72 | } 73 | } 74 | } 75 | 76 | public static IEnumerable ImplicitTestData 77 | { 78 | get 79 | { 80 | yield return new object[] {0u, 0u}; 81 | yield return new object[] {250u, 250u}; 82 | yield return new object[] {254u, 250u}; 83 | yield return new object[] {255u, 260u}; 84 | yield return new object[] {256u, 260u}; 85 | 86 | foreach (var val in GenerateSampleData()) 87 | { 88 | var expected = CalculateOutcomeValue(val)*10; 89 | 90 | yield return new object[] {val, expected}; 91 | } 92 | } 93 | } 94 | 95 | private static uint CalculateOutcomeValue(uint val) 96 | { 97 | var blink1Duration = (double) val/10; 98 | var roundedDuration = Math.Round(blink1Duration, 0, MidpointRounding.ToEven); 99 | 100 | return Convert.ToUInt32(roundedDuration); 101 | } 102 | 103 | private static IEnumerable GenerateSampleData() 104 | { 105 | return Enumerable.Range(0, 15).Select(_ => (uint) Random.Next(0, 365000)); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1.Tests/Colors/ColorGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using AutoFixture.Xunit2; 3 | using Sleddog.Blink1.Colors; 4 | using Xunit; 5 | 6 | namespace Sleddog.Blink1.Tests.Colors 7 | { 8 | public class ColorGeneratorTests 9 | { 10 | [Theory] 11 | [AutoData] 12 | public void GenerateColorsReturnsTheGivenCountOfColors(int numberOfColors) 13 | { 14 | var sut = new ColorGenerator(); 15 | 16 | var colors = sut.GenerateColors(numberOfColors); 17 | 18 | var expected = numberOfColors; 19 | var actual = colors.Count; 20 | 21 | Assert.Equal(expected, actual); 22 | } 23 | 24 | [Theory] 25 | [AutoData] 26 | public void GeneratedColorsAreDifferent(int numberOfColors) 27 | { 28 | var sut = new ColorGenerator(); 29 | 30 | var expected = sut.GenerateColors(numberOfColors); 31 | 32 | var actual = expected.Distinct(); 33 | 34 | Assert.Equal(expected, actual); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1.Tests/Colors/GammaCorrectorTests.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Sleddog.Blink1.Colors; 3 | using Xunit; 4 | using AutoFixture.Xunit2; 5 | 6 | namespace Sleddog.Blink1.Tests.Colors 7 | { 8 | public class GammaCorrectorTests 9 | { 10 | [Fact] 11 | public void Encode_Black_ReturnsBlack() 12 | { 13 | var corrector = new GammaCorrector(); 14 | var black = Color.FromArgb(0, 0, 0); 15 | var result = corrector.Encode(black); 16 | Assert.Equal(black, result); 17 | } 18 | 19 | [Fact] 20 | public void Encode_White_ReturnsWhite() 21 | { 22 | var corrector = new GammaCorrector(); 23 | var white = Color.FromArgb(255, 255, 255); 24 | var result = corrector.Encode(white); 25 | Assert.Equal(white, result); 26 | } 27 | 28 | [Theory] 29 | [AutoData] 30 | public void Encode_Color_ReturnsExpectedGammaCorrectedColor(int r, int g, int b) 31 | { 32 | var corrector = new GammaCorrector(); 33 | var color = Color.FromArgb(r, g, b); 34 | var result = corrector.Encode(color); 35 | var expected = Color.FromArgb( 36 | GammaCorrect(color.R, 2.2), 37 | GammaCorrect(color.G, 2.2), 38 | GammaCorrect(color.B, 2.2)); 39 | Assert.Equal(expected, result); 40 | } 41 | 42 | private static int GammaCorrect(int value, double gamma) 43 | { 44 | return (int)Math.Round(Math.Pow(value / 255.0, gamma) * 255); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.Tests/Colors/HSLTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using AutoFixture.Xunit2; 5 | using Sleddog.Blink1.Colors; 6 | using Xunit; 7 | 8 | namespace Sleddog.Blink1.Tests.Colors 9 | { 10 | public class HslTests 11 | { 12 | public static IEnumerable Hsl2Rgb 13 | { 14 | get 15 | { 16 | return new[] 17 | { 18 | new object[] {(ushort) 0, 0, 1, Color.FromArgb(255, 255, 255)}, 19 | new object[] {(ushort) 0, 0, 0.502f, Color.FromArgb(128, 128, 128)}, 20 | new object[] {(ushort) 0, 0, 0, Color.FromArgb(0, 0, 0)}, 21 | new object[] {(ushort) 0, 1, 0.5f, Color.FromArgb(255, 0, 0)}, 22 | new object[] {(ushort) 120, 1, 0.5f, Color.FromArgb(0, 255, 0)}, 23 | new object[] {(ushort) 240, 1, 0.5f, Color.FromArgb(0, 0, 255)}, 24 | new object[] {(ushort) 284, 0.807f, 0.224f, Color.FromArgb(78, 11, 103)}, 25 | new object[] {(ushort) 210, 0.50f, 0.165f, Color.FromArgb(21, 42, 63)} 26 | }; 27 | } 28 | } 29 | 30 | [Theory] 31 | [InlineData((ushort) 361, 0, 0)] 32 | [InlineData((ushort) 0, 1.1f, 0)] 33 | [InlineData((ushort) 0, 0, 1.1f)] 34 | public void HslCtorBoundaryCheck(ushort hue, float saturation, float luminance) 35 | { 36 | Assert.Throws(() => new Hsl(hue, saturation, luminance)); 37 | } 38 | 39 | [Theory] 40 | [MemberData(nameof(Hsl2Rgb))] 41 | public void HsltoRgbIsConvertedCorrectly(ushort hue, float saturation, float luminance, Color expected) 42 | { 43 | var sut = new Hsl(hue, saturation, luminance); 44 | 45 | Color actual = sut; 46 | 47 | Assert.Equal(expected, actual); 48 | } 49 | 50 | [Theory] 51 | [AutoData] 52 | public void ZeroSaturationRendersColorFactoredByLuminance(ushort hue, float luminance) 53 | { 54 | var hueValue = (ushort) (hue % 360); 55 | var luminanceValue = luminance % 1; 56 | 57 | var hsl = new Hsl(hueValue, 0, luminanceValue); 58 | 59 | Color actual = hsl; 60 | 61 | var colorValue = luminanceValue / 1 * 255; 62 | 63 | var expectedColorValue = Convert.ToInt32(colorValue); 64 | 65 | var expected = Color.FromArgb(expectedColorValue, expectedColorValue, expectedColorValue); 66 | 67 | Assert.Equal(expected, actual); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1.Tests/Sleddog.Blink1.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.Tests/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net9.0": { 5 | "AutoFixture.Xunit2": { 6 | "type": "Direct", 7 | "requested": "[4.18.1, )", 8 | "resolved": "4.18.1", 9 | "contentHash": "I5Cwv1bvWb0lf2x2zO42bBQ2WaGudBh7tVBCzKIf8KmRJG+hmYY7ku3znnFZDVxbQaihNaqNkztLTwK4PwaoWg==", 10 | "dependencies": { 11 | "AutoFixture": "4.18.1", 12 | "xunit.extensibility.core": "[2.2.0, 3.0.0)" 13 | } 14 | }, 15 | "Microsoft.NET.Test.Sdk": { 16 | "type": "Direct", 17 | "requested": "[17.13.0, )", 18 | "resolved": "17.13.0", 19 | "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", 20 | "dependencies": { 21 | "Microsoft.CodeCoverage": "17.13.0", 22 | "Microsoft.TestPlatform.TestHost": "17.13.0" 23 | } 24 | }, 25 | "System.Net.Http": { 26 | "type": "Direct", 27 | "requested": "[4.3.4, )", 28 | "resolved": "4.3.4", 29 | "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", 30 | "dependencies": { 31 | "Microsoft.NETCore.Platforms": "1.1.1", 32 | "System.Collections": "4.3.0", 33 | "System.Diagnostics.Debug": "4.3.0", 34 | "System.Diagnostics.DiagnosticSource": "4.3.0", 35 | "System.Diagnostics.Tracing": "4.3.0", 36 | "System.Globalization": "4.3.0", 37 | "System.Globalization.Extensions": "4.3.0", 38 | "System.IO": "4.3.0", 39 | "System.IO.FileSystem": "4.3.0", 40 | "System.Net.Primitives": "4.3.0", 41 | "System.Resources.ResourceManager": "4.3.0", 42 | "System.Runtime": "4.3.0", 43 | "System.Runtime.Extensions": "4.3.0", 44 | "System.Runtime.Handles": "4.3.0", 45 | "System.Runtime.InteropServices": "4.3.0", 46 | "System.Security.Cryptography.Algorithms": "4.3.0", 47 | "System.Security.Cryptography.Encoding": "4.3.0", 48 | "System.Security.Cryptography.OpenSsl": "4.3.0", 49 | "System.Security.Cryptography.Primitives": "4.3.0", 50 | "System.Security.Cryptography.X509Certificates": "4.3.0", 51 | "System.Text.Encoding": "4.3.0", 52 | "System.Threading": "4.3.0", 53 | "System.Threading.Tasks": "4.3.0", 54 | "runtime.native.System": "4.3.0", 55 | "runtime.native.System.Net.Http": "4.3.0", 56 | "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" 57 | } 58 | }, 59 | "System.Text.RegularExpressions": { 60 | "type": "Direct", 61 | "requested": "[4.3.1, )", 62 | "resolved": "4.3.1", 63 | "contentHash": "N0kNRrWe4+nXOWlpLT4LAY5brb8caNFlUuIRpraCVMDLYutKkol1aV079rQjLuSxKMJT2SpBQsYX9xbcTMmzwg==", 64 | "dependencies": { 65 | "System.Runtime": "4.3.1" 66 | } 67 | }, 68 | "xunit": { 69 | "type": "Direct", 70 | "requested": "[2.9.3, )", 71 | "resolved": "2.9.3", 72 | "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", 73 | "dependencies": { 74 | "xunit.analyzers": "1.18.0", 75 | "xunit.assert": "2.9.3", 76 | "xunit.core": "[2.9.3]" 77 | } 78 | }, 79 | "xunit.runner.visualstudio": { 80 | "type": "Direct", 81 | "requested": "[3.1.0, )", 82 | "resolved": "3.1.0", 83 | "contentHash": "K9O9TOzugqOo4LJ87uuq1VG8RAqGp20Ng85Wx932oT5LNBkIgeeGYubVW5UMnOOTanFNbGavmbuYrJr4INzSwg==" 84 | }, 85 | "AutoFixture": { 86 | "type": "Transitive", 87 | "resolved": "4.18.1", 88 | "contentHash": "BmWZDY4fkrYOyd5/CTBOeXbzsNwV8kI4kDi/Ty1Y5F+WDHBVKxzfWlBE4RSicvZ+EOi2XDaN5uwdrHsItLW6Kw==", 89 | "dependencies": { 90 | "Fare": "[2.1.1, 3.0.0)", 91 | "System.ComponentModel.Annotations": "4.3.0" 92 | } 93 | }, 94 | "Fare": { 95 | "type": "Transitive", 96 | "resolved": "2.1.1", 97 | "contentHash": "HaI8puqA66YU7/9cK4Sgbs1taUTP1Ssa4QT2PIzqJ7GvAbN1QgkjbRsjH+FSbMh1MJdvS0CIwQNLtFT+KF6KpA==", 98 | "dependencies": { 99 | "NETStandard.Library": "1.6.1" 100 | } 101 | }, 102 | "HidApi.Net": { 103 | "type": "Transitive", 104 | "resolved": "1.1.0", 105 | "contentHash": "PrIvZYB8zPWahCPNiapJ9r2e1GC8wPgjlX8W2H+39wSDuDzKG3t5cMzM0SrupT4Cws4x95C8PkAzRdEoglMo0Q==", 106 | "dependencies": { 107 | "WCharT.Net": "0.1.2" 108 | } 109 | }, 110 | "Microsoft.CodeCoverage": { 111 | "type": "Transitive", 112 | "resolved": "17.13.0", 113 | "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" 114 | }, 115 | "Microsoft.NETCore.Platforms": { 116 | "type": "Transitive", 117 | "resolved": "1.1.1", 118 | "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ==" 119 | }, 120 | "Microsoft.NETCore.Targets": { 121 | "type": "Transitive", 122 | "resolved": "1.1.3", 123 | "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" 124 | }, 125 | "Microsoft.TestPlatform.ObjectModel": { 126 | "type": "Transitive", 127 | "resolved": "17.13.0", 128 | "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", 129 | "dependencies": { 130 | "System.Reflection.Metadata": "1.6.0" 131 | } 132 | }, 133 | "Microsoft.TestPlatform.TestHost": { 134 | "type": "Transitive", 135 | "resolved": "17.13.0", 136 | "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", 137 | "dependencies": { 138 | "Microsoft.TestPlatform.ObjectModel": "17.13.0", 139 | "Newtonsoft.Json": "13.0.1" 140 | } 141 | }, 142 | "Microsoft.Win32.Primitives": { 143 | "type": "Transitive", 144 | "resolved": "4.3.0", 145 | "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", 146 | "dependencies": { 147 | "Microsoft.NETCore.Platforms": "1.1.0", 148 | "Microsoft.NETCore.Targets": "1.1.0", 149 | "System.Runtime": "4.3.0" 150 | } 151 | }, 152 | "NETStandard.Library": { 153 | "type": "Transitive", 154 | "resolved": "1.6.1", 155 | "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", 156 | "dependencies": { 157 | "Microsoft.NETCore.Platforms": "1.1.0", 158 | "Microsoft.Win32.Primitives": "4.3.0", 159 | "System.AppContext": "4.3.0", 160 | "System.Collections": "4.3.0", 161 | "System.Collections.Concurrent": "4.3.0", 162 | "System.Console": "4.3.0", 163 | "System.Diagnostics.Debug": "4.3.0", 164 | "System.Diagnostics.Tools": "4.3.0", 165 | "System.Diagnostics.Tracing": "4.3.0", 166 | "System.Globalization": "4.3.0", 167 | "System.Globalization.Calendars": "4.3.0", 168 | "System.IO": "4.3.0", 169 | "System.IO.Compression": "4.3.0", 170 | "System.IO.Compression.ZipFile": "4.3.0", 171 | "System.IO.FileSystem": "4.3.0", 172 | "System.IO.FileSystem.Primitives": "4.3.0", 173 | "System.Linq": "4.3.0", 174 | "System.Linq.Expressions": "4.3.0", 175 | "System.Net.Http": "4.3.0", 176 | "System.Net.Primitives": "4.3.0", 177 | "System.Net.Sockets": "4.3.0", 178 | "System.ObjectModel": "4.3.0", 179 | "System.Reflection": "4.3.0", 180 | "System.Reflection.Extensions": "4.3.0", 181 | "System.Reflection.Primitives": "4.3.0", 182 | "System.Resources.ResourceManager": "4.3.0", 183 | "System.Runtime": "4.3.0", 184 | "System.Runtime.Extensions": "4.3.0", 185 | "System.Runtime.Handles": "4.3.0", 186 | "System.Runtime.InteropServices": "4.3.0", 187 | "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", 188 | "System.Runtime.Numerics": "4.3.0", 189 | "System.Security.Cryptography.Algorithms": "4.3.0", 190 | "System.Security.Cryptography.Encoding": "4.3.0", 191 | "System.Security.Cryptography.Primitives": "4.3.0", 192 | "System.Security.Cryptography.X509Certificates": "4.3.0", 193 | "System.Text.Encoding": "4.3.0", 194 | "System.Text.Encoding.Extensions": "4.3.0", 195 | "System.Text.RegularExpressions": "4.3.0", 196 | "System.Threading": "4.3.0", 197 | "System.Threading.Tasks": "4.3.0", 198 | "System.Threading.Timer": "4.3.0", 199 | "System.Xml.ReaderWriter": "4.3.0", 200 | "System.Xml.XDocument": "4.3.0" 201 | } 202 | }, 203 | "Newtonsoft.Json": { 204 | "type": "Transitive", 205 | "resolved": "13.0.1", 206 | "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" 207 | }, 208 | "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { 209 | "type": "Transitive", 210 | "resolved": "4.3.2", 211 | "contentHash": "7VSGO0URRKoMEAq0Sc9cRz8mb6zbyx/BZDEWhgPdzzpmFhkam3fJ1DAGWFXBI4nGlma+uPKpfuMQP5LXRnOH5g==" 212 | }, 213 | "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { 214 | "type": "Transitive", 215 | "resolved": "4.3.2", 216 | "contentHash": "0oAaTAm6e2oVH+/Zttt0cuhGaePQYKII1dY8iaqP7CvOpVKgLybKRFvQjXR2LtxXOXTVPNv14j0ot8uV+HrUmw==" 217 | }, 218 | "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { 219 | "type": "Transitive", 220 | "resolved": "4.3.2", 221 | "contentHash": "G24ibsCNi5Kbz0oXWynBoRgtGvsw5ZSVEWjv13/KiCAM8C6wz9zzcCniMeQFIkJ2tasjo2kXlvlBZhplL51kGg==" 222 | }, 223 | "runtime.native.System": { 224 | "type": "Transitive", 225 | "resolved": "4.3.0", 226 | "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", 227 | "dependencies": { 228 | "Microsoft.NETCore.Platforms": "1.1.0", 229 | "Microsoft.NETCore.Targets": "1.1.0" 230 | } 231 | }, 232 | "runtime.native.System.IO.Compression": { 233 | "type": "Transitive", 234 | "resolved": "4.3.0", 235 | "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", 236 | "dependencies": { 237 | "Microsoft.NETCore.Platforms": "1.1.0", 238 | "Microsoft.NETCore.Targets": "1.1.0" 239 | } 240 | }, 241 | "runtime.native.System.Net.Http": { 242 | "type": "Transitive", 243 | "resolved": "4.3.0", 244 | "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", 245 | "dependencies": { 246 | "Microsoft.NETCore.Platforms": "1.1.0", 247 | "Microsoft.NETCore.Targets": "1.1.0" 248 | } 249 | }, 250 | "runtime.native.System.Security.Cryptography.Apple": { 251 | "type": "Transitive", 252 | "resolved": "4.3.0", 253 | "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", 254 | "dependencies": { 255 | "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" 256 | } 257 | }, 258 | "runtime.native.System.Security.Cryptography.OpenSsl": { 259 | "type": "Transitive", 260 | "resolved": "4.3.2", 261 | "contentHash": "QR1OwtwehHxSeQvZKXe+iSd+d3XZNkEcuWMFYa2i0aG1l+lR739HPicKMlTbJst3spmeekDVBUS7SeS26s4U/g==", 262 | "dependencies": { 263 | "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", 264 | "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", 265 | "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", 266 | "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", 267 | "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", 268 | "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", 269 | "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", 270 | "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", 271 | "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", 272 | "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" 273 | } 274 | }, 275 | "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { 276 | "type": "Transitive", 277 | "resolved": "4.3.2", 278 | "contentHash": "I+GNKGg2xCHueRd1m9PzeEW7WLbNNLznmTuEi8/vZX71HudUbx1UTwlGkiwMri7JLl8hGaIAWnA/GONhu+LOyQ==" 279 | }, 280 | "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { 281 | "type": "Transitive", 282 | "resolved": "4.3.2", 283 | "contentHash": "1Z3TAq1ytS1IBRtPXJvEUZdVsfWfeNEhBkbiOCGEl9wwAfsjP2lz3ZFDx5tq8p60/EqbS0HItG5piHuB71RjoA==" 284 | }, 285 | "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { 286 | "type": "Transitive", 287 | "resolved": "4.3.0", 288 | "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" 289 | }, 290 | "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { 291 | "type": "Transitive", 292 | "resolved": "4.3.2", 293 | "contentHash": "6mU/cVmmHtQiDXhnzUImxIcDL48GbTk+TsptXyJA+MIOG9LRjPoAQC/qBFB7X+UNyK86bmvGwC8t+M66wsYC8w==" 294 | }, 295 | "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { 296 | "type": "Transitive", 297 | "resolved": "4.3.2", 298 | "contentHash": "vjwG0GGcTW/PPg6KVud8F9GLWYuAV1rrw1BKAqY0oh4jcUqg15oYF1+qkGR2x2ZHM4DQnWKQ7cJgYbfncz/lYg==" 299 | }, 300 | "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { 301 | "type": "Transitive", 302 | "resolved": "4.3.2", 303 | "contentHash": "7KMFpTkHC/zoExs+PwP8jDCWcrK9H6L7soowT80CUx3e+nxP/AFnq0AQAW5W76z2WYbLAYCRyPfwYFG6zkvQRw==" 304 | }, 305 | "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { 306 | "type": "Transitive", 307 | "resolved": "4.3.2", 308 | "contentHash": "xrlmRCnKZJLHxyyLIqkZjNXqgxnKdZxfItrPkjI+6pkRo5lHX8YvSZlWrSI5AVwLMi4HbNWP7064hcAWeZKp5w==" 309 | }, 310 | "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { 311 | "type": "Transitive", 312 | "resolved": "4.3.2", 313 | "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" 314 | }, 315 | "System.AppContext": { 316 | "type": "Transitive", 317 | "resolved": "4.3.0", 318 | "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", 319 | "dependencies": { 320 | "System.Runtime": "4.3.0" 321 | } 322 | }, 323 | "System.Buffers": { 324 | "type": "Transitive", 325 | "resolved": "4.3.0", 326 | "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==", 327 | "dependencies": { 328 | "System.Diagnostics.Debug": "4.3.0", 329 | "System.Diagnostics.Tracing": "4.3.0", 330 | "System.Resources.ResourceManager": "4.3.0", 331 | "System.Runtime": "4.3.0", 332 | "System.Threading": "4.3.0" 333 | } 334 | }, 335 | "System.Collections": { 336 | "type": "Transitive", 337 | "resolved": "4.3.0", 338 | "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", 339 | "dependencies": { 340 | "Microsoft.NETCore.Platforms": "1.1.0", 341 | "Microsoft.NETCore.Targets": "1.1.0", 342 | "System.Runtime": "4.3.0" 343 | } 344 | }, 345 | "System.Collections.Concurrent": { 346 | "type": "Transitive", 347 | "resolved": "4.3.0", 348 | "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", 349 | "dependencies": { 350 | "System.Collections": "4.3.0", 351 | "System.Diagnostics.Debug": "4.3.0", 352 | "System.Diagnostics.Tracing": "4.3.0", 353 | "System.Globalization": "4.3.0", 354 | "System.Reflection": "4.3.0", 355 | "System.Resources.ResourceManager": "4.3.0", 356 | "System.Runtime": "4.3.0", 357 | "System.Runtime.Extensions": "4.3.0", 358 | "System.Threading": "4.3.0", 359 | "System.Threading.Tasks": "4.3.0" 360 | } 361 | }, 362 | "System.ComponentModel": { 363 | "type": "Transitive", 364 | "resolved": "4.3.0", 365 | "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", 366 | "dependencies": { 367 | "System.Runtime": "4.3.0" 368 | } 369 | }, 370 | "System.ComponentModel.Annotations": { 371 | "type": "Transitive", 372 | "resolved": "4.3.0", 373 | "contentHash": "SY2RLItHt43rd8J9D8M8e8NM4m+9WLN2uUd9G0n1I4hj/7w+v3pzK6ZBjexlG1/2xvLKQsqir3UGVSyBTXMLWA==", 374 | "dependencies": { 375 | "System.Collections": "4.3.0", 376 | "System.ComponentModel": "4.3.0", 377 | "System.Globalization": "4.3.0", 378 | "System.Linq": "4.3.0", 379 | "System.Reflection": "4.3.0", 380 | "System.Reflection.Extensions": "4.3.0", 381 | "System.Resources.ResourceManager": "4.3.0", 382 | "System.Runtime": "4.3.0", 383 | "System.Runtime.Extensions": "4.3.0", 384 | "System.Text.RegularExpressions": "4.3.0", 385 | "System.Threading": "4.3.0" 386 | } 387 | }, 388 | "System.Console": { 389 | "type": "Transitive", 390 | "resolved": "4.3.0", 391 | "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", 392 | "dependencies": { 393 | "Microsoft.NETCore.Platforms": "1.1.0", 394 | "Microsoft.NETCore.Targets": "1.1.0", 395 | "System.IO": "4.3.0", 396 | "System.Runtime": "4.3.0", 397 | "System.Text.Encoding": "4.3.0" 398 | } 399 | }, 400 | "System.Diagnostics.Debug": { 401 | "type": "Transitive", 402 | "resolved": "4.3.0", 403 | "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", 404 | "dependencies": { 405 | "Microsoft.NETCore.Platforms": "1.1.0", 406 | "Microsoft.NETCore.Targets": "1.1.0", 407 | "System.Runtime": "4.3.0" 408 | } 409 | }, 410 | "System.Diagnostics.DiagnosticSource": { 411 | "type": "Transitive", 412 | "resolved": "4.3.0", 413 | "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", 414 | "dependencies": { 415 | "System.Collections": "4.3.0", 416 | "System.Diagnostics.Tracing": "4.3.0", 417 | "System.Reflection": "4.3.0", 418 | "System.Runtime": "4.3.0", 419 | "System.Threading": "4.3.0" 420 | } 421 | }, 422 | "System.Diagnostics.Tools": { 423 | "type": "Transitive", 424 | "resolved": "4.3.0", 425 | "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", 426 | "dependencies": { 427 | "Microsoft.NETCore.Platforms": "1.1.0", 428 | "Microsoft.NETCore.Targets": "1.1.0", 429 | "System.Runtime": "4.3.0" 430 | } 431 | }, 432 | "System.Diagnostics.Tracing": { 433 | "type": "Transitive", 434 | "resolved": "4.3.0", 435 | "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", 436 | "dependencies": { 437 | "Microsoft.NETCore.Platforms": "1.1.0", 438 | "Microsoft.NETCore.Targets": "1.1.0", 439 | "System.Runtime": "4.3.0" 440 | } 441 | }, 442 | "System.Globalization": { 443 | "type": "Transitive", 444 | "resolved": "4.3.0", 445 | "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", 446 | "dependencies": { 447 | "Microsoft.NETCore.Platforms": "1.1.0", 448 | "Microsoft.NETCore.Targets": "1.1.0", 449 | "System.Runtime": "4.3.0" 450 | } 451 | }, 452 | "System.Globalization.Calendars": { 453 | "type": "Transitive", 454 | "resolved": "4.3.0", 455 | "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", 456 | "dependencies": { 457 | "Microsoft.NETCore.Platforms": "1.1.0", 458 | "Microsoft.NETCore.Targets": "1.1.0", 459 | "System.Globalization": "4.3.0", 460 | "System.Runtime": "4.3.0" 461 | } 462 | }, 463 | "System.Globalization.Extensions": { 464 | "type": "Transitive", 465 | "resolved": "4.3.0", 466 | "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", 467 | "dependencies": { 468 | "Microsoft.NETCore.Platforms": "1.1.0", 469 | "System.Globalization": "4.3.0", 470 | "System.Resources.ResourceManager": "4.3.0", 471 | "System.Runtime": "4.3.0", 472 | "System.Runtime.Extensions": "4.3.0", 473 | "System.Runtime.InteropServices": "4.3.0" 474 | } 475 | }, 476 | "System.IO": { 477 | "type": "Transitive", 478 | "resolved": "4.3.0", 479 | "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", 480 | "dependencies": { 481 | "Microsoft.NETCore.Platforms": "1.1.0", 482 | "Microsoft.NETCore.Targets": "1.1.0", 483 | "System.Runtime": "4.3.0", 484 | "System.Text.Encoding": "4.3.0", 485 | "System.Threading.Tasks": "4.3.0" 486 | } 487 | }, 488 | "System.IO.Compression": { 489 | "type": "Transitive", 490 | "resolved": "4.3.0", 491 | "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", 492 | "dependencies": { 493 | "Microsoft.NETCore.Platforms": "1.1.0", 494 | "System.Buffers": "4.3.0", 495 | "System.Collections": "4.3.0", 496 | "System.Diagnostics.Debug": "4.3.0", 497 | "System.IO": "4.3.0", 498 | "System.Resources.ResourceManager": "4.3.0", 499 | "System.Runtime": "4.3.0", 500 | "System.Runtime.Extensions": "4.3.0", 501 | "System.Runtime.Handles": "4.3.0", 502 | "System.Runtime.InteropServices": "4.3.0", 503 | "System.Text.Encoding": "4.3.0", 504 | "System.Threading": "4.3.0", 505 | "System.Threading.Tasks": "4.3.0", 506 | "runtime.native.System": "4.3.0", 507 | "runtime.native.System.IO.Compression": "4.3.0" 508 | } 509 | }, 510 | "System.IO.Compression.ZipFile": { 511 | "type": "Transitive", 512 | "resolved": "4.3.0", 513 | "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", 514 | "dependencies": { 515 | "System.Buffers": "4.3.0", 516 | "System.IO": "4.3.0", 517 | "System.IO.Compression": "4.3.0", 518 | "System.IO.FileSystem": "4.3.0", 519 | "System.IO.FileSystem.Primitives": "4.3.0", 520 | "System.Resources.ResourceManager": "4.3.0", 521 | "System.Runtime": "4.3.0", 522 | "System.Runtime.Extensions": "4.3.0", 523 | "System.Text.Encoding": "4.3.0" 524 | } 525 | }, 526 | "System.IO.FileSystem": { 527 | "type": "Transitive", 528 | "resolved": "4.3.0", 529 | "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", 530 | "dependencies": { 531 | "Microsoft.NETCore.Platforms": "1.1.0", 532 | "Microsoft.NETCore.Targets": "1.1.0", 533 | "System.IO": "4.3.0", 534 | "System.IO.FileSystem.Primitives": "4.3.0", 535 | "System.Runtime": "4.3.0", 536 | "System.Runtime.Handles": "4.3.0", 537 | "System.Text.Encoding": "4.3.0", 538 | "System.Threading.Tasks": "4.3.0" 539 | } 540 | }, 541 | "System.IO.FileSystem.Primitives": { 542 | "type": "Transitive", 543 | "resolved": "4.3.0", 544 | "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", 545 | "dependencies": { 546 | "System.Runtime": "4.3.0" 547 | } 548 | }, 549 | "System.Linq": { 550 | "type": "Transitive", 551 | "resolved": "4.3.0", 552 | "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", 553 | "dependencies": { 554 | "System.Collections": "4.3.0", 555 | "System.Diagnostics.Debug": "4.3.0", 556 | "System.Resources.ResourceManager": "4.3.0", 557 | "System.Runtime": "4.3.0", 558 | "System.Runtime.Extensions": "4.3.0" 559 | } 560 | }, 561 | "System.Linq.Expressions": { 562 | "type": "Transitive", 563 | "resolved": "4.3.0", 564 | "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", 565 | "dependencies": { 566 | "System.Collections": "4.3.0", 567 | "System.Diagnostics.Debug": "4.3.0", 568 | "System.Globalization": "4.3.0", 569 | "System.IO": "4.3.0", 570 | "System.Linq": "4.3.0", 571 | "System.ObjectModel": "4.3.0", 572 | "System.Reflection": "4.3.0", 573 | "System.Reflection.Emit": "4.3.0", 574 | "System.Reflection.Emit.ILGeneration": "4.3.0", 575 | "System.Reflection.Emit.Lightweight": "4.3.0", 576 | "System.Reflection.Extensions": "4.3.0", 577 | "System.Reflection.Primitives": "4.3.0", 578 | "System.Reflection.TypeExtensions": "4.3.0", 579 | "System.Resources.ResourceManager": "4.3.0", 580 | "System.Runtime": "4.3.0", 581 | "System.Runtime.Extensions": "4.3.0", 582 | "System.Threading": "4.3.0" 583 | } 584 | }, 585 | "System.Net.Primitives": { 586 | "type": "Transitive", 587 | "resolved": "4.3.0", 588 | "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", 589 | "dependencies": { 590 | "Microsoft.NETCore.Platforms": "1.1.0", 591 | "Microsoft.NETCore.Targets": "1.1.0", 592 | "System.Runtime": "4.3.0", 593 | "System.Runtime.Handles": "4.3.0" 594 | } 595 | }, 596 | "System.Net.Sockets": { 597 | "type": "Transitive", 598 | "resolved": "4.3.0", 599 | "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", 600 | "dependencies": { 601 | "Microsoft.NETCore.Platforms": "1.1.0", 602 | "Microsoft.NETCore.Targets": "1.1.0", 603 | "System.IO": "4.3.0", 604 | "System.Net.Primitives": "4.3.0", 605 | "System.Runtime": "4.3.0", 606 | "System.Threading.Tasks": "4.3.0" 607 | } 608 | }, 609 | "System.ObjectModel": { 610 | "type": "Transitive", 611 | "resolved": "4.3.0", 612 | "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", 613 | "dependencies": { 614 | "System.Collections": "4.3.0", 615 | "System.Diagnostics.Debug": "4.3.0", 616 | "System.Resources.ResourceManager": "4.3.0", 617 | "System.Runtime": "4.3.0", 618 | "System.Threading": "4.3.0" 619 | } 620 | }, 621 | "System.Reactive": { 622 | "type": "Transitive", 623 | "resolved": "6.0.1", 624 | "contentHash": "rHaWtKDwCi9qJ3ObKo8LHPMuuwv33YbmQi7TcUK1C264V3MFnOr5Im7QgCTdLniztP3GJyeiSg5x8NqYJFqRmg==" 625 | }, 626 | "System.Reflection": { 627 | "type": "Transitive", 628 | "resolved": "4.3.0", 629 | "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", 630 | "dependencies": { 631 | "Microsoft.NETCore.Platforms": "1.1.0", 632 | "Microsoft.NETCore.Targets": "1.1.0", 633 | "System.IO": "4.3.0", 634 | "System.Reflection.Primitives": "4.3.0", 635 | "System.Runtime": "4.3.0" 636 | } 637 | }, 638 | "System.Reflection.Emit": { 639 | "type": "Transitive", 640 | "resolved": "4.3.0", 641 | "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", 642 | "dependencies": { 643 | "System.IO": "4.3.0", 644 | "System.Reflection": "4.3.0", 645 | "System.Reflection.Emit.ILGeneration": "4.3.0", 646 | "System.Reflection.Primitives": "4.3.0", 647 | "System.Runtime": "4.3.0" 648 | } 649 | }, 650 | "System.Reflection.Emit.ILGeneration": { 651 | "type": "Transitive", 652 | "resolved": "4.3.0", 653 | "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", 654 | "dependencies": { 655 | "System.Reflection": "4.3.0", 656 | "System.Reflection.Primitives": "4.3.0", 657 | "System.Runtime": "4.3.0" 658 | } 659 | }, 660 | "System.Reflection.Emit.Lightweight": { 661 | "type": "Transitive", 662 | "resolved": "4.3.0", 663 | "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", 664 | "dependencies": { 665 | "System.Reflection": "4.3.0", 666 | "System.Reflection.Emit.ILGeneration": "4.3.0", 667 | "System.Reflection.Primitives": "4.3.0", 668 | "System.Runtime": "4.3.0" 669 | } 670 | }, 671 | "System.Reflection.Extensions": { 672 | "type": "Transitive", 673 | "resolved": "4.3.0", 674 | "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", 675 | "dependencies": { 676 | "Microsoft.NETCore.Platforms": "1.1.0", 677 | "Microsoft.NETCore.Targets": "1.1.0", 678 | "System.Reflection": "4.3.0", 679 | "System.Runtime": "4.3.0" 680 | } 681 | }, 682 | "System.Reflection.Metadata": { 683 | "type": "Transitive", 684 | "resolved": "1.6.0", 685 | "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" 686 | }, 687 | "System.Reflection.Primitives": { 688 | "type": "Transitive", 689 | "resolved": "4.3.0", 690 | "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", 691 | "dependencies": { 692 | "Microsoft.NETCore.Platforms": "1.1.0", 693 | "Microsoft.NETCore.Targets": "1.1.0", 694 | "System.Runtime": "4.3.0" 695 | } 696 | }, 697 | "System.Reflection.TypeExtensions": { 698 | "type": "Transitive", 699 | "resolved": "4.3.0", 700 | "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", 701 | "dependencies": { 702 | "System.Reflection": "4.3.0", 703 | "System.Runtime": "4.3.0" 704 | } 705 | }, 706 | "System.Resources.ResourceManager": { 707 | "type": "Transitive", 708 | "resolved": "4.3.0", 709 | "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", 710 | "dependencies": { 711 | "Microsoft.NETCore.Platforms": "1.1.0", 712 | "Microsoft.NETCore.Targets": "1.1.0", 713 | "System.Globalization": "4.3.0", 714 | "System.Reflection": "4.3.0", 715 | "System.Runtime": "4.3.0" 716 | } 717 | }, 718 | "System.Runtime": { 719 | "type": "Transitive", 720 | "resolved": "4.3.1", 721 | "contentHash": "abhfv1dTK6NXOmu4bgHIONxHyEqFjW8HwXPmpY9gmll+ix9UNo4XDcmzJn6oLooftxNssVHdJC1pGT9jkSynQg==", 722 | "dependencies": { 723 | "Microsoft.NETCore.Platforms": "1.1.1", 724 | "Microsoft.NETCore.Targets": "1.1.3" 725 | } 726 | }, 727 | "System.Runtime.Extensions": { 728 | "type": "Transitive", 729 | "resolved": "4.3.0", 730 | "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", 731 | "dependencies": { 732 | "Microsoft.NETCore.Platforms": "1.1.0", 733 | "Microsoft.NETCore.Targets": "1.1.0", 734 | "System.Runtime": "4.3.0" 735 | } 736 | }, 737 | "System.Runtime.Handles": { 738 | "type": "Transitive", 739 | "resolved": "4.3.0", 740 | "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", 741 | "dependencies": { 742 | "Microsoft.NETCore.Platforms": "1.1.0", 743 | "Microsoft.NETCore.Targets": "1.1.0", 744 | "System.Runtime": "4.3.0" 745 | } 746 | }, 747 | "System.Runtime.InteropServices": { 748 | "type": "Transitive", 749 | "resolved": "4.3.0", 750 | "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", 751 | "dependencies": { 752 | "Microsoft.NETCore.Platforms": "1.1.0", 753 | "Microsoft.NETCore.Targets": "1.1.0", 754 | "System.Reflection": "4.3.0", 755 | "System.Reflection.Primitives": "4.3.0", 756 | "System.Runtime": "4.3.0", 757 | "System.Runtime.Handles": "4.3.0" 758 | } 759 | }, 760 | "System.Runtime.InteropServices.RuntimeInformation": { 761 | "type": "Transitive", 762 | "resolved": "4.3.0", 763 | "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", 764 | "dependencies": { 765 | "System.Reflection": "4.3.0", 766 | "System.Reflection.Extensions": "4.3.0", 767 | "System.Resources.ResourceManager": "4.3.0", 768 | "System.Runtime": "4.3.0", 769 | "System.Runtime.InteropServices": "4.3.0", 770 | "System.Threading": "4.3.0", 771 | "runtime.native.System": "4.3.0" 772 | } 773 | }, 774 | "System.Runtime.Numerics": { 775 | "type": "Transitive", 776 | "resolved": "4.3.0", 777 | "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", 778 | "dependencies": { 779 | "System.Globalization": "4.3.0", 780 | "System.Resources.ResourceManager": "4.3.0", 781 | "System.Runtime": "4.3.0", 782 | "System.Runtime.Extensions": "4.3.0" 783 | } 784 | }, 785 | "System.Security.Cryptography.Algorithms": { 786 | "type": "Transitive", 787 | "resolved": "4.3.0", 788 | "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", 789 | "dependencies": { 790 | "Microsoft.NETCore.Platforms": "1.1.0", 791 | "System.Collections": "4.3.0", 792 | "System.IO": "4.3.0", 793 | "System.Resources.ResourceManager": "4.3.0", 794 | "System.Runtime": "4.3.0", 795 | "System.Runtime.Extensions": "4.3.0", 796 | "System.Runtime.Handles": "4.3.0", 797 | "System.Runtime.InteropServices": "4.3.0", 798 | "System.Runtime.Numerics": "4.3.0", 799 | "System.Security.Cryptography.Encoding": "4.3.0", 800 | "System.Security.Cryptography.Primitives": "4.3.0", 801 | "System.Text.Encoding": "4.3.0", 802 | "runtime.native.System.Security.Cryptography.Apple": "4.3.0", 803 | "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" 804 | } 805 | }, 806 | "System.Security.Cryptography.Cng": { 807 | "type": "Transitive", 808 | "resolved": "4.3.0", 809 | "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", 810 | "dependencies": { 811 | "Microsoft.NETCore.Platforms": "1.1.0", 812 | "System.IO": "4.3.0", 813 | "System.Resources.ResourceManager": "4.3.0", 814 | "System.Runtime": "4.3.0", 815 | "System.Runtime.Extensions": "4.3.0", 816 | "System.Runtime.Handles": "4.3.0", 817 | "System.Runtime.InteropServices": "4.3.0", 818 | "System.Security.Cryptography.Algorithms": "4.3.0", 819 | "System.Security.Cryptography.Encoding": "4.3.0", 820 | "System.Security.Cryptography.Primitives": "4.3.0", 821 | "System.Text.Encoding": "4.3.0" 822 | } 823 | }, 824 | "System.Security.Cryptography.Csp": { 825 | "type": "Transitive", 826 | "resolved": "4.3.0", 827 | "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", 828 | "dependencies": { 829 | "Microsoft.NETCore.Platforms": "1.1.0", 830 | "System.IO": "4.3.0", 831 | "System.Reflection": "4.3.0", 832 | "System.Resources.ResourceManager": "4.3.0", 833 | "System.Runtime": "4.3.0", 834 | "System.Runtime.Extensions": "4.3.0", 835 | "System.Runtime.Handles": "4.3.0", 836 | "System.Runtime.InteropServices": "4.3.0", 837 | "System.Security.Cryptography.Algorithms": "4.3.0", 838 | "System.Security.Cryptography.Encoding": "4.3.0", 839 | "System.Security.Cryptography.Primitives": "4.3.0", 840 | "System.Text.Encoding": "4.3.0", 841 | "System.Threading": "4.3.0" 842 | } 843 | }, 844 | "System.Security.Cryptography.Encoding": { 845 | "type": "Transitive", 846 | "resolved": "4.3.0", 847 | "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", 848 | "dependencies": { 849 | "Microsoft.NETCore.Platforms": "1.1.0", 850 | "System.Collections": "4.3.0", 851 | "System.Collections.Concurrent": "4.3.0", 852 | "System.Linq": "4.3.0", 853 | "System.Resources.ResourceManager": "4.3.0", 854 | "System.Runtime": "4.3.0", 855 | "System.Runtime.Extensions": "4.3.0", 856 | "System.Runtime.Handles": "4.3.0", 857 | "System.Runtime.InteropServices": "4.3.0", 858 | "System.Security.Cryptography.Primitives": "4.3.0", 859 | "System.Text.Encoding": "4.3.0", 860 | "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" 861 | } 862 | }, 863 | "System.Security.Cryptography.OpenSsl": { 864 | "type": "Transitive", 865 | "resolved": "4.3.0", 866 | "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", 867 | "dependencies": { 868 | "System.Collections": "4.3.0", 869 | "System.IO": "4.3.0", 870 | "System.Resources.ResourceManager": "4.3.0", 871 | "System.Runtime": "4.3.0", 872 | "System.Runtime.Extensions": "4.3.0", 873 | "System.Runtime.Handles": "4.3.0", 874 | "System.Runtime.InteropServices": "4.3.0", 875 | "System.Runtime.Numerics": "4.3.0", 876 | "System.Security.Cryptography.Algorithms": "4.3.0", 877 | "System.Security.Cryptography.Encoding": "4.3.0", 878 | "System.Security.Cryptography.Primitives": "4.3.0", 879 | "System.Text.Encoding": "4.3.0", 880 | "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" 881 | } 882 | }, 883 | "System.Security.Cryptography.Primitives": { 884 | "type": "Transitive", 885 | "resolved": "4.3.0", 886 | "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", 887 | "dependencies": { 888 | "System.Diagnostics.Debug": "4.3.0", 889 | "System.Globalization": "4.3.0", 890 | "System.IO": "4.3.0", 891 | "System.Resources.ResourceManager": "4.3.0", 892 | "System.Runtime": "4.3.0", 893 | "System.Threading": "4.3.0", 894 | "System.Threading.Tasks": "4.3.0" 895 | } 896 | }, 897 | "System.Security.Cryptography.X509Certificates": { 898 | "type": "Transitive", 899 | "resolved": "4.3.0", 900 | "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", 901 | "dependencies": { 902 | "Microsoft.NETCore.Platforms": "1.1.0", 903 | "System.Collections": "4.3.0", 904 | "System.Diagnostics.Debug": "4.3.0", 905 | "System.Globalization": "4.3.0", 906 | "System.Globalization.Calendars": "4.3.0", 907 | "System.IO": "4.3.0", 908 | "System.IO.FileSystem": "4.3.0", 909 | "System.IO.FileSystem.Primitives": "4.3.0", 910 | "System.Resources.ResourceManager": "4.3.0", 911 | "System.Runtime": "4.3.0", 912 | "System.Runtime.Extensions": "4.3.0", 913 | "System.Runtime.Handles": "4.3.0", 914 | "System.Runtime.InteropServices": "4.3.0", 915 | "System.Runtime.Numerics": "4.3.0", 916 | "System.Security.Cryptography.Algorithms": "4.3.0", 917 | "System.Security.Cryptography.Cng": "4.3.0", 918 | "System.Security.Cryptography.Csp": "4.3.0", 919 | "System.Security.Cryptography.Encoding": "4.3.0", 920 | "System.Security.Cryptography.OpenSsl": "4.3.0", 921 | "System.Security.Cryptography.Primitives": "4.3.0", 922 | "System.Text.Encoding": "4.3.0", 923 | "System.Threading": "4.3.0", 924 | "runtime.native.System": "4.3.0", 925 | "runtime.native.System.Net.Http": "4.3.0", 926 | "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" 927 | } 928 | }, 929 | "System.Text.Encoding": { 930 | "type": "Transitive", 931 | "resolved": "4.3.0", 932 | "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", 933 | "dependencies": { 934 | "Microsoft.NETCore.Platforms": "1.1.0", 935 | "Microsoft.NETCore.Targets": "1.1.0", 936 | "System.Runtime": "4.3.0" 937 | } 938 | }, 939 | "System.Text.Encoding.Extensions": { 940 | "type": "Transitive", 941 | "resolved": "4.3.0", 942 | "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", 943 | "dependencies": { 944 | "Microsoft.NETCore.Platforms": "1.1.0", 945 | "Microsoft.NETCore.Targets": "1.1.0", 946 | "System.Runtime": "4.3.0", 947 | "System.Text.Encoding": "4.3.0" 948 | } 949 | }, 950 | "System.Threading": { 951 | "type": "Transitive", 952 | "resolved": "4.3.0", 953 | "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", 954 | "dependencies": { 955 | "System.Runtime": "4.3.0", 956 | "System.Threading.Tasks": "4.3.0" 957 | } 958 | }, 959 | "System.Threading.Tasks": { 960 | "type": "Transitive", 961 | "resolved": "4.3.0", 962 | "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", 963 | "dependencies": { 964 | "Microsoft.NETCore.Platforms": "1.1.0", 965 | "Microsoft.NETCore.Targets": "1.1.0", 966 | "System.Runtime": "4.3.0" 967 | } 968 | }, 969 | "System.Threading.Tasks.Extensions": { 970 | "type": "Transitive", 971 | "resolved": "4.3.0", 972 | "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", 973 | "dependencies": { 974 | "System.Collections": "4.3.0", 975 | "System.Runtime": "4.3.0", 976 | "System.Threading.Tasks": "4.3.0" 977 | } 978 | }, 979 | "System.Threading.Timer": { 980 | "type": "Transitive", 981 | "resolved": "4.3.0", 982 | "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", 983 | "dependencies": { 984 | "Microsoft.NETCore.Platforms": "1.1.0", 985 | "Microsoft.NETCore.Targets": "1.1.0", 986 | "System.Runtime": "4.3.0" 987 | } 988 | }, 989 | "System.Xml.ReaderWriter": { 990 | "type": "Transitive", 991 | "resolved": "4.3.0", 992 | "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", 993 | "dependencies": { 994 | "System.Collections": "4.3.0", 995 | "System.Diagnostics.Debug": "4.3.0", 996 | "System.Globalization": "4.3.0", 997 | "System.IO": "4.3.0", 998 | "System.IO.FileSystem": "4.3.0", 999 | "System.IO.FileSystem.Primitives": "4.3.0", 1000 | "System.Resources.ResourceManager": "4.3.0", 1001 | "System.Runtime": "4.3.0", 1002 | "System.Runtime.Extensions": "4.3.0", 1003 | "System.Runtime.InteropServices": "4.3.0", 1004 | "System.Text.Encoding": "4.3.0", 1005 | "System.Text.Encoding.Extensions": "4.3.0", 1006 | "System.Text.RegularExpressions": "4.3.0", 1007 | "System.Threading.Tasks": "4.3.0", 1008 | "System.Threading.Tasks.Extensions": "4.3.0" 1009 | } 1010 | }, 1011 | "System.Xml.XDocument": { 1012 | "type": "Transitive", 1013 | "resolved": "4.3.0", 1014 | "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", 1015 | "dependencies": { 1016 | "System.Collections": "4.3.0", 1017 | "System.Diagnostics.Debug": "4.3.0", 1018 | "System.Diagnostics.Tools": "4.3.0", 1019 | "System.Globalization": "4.3.0", 1020 | "System.IO": "4.3.0", 1021 | "System.Reflection": "4.3.0", 1022 | "System.Resources.ResourceManager": "4.3.0", 1023 | "System.Runtime": "4.3.0", 1024 | "System.Runtime.Extensions": "4.3.0", 1025 | "System.Text.Encoding": "4.3.0", 1026 | "System.Threading": "4.3.0", 1027 | "System.Xml.ReaderWriter": "4.3.0" 1028 | } 1029 | }, 1030 | "WCharT.Net": { 1031 | "type": "Transitive", 1032 | "resolved": "0.1.2", 1033 | "contentHash": "WAGMmSxbejfwy2Po750WzyCgE5+o8B//2oUbLgfgOiWRU3U3uwd1f3OG//oyjfln4NLEuLVUp80vqiv+R5JD+w==" 1034 | }, 1035 | "xunit.abstractions": { 1036 | "type": "Transitive", 1037 | "resolved": "2.0.3", 1038 | "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" 1039 | }, 1040 | "xunit.analyzers": { 1041 | "type": "Transitive", 1042 | "resolved": "1.18.0", 1043 | "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" 1044 | }, 1045 | "xunit.assert": { 1046 | "type": "Transitive", 1047 | "resolved": "2.9.3", 1048 | "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" 1049 | }, 1050 | "xunit.core": { 1051 | "type": "Transitive", 1052 | "resolved": "2.9.3", 1053 | "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", 1054 | "dependencies": { 1055 | "xunit.extensibility.core": "[2.9.3]", 1056 | "xunit.extensibility.execution": "[2.9.3]" 1057 | } 1058 | }, 1059 | "xunit.extensibility.core": { 1060 | "type": "Transitive", 1061 | "resolved": "2.9.3", 1062 | "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", 1063 | "dependencies": { 1064 | "xunit.abstractions": "2.0.3" 1065 | } 1066 | }, 1067 | "xunit.extensibility.execution": { 1068 | "type": "Transitive", 1069 | "resolved": "2.9.3", 1070 | "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", 1071 | "dependencies": { 1072 | "xunit.extensibility.core": "[2.9.3]" 1073 | } 1074 | }, 1075 | "sleddog.blink1": { 1076 | "type": "Project", 1077 | "dependencies": { 1078 | "HidApi.Net": "[1.1.0, )", 1079 | "System.Reactive": "[6.0.1, )" 1080 | } 1081 | } 1082 | } 1083 | } 1084 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32929.385 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sleddog.Blink1", "Sleddog.Blink1\Sleddog.Blink1.csproj", "{87076250-788F-4E71-8910-DD6125A85761}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sleddog.Blink1.ExplicitTests", "Sleddog.Blink1.ExplicitTests\Sleddog.Blink1.ExplicitTests.csproj", "{F0B688A4-D91E-4B63-A722-9C8E0D34E6F8}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sleddog.Blink1.Tests", "Sleddog.Blink1.Tests\Sleddog.Blink1.Tests.csproj", "{0740C658-3078-4E0A-8268-DACCE88303A3}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {87076250-788F-4E71-8910-DD6125A85761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {87076250-788F-4E71-8910-DD6125A85761}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {87076250-788F-4E71-8910-DD6125A85761}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {87076250-788F-4E71-8910-DD6125A85761}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {F0B688A4-D91E-4B63-A722-9C8E0D34E6F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {F0B688A4-D91E-4B63-A722-9C8E0D34E6F8}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {F0B688A4-D91E-4B63-A722-9C8E0D34E6F8}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {F0B688A4-D91E-4B63-A722-9C8E0D34E6F8}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {0740C658-3078-4E0A-8268-DACCE88303A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {0740C658-3078-4E0A-8268-DACCE88303A3}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {0740C658-3078-4E0A-8268-DACCE88303A3}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {0740C658-3078-4E0A-8268-DACCE88303A3}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {256D930B-C52F-4F32-8FC1-430F68E701F0} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | LED 3 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 4 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Blink1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Reactive.Linq; 4 | using Sleddog.Blink1.Colors; 5 | using Sleddog.Blink1.Commands; 6 | using Sleddog.Blink1.Internal; 7 | 8 | namespace Sleddog.Blink1 9 | { 10 | public class Blink1 : IBlink1, IDisposable 11 | { 12 | internal readonly Blink1CommandBus CommandBus; 13 | protected readonly ushort NumberOfPresets; 14 | 15 | internal Blink1(Blink1CommandBus commandBus) : this(commandBus, 12) 16 | { 17 | } 18 | 19 | internal Blink1(Blink1CommandBus commandBus, ushort numberOfPresets) 20 | { 21 | EnableGamma = true; 22 | 23 | CommandBus = commandBus; 24 | NumberOfPresets = numberOfPresets; 25 | } 26 | 27 | public bool EnableGamma { get; set; } 28 | 29 | public Version Version => CommandBus.SendQuery(new VersionQuery()); 30 | 31 | public string SerialNumber => CommandBus.ReadSerial(); 32 | 33 | public bool Blink(Color inputColor, TimeSpan interval, ushort times) 34 | { 35 | var timeOnInMilliseconds = Math.Min(interval.TotalMilliseconds / 4, 250); 36 | 37 | var onTime = TimeSpan.FromMilliseconds(timeOnInMilliseconds); 38 | 39 | var color = ProcessColor(inputColor); 40 | 41 | var x = Observable.Timer(TimeSpan.Zero, interval).TakeWhile(count => count < times).Select(_ => color); 42 | var y = Observable.Timer(onTime, interval).TakeWhile(count => count < times).Select(_ => Color.Black); 43 | 44 | x.Merge(y).Subscribe(c => CommandBus.SendCommand(new SetColorCommand(c))); 45 | 46 | return true; 47 | } 48 | 49 | public bool Set(Color inputColor) 50 | { 51 | var color = ProcessColor(inputColor); 52 | 53 | return CommandBus.SendCommand(new SetColorCommand(color)); 54 | } 55 | 56 | public bool Fade(Color inputColor, TimeSpan fadeDuration) 57 | { 58 | var color = ProcessColor(inputColor); 59 | 60 | return CommandBus.SendCommand(new FadeToColorCommand(color, fadeDuration)); 61 | } 62 | 63 | public bool Show(Color inputColor, TimeSpan visibleTime) 64 | { 65 | var timer = ObservableExt.TimerMaxTick(1, TimeSpan.Zero, visibleTime); 66 | 67 | var color = ProcessColor(inputColor); 68 | 69 | var colors = new[] {color, Color.Black}.ToObservable(); 70 | 71 | colors.Zip(timer, (c, t) => new {Color = c, Count = t}) 72 | .Subscribe(item => CommandBus.SendCommand(new SetColorCommand(item.Color))); 73 | 74 | return true; 75 | } 76 | 77 | public bool Save(Blink1Preset preset, ushort position) 78 | { 79 | if (position < NumberOfPresets) 80 | { 81 | if (EnableGamma) 82 | { 83 | var color = ProcessColor(preset.Color); 84 | 85 | var correctedPreset = new Blink1Preset(color, preset.Duration); 86 | 87 | return CommandBus.SendCommand(new SetPresetCommand(correctedPreset, position)); 88 | } 89 | 90 | return CommandBus.SendCommand(new SetPresetCommand(preset, position)); 91 | } 92 | 93 | var message = $"Unable to save a preset outside the upper count ({NumberOfPresets}) of preset slots"; 94 | 95 | throw new ArgumentOutOfRangeException(nameof(position), message); 96 | } 97 | 98 | public Blink1Preset ReadPreset(ushort position) 99 | { 100 | if (position < NumberOfPresets) 101 | return CommandBus.SendQuery(new ReadPresetQuery(position)); 102 | 103 | var message = $"Unable to read a preset from position {position} since there is only {NumberOfPresets} preset slots"; 104 | 105 | throw new ArgumentOutOfRangeException(nameof(position), message); 106 | } 107 | 108 | public bool Play(ushort startPosition) 109 | { 110 | if (startPosition < NumberOfPresets) 111 | return CommandBus.SendCommand(new PlayPresetCommand(startPosition)); 112 | 113 | var message = $"Unable to play from position {startPosition} since there is only {NumberOfPresets} preset slots"; 114 | 115 | throw new ArgumentOutOfRangeException(nameof(startPosition), message); 116 | } 117 | 118 | public bool Pause() 119 | { 120 | return CommandBus.SendCommand(new StopPresetCommand()); 121 | } 122 | 123 | public bool EnableInactivityMode(TimeSpan waitDuration) 124 | { 125 | return CommandBus.SendCommand(new EnableInactivityModeCommand(waitDuration)); 126 | } 127 | 128 | public bool DisableInactivityMode() 129 | { 130 | return CommandBus.SendCommand(new DisableInactivityModeCommand()); 131 | } 132 | 133 | public void TurnOff() 134 | { 135 | Set(Color.Black); 136 | } 137 | 138 | public void Dispose() 139 | { 140 | CommandBus?.Dispose(); 141 | } 142 | 143 | private Color ProcessColor(Color inputColor) 144 | { 145 | if (EnableGamma) 146 | { 147 | var gammaCorrector = new GammaCorrector(); 148 | 149 | return gammaCorrector.Encode(inputColor); 150 | } 151 | 152 | return inputColor; 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Blink1Connector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using HidApi; 6 | using Sleddog.Blink1.Colors; 7 | using Sleddog.Blink1.Internal; 8 | 9 | namespace Sleddog.Blink1 10 | { 11 | public static class Blink1Connector 12 | { 13 | private const int VendorId = 0x27B8; 14 | private const int ProductId = 0x01ED; 15 | 16 | private static readonly Dictionary deviceTypeMap = new Dictionary 17 | { 18 | {0x31, DeviceType.Blink1}, 19 | {0x32, DeviceType.Blink1Mk2} 20 | }; 21 | 22 | public static IBlink1 Connect(string serial) 23 | { 24 | var serialToFind = serial.StartsWith("0x") ? serial : $"0x{serial}"; 25 | 26 | var deviceInfos = ListBlink1Devices(); 27 | 28 | if(deviceInfos.Any()) 29 | { 30 | foreach(var deviceInfo in deviceInfos) 31 | { 32 | var deviceData = IdentityDevice(deviceInfo); 33 | 34 | if(deviceData.Item1.Equals(serialToFind, StringComparison.InvariantCultureIgnoreCase)) 35 | { 36 | if(deviceData.Item2 == DeviceType.Blink1) 37 | { 38 | return new Blink1(new Blink1CommandBus(deviceInfo)); 39 | } 40 | 41 | return new Blink1Mk2(new Blink1CommandBus(deviceInfo)); 42 | } 43 | } 44 | } 45 | 46 | return null; 47 | } 48 | 49 | public static IEnumerable Scan() 50 | { 51 | var devices = ListBlink1Devices(); 52 | 53 | if(devices.Any()) 54 | { 55 | var deviceList = IdentifyDevices(devices).ToArray(); 56 | 57 | foreach(var device in deviceList) 58 | { 59 | if(device.Item1 == DeviceType.Blink1) 60 | { 61 | yield return new Blink1(new Blink1CommandBus(device.Item2)); 62 | } 63 | else 64 | { 65 | yield return new Blink1Mk2(new Blink1CommandBus(device.Item2)); 66 | } 67 | } 68 | } 69 | } 70 | 71 | public static IEnumerable Identify(TimeSpan identifyTime) 72 | { 73 | var colorGenerator = new ColorGenerator(); 74 | 75 | var blinks = Scan().ToList(); 76 | 77 | var colors = colorGenerator.GenerateColors(blinks.Count); 78 | 79 | var blink1Identifiers = (from b in blinks 80 | from c in colors 81 | select new Blink1Identifier(b, c)).ToList(); 82 | 83 | Parallel.ForEach(blink1Identifiers, bi => 84 | { 85 | var blink1 = bi.Blink1; 86 | 87 | blink1.Show(bi.Color, identifyTime); 88 | }); 89 | 90 | return blink1Identifiers; 91 | } 92 | 93 | private static Tuple IdentityDevice(DeviceInfo deviceInfo) 94 | { 95 | var chars = (from o in deviceInfo.SerialNumber where o != 0 select o).ToArray(); 96 | 97 | var deviceType = DetermineDeviceType((byte) deviceInfo.SerialNumber[0]); 98 | 99 | var serialNumber = $"0x{string.Join(string.Empty, chars)}"; 100 | 101 | return Tuple.Create(serialNumber, deviceType); 102 | } 103 | 104 | private static DeviceInfo[] ListBlink1Devices() 105 | { 106 | var devices = Hid.Enumerate(VendorId, ProductId); 107 | 108 | return devices as DeviceInfo[] ?? devices.ToArray(); 109 | } 110 | 111 | private static IEnumerable> IdentifyDevices(IEnumerable deviceInfos) 112 | { 113 | foreach(var deviceInfo in deviceInfos) 114 | { 115 | var significantByte = (byte) deviceInfo.SerialNumber[0]; 116 | 117 | var deviceType = DetermineDeviceType(significantByte); 118 | 119 | yield return Tuple.Create(deviceType, deviceInfo); 120 | } 121 | } 122 | 123 | private static DeviceType DetermineDeviceType(byte b) 124 | { 125 | if(deviceTypeMap.TryGetValue(b, out var deviceType)) 126 | { 127 | return deviceType; 128 | } 129 | 130 | /* 131 | * Default to mk2 in the case the specific device haven't been found 132 | * This ensures future versions should work as they'll be 1 model backwards compatible 133 | */ 134 | return DeviceType.Blink1Mk2; 135 | } 136 | 137 | private enum DeviceType 138 | { 139 | Blink1, 140 | Blink1Mk2 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Blink1Identifier.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace Sleddog.Blink1 4 | { 5 | public class Blink1Identifier 6 | { 7 | public IBlink1 Blink1 { get; } 8 | public Color Color { get; } 9 | 10 | public Blink1Identifier(IBlink1 blink1, Color color) 11 | { 12 | Blink1 = blink1; 13 | Color = color; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Blink1Mk2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using Sleddog.Blink1.Commands; 4 | using Sleddog.Blink1.Internal; 5 | 6 | namespace Sleddog.Blink1 7 | { 8 | public class Blink1Mk2 : Blink1, IBlink1Mk2 9 | { 10 | internal Blink1Mk2(Blink1CommandBus commandBus) 11 | : base(commandBus, 32) 12 | { 13 | } 14 | 15 | public new bool EnableGamma 16 | { 17 | get => base.EnableGamma; 18 | set { } 19 | } 20 | 21 | public bool Fade(Color color, TimeSpan fadeDuration, LEDPosition ledPosition) 22 | { 23 | var command = new FadeToColorCommand(color, fadeDuration, ledPosition); 24 | 25 | return CommandBus.SendCommand(command); 26 | } 27 | 28 | public bool Play(ushort startPosition, ushort endPosition, ushort count) 29 | { 30 | var command = new PlayPresetCommand(startPosition, endPosition, count); 31 | 32 | return CommandBus.SendCommand(command); 33 | } 34 | 35 | public bool SavePresets() 36 | { 37 | var command = new SavePresetsCommand(); 38 | 39 | return CommandBus.SendCommand(command); 40 | } 41 | 42 | public bool EnabledInactivityMode(TimeSpan waitDuration, bool maintainState, ushort startPosition, 43 | ushort endPosition) 44 | { 45 | var command = new EnableInactivityModeCommand(waitDuration, maintainState, startPosition, endPosition); 46 | 47 | return CommandBus.SendCommand(command); 48 | } 49 | 50 | public PlaybackStatus ReadPlaybackStatus() 51 | { 52 | var query = new ReadPlaybackStateQuery(); 53 | 54 | return CommandBus.SendQuery(query); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Blink1Preset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using Sleddog.Blink1.Internal; 4 | 5 | namespace Sleddog.Blink1 6 | { 7 | public class Blink1Preset 8 | { 9 | public Blink1Preset(Color color, TimeSpan duration) 10 | { 11 | Color = color; 12 | PresetDuration = duration; 13 | } 14 | 15 | public Color Color { get; } 16 | 17 | public TimeSpan Duration => PresetDuration; 18 | 19 | internal Blink1Duration PresetDuration { get; } 20 | 21 | protected bool Equals(Blink1Preset other) 22 | { 23 | var equal = Color.R.Equals(other.Color.R) && 24 | Color.G.Equals(other.Color.G) && 25 | Color.B.Equals(other.Color.B) && 26 | PresetDuration.Equals(other.PresetDuration); 27 | 28 | return equal; 29 | } 30 | 31 | public override bool Equals(object obj) 32 | { 33 | if (ReferenceEquals(null, obj)) 34 | { 35 | return false; 36 | } 37 | if (ReferenceEquals(this, obj)) 38 | { 39 | return true; 40 | } 41 | if (obj.GetType() != GetType()) 42 | { 43 | return false; 44 | } 45 | return Equals((Blink1Preset) obj); 46 | } 47 | 48 | public override int GetHashCode() 49 | { 50 | unchecked 51 | { 52 | return (Color.GetHashCode()*397) ^ (PresetDuration != null ? PresetDuration.GetHashCode() : 0); 53 | } 54 | } 55 | 56 | public static bool operator ==(Blink1Preset left, Blink1Preset right) 57 | { 58 | return Equals(left, right); 59 | } 60 | 61 | public static bool operator !=(Blink1Preset left, Blink1Preset right) 62 | { 63 | return !Equals(left, right); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Colors/ColorGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | 5 | namespace Sleddog.Blink1.Colors 6 | { 7 | internal class ColorGenerator 8 | { 9 | private static readonly Random Random = new Random(); 10 | 11 | public List GenerateColors(int colorCount) 12 | { 13 | var colors = new List(); 14 | 15 | var cellRange = 1f/colorCount; 16 | var cellOffset = Random.NextDouble()*cellRange; 17 | 18 | for (var i = 0; i < colorCount; i++) 19 | { 20 | var newHue = cellRange*i + cellOffset; 21 | 22 | if (newHue > 1) 23 | { 24 | newHue -= 1; 25 | } 26 | 27 | newHue *= 360; 28 | 29 | var hue = Convert.ToUInt16(newHue); 30 | 31 | var hslColor = new Hsl(hue, 0.3f, 0.4f); 32 | 33 | colors.Add(hslColor); 34 | } 35 | 36 | return colors; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Colors/GammaCorrector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace Sleddog.Blink1.Colors 5 | { 6 | internal class GammaCorrector 7 | { 8 | private static readonly double GammaValue = 2.2; 9 | 10 | public Color Encode(Color color) 11 | { 12 | var r = color.R; 13 | var g = color.G; 14 | var b = color.B; 15 | 16 | return Color.FromArgb(Encode(r), Encode(g), Encode(b)); 17 | } 18 | 19 | private int Encode(int value) 20 | { 21 | var correctedValue = Math.Pow((value/(double) 255), GammaValue)*255; 22 | 23 | return (int) Math.Round(correctedValue); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Colors/HSL.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace Sleddog.Blink1.Colors 5 | { 6 | internal class Hsl 7 | { 8 | public ushort Hue { get; } 9 | public float Saturation { get; } 10 | public float Luminance { get; } 11 | 12 | public Hsl(ushort hue, float saturation, float luminance) 13 | { 14 | if (hue > 360) 15 | { 16 | throw new ArgumentOutOfRangeException(nameof(hue)); 17 | } 18 | 19 | if (saturation < 0 || saturation > 1) 20 | { 21 | throw new ArgumentOutOfRangeException(nameof(saturation)); 22 | } 23 | 24 | if (luminance < 0 || luminance > 1) 25 | { 26 | throw new ArgumentOutOfRangeException(nameof(luminance)); 27 | } 28 | 29 | Hue = hue; 30 | Saturation = saturation; 31 | Luminance = luminance; 32 | } 33 | 34 | public static implicit operator Color(Hsl hsl) 35 | { 36 | var hue = hsl.Hue; 37 | var saturation = hsl.Saturation; 38 | var luminance = hsl.Luminance; 39 | 40 | if (luminance.Equals(0)) 41 | { 42 | return Color.FromArgb(0, 0, 0); 43 | } 44 | 45 | if (luminance.Equals(1)) 46 | { 47 | return Color.FromArgb(255, 255, 255); 48 | } 49 | 50 | var chroma = (1 - Math.Abs(2*luminance - 1))*saturation; 51 | var hSection = hue/60f; 52 | var x = chroma*(1 - Math.Abs(hSection%2 - 1)); 53 | 54 | var rgbValues = Tuple.Create(0f, 0f, 0f); 55 | 56 | if (hSection >= 0 && hSection < 1) 57 | { 58 | rgbValues = Tuple.Create(chroma, x, 0f); 59 | } 60 | else if (hSection >= 1 && hSection < 2) 61 | { 62 | rgbValues = Tuple.Create(x, chroma, 0f); 63 | } 64 | else if (hSection >= 2 && hSection < 3) 65 | { 66 | rgbValues = Tuple.Create(0f, chroma, x); 67 | } 68 | else if (hSection >= 3 && hSection < 4) 69 | { 70 | rgbValues = Tuple.Create(0f, x, chroma); 71 | } 72 | else if (hSection >= 4 && hSection < 5) 73 | { 74 | rgbValues = Tuple.Create(x, 0f, chroma); 75 | } 76 | else if (hSection >= 5 && hSection < 6) 77 | { 78 | rgbValues = Tuple.Create(chroma, 0f, x); 79 | } 80 | 81 | var m = luminance - 0.5f*chroma; 82 | 83 | var modRgbValues = Tuple.Create(rgbValues.Item1 + m, rgbValues.Item2 + m, rgbValues.Item3 + m); 84 | 85 | var r = (int) Math.Floor(modRgbValues.Item1*255); 86 | var g = (int) Math.Floor(modRgbValues.Item2*255); 87 | var b = (int) Math.Floor(modRgbValues.Item3*255); 88 | 89 | return Color.FromArgb(r, g, b); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/DisableInactivityModeCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sleddog.Blink1.Internal; 3 | using Sleddog.Blink1.Internal.Interfaces; 4 | 5 | namespace Sleddog.Blink1.Commands 6 | { 7 | internal class DisableInactivityModeCommand : IBlink1Command 8 | { 9 | public byte[] ToHidCommand() 10 | { 11 | return new[] 12 | { 13 | (byte) Blink1Commands.InactivityMode, 14 | Convert.ToByte(false) 15 | }; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/EnableInactivityModeCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sleddog.Blink1.Internal; 3 | using Sleddog.Blink1.Internal.Interfaces; 4 | 5 | namespace Sleddog.Blink1.Commands 6 | { 7 | internal class EnableInactivityModeCommand : IBlink1Command 8 | { 9 | private readonly Blink1Duration waitDuration; 10 | private readonly bool maintainState; 11 | private readonly ushort startPosition; 12 | private readonly ushort endPosition; 13 | 14 | public EnableInactivityModeCommand(Blink1Duration waitDuration) 15 | { 16 | this.waitDuration = waitDuration; 17 | } 18 | 19 | public EnableInactivityModeCommand(Blink1Duration waitDuration, bool maintainState, ushort startPosition, 20 | ushort endPosition) 21 | { 22 | this.waitDuration = waitDuration; 23 | this.maintainState = maintainState; 24 | this.startPosition = startPosition; 25 | this.endPosition = endPosition; 26 | } 27 | 28 | public byte[] ToHidCommand() 29 | { 30 | return new[] 31 | { 32 | (byte) Blink1Commands.InactivityMode, 33 | Convert.ToByte(true), 34 | waitDuration.High, 35 | waitDuration.Low, 36 | Convert.ToByte(maintainState), 37 | Convert.ToByte(startPosition), 38 | Convert.ToByte(endPosition) 39 | }; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/FadeToColorCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using Sleddog.Blink1.Internal; 4 | using Sleddog.Blink1.Internal.Interfaces; 5 | 6 | namespace Sleddog.Blink1.Commands 7 | { 8 | internal class FadeToColorCommand : IBlink1Command 9 | { 10 | private readonly Color color; 11 | private readonly Blink1Duration duration; 12 | private readonly LEDPosition ledPosition; 13 | 14 | public FadeToColorCommand(Color color, Blink1Duration duration) : this(color, duration, LEDPosition.Both) 15 | { 16 | } 17 | 18 | public FadeToColorCommand(Color color, Blink1Duration duration, LEDPosition ledPosition) 19 | { 20 | this.color = color; 21 | this.duration = duration; 22 | this.ledPosition = ledPosition; 23 | } 24 | 25 | public byte[] ToHidCommand() 26 | { 27 | return new[] 28 | { 29 | (byte) Blink1Commands.FadeToColor, 30 | color.R, 31 | color.G, 32 | color.B, 33 | duration.High, 34 | duration.Low, 35 | Convert.ToByte(ledPosition) 36 | }; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/PlayPresetCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sleddog.Blink1.Internal; 3 | using Sleddog.Blink1.Internal.Interfaces; 4 | 5 | namespace Sleddog.Blink1.Commands 6 | { 7 | internal class PlayPresetCommand : IBlink1Command 8 | { 9 | private readonly byte startPosition; 10 | private readonly byte endPosition; 11 | private readonly byte count; 12 | 13 | public PlayPresetCommand(ushort startPosition) : this(startPosition, 0, 0) 14 | { 15 | } 16 | 17 | public PlayPresetCommand(ushort startPosition, ushort endPosition, ushort count) 18 | { 19 | this.startPosition = Convert.ToByte(startPosition); 20 | this.endPosition = Convert.ToByte(endPosition); 21 | this.count = Convert.ToByte(count); 22 | } 23 | 24 | public byte[] ToHidCommand() 25 | { 26 | return new[] 27 | { 28 | (byte) Blink1Commands.PresetControl, 29 | Convert.ToByte(true), 30 | startPosition, 31 | endPosition, 32 | count 33 | }; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/ReadPlaybackStateQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sleddog.Blink1.Internal; 3 | using Sleddog.Blink1.Internal.Interfaces; 4 | 5 | namespace Sleddog.Blink1.Commands 6 | { 7 | public class ReadPlaybackStateQuery : IBlink1Query 8 | { 9 | public PlaybackStatus ToResponseType(ReadOnlySpan responseData) 10 | { 11 | var isPlaying = Convert.ToBoolean(responseData[2]); 12 | 13 | var start = Convert.ToUInt16(responseData[3]); 14 | var end = Convert.ToUInt16(responseData[4]); 15 | 16 | var count = Convert.ToUInt16(responseData[5]); 17 | var position = Convert.ToUInt16(responseData[6]); 18 | 19 | return new PlaybackStatus(isPlaying, start, end, count, position); 20 | } 21 | 22 | public byte[] ToHidCommand() 23 | { 24 | return new[] 25 | { 26 | (byte) Blink1Commands.ReadPlaybackState 27 | }; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/ReadPresetQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using Sleddog.Blink1.Internal; 4 | using Sleddog.Blink1.Internal.Interfaces; 5 | 6 | namespace Sleddog.Blink1.Commands 7 | { 8 | internal class ReadPresetQuery : IBlink1Query 9 | { 10 | private readonly byte position; 11 | 12 | public ReadPresetQuery(ushort position) 13 | { 14 | this.position = Convert.ToByte(position); 15 | } 16 | 17 | public Blink1Preset ToResponseType(ReadOnlySpan responseData) 18 | { 19 | var color = Color.FromArgb(responseData[2], responseData[3], responseData[4]); 20 | var duration = new Blink1Duration(responseData[5], responseData[6]); 21 | 22 | return new Blink1Preset(color, duration); 23 | } 24 | 25 | public byte[] ToHidCommand() 26 | { 27 | return new[] 28 | { 29 | (byte) Blink1Commands.ReadPreset, 30 | Convert.ToByte(0), 31 | Convert.ToByte(0), 32 | Convert.ToByte(0), 33 | Convert.ToByte(0), 34 | Convert.ToByte(0), 35 | position 36 | }; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/SavePresetsCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sleddog.Blink1.Internal; 3 | using Sleddog.Blink1.Internal.Interfaces; 4 | 5 | namespace Sleddog.Blink1.Commands 6 | { 7 | internal class SavePresetsCommand : IBlink1Command 8 | { 9 | public byte[] ToHidCommand() 10 | { 11 | return new[] 12 | { 13 | (byte) Blink1Commands.SavePresetMk2, 14 | Convert.ToByte(0xBE), 15 | Convert.ToByte(0xEF), 16 | Convert.ToByte(0xCA), 17 | Convert.ToByte(0xFE), 18 | Convert.ToByte(0x00), 19 | Convert.ToByte(0x00) 20 | }; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/SetColorCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Sleddog.Blink1.Internal; 3 | using Sleddog.Blink1.Internal.Interfaces; 4 | 5 | namespace Sleddog.Blink1.Commands 6 | { 7 | internal class SetColorCommand : IBlink1Command 8 | { 9 | private readonly Color color; 10 | 11 | public SetColorCommand(Color color) 12 | { 13 | this.color = color; 14 | } 15 | 16 | public byte[] ToHidCommand() 17 | { 18 | return new[] 19 | { 20 | (byte) Blink1Commands.SetColor, 21 | color.R, 22 | color.G, 23 | color.B 24 | }; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/SetPresetCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sleddog.Blink1.Internal; 3 | using Sleddog.Blink1.Internal.Interfaces; 4 | 5 | namespace Sleddog.Blink1.Commands 6 | { 7 | internal class SetPresetCommand : IBlink1Command 8 | { 9 | private readonly Blink1Preset preset; 10 | private readonly byte position; 11 | 12 | public SetPresetCommand(Blink1Preset preset, ushort position) 13 | { 14 | this.preset = preset; 15 | this.position = Convert.ToByte(position); 16 | } 17 | 18 | public byte[] ToHidCommand() 19 | { 20 | var presetDuration = preset.PresetDuration; 21 | var presetColor = preset.Color; 22 | 23 | return new[] 24 | { 25 | (byte) Blink1Commands.SavePreset, 26 | presetColor.R, 27 | presetColor.G, 28 | presetColor.B, 29 | presetDuration.High, 30 | presetDuration.Low, 31 | position 32 | }; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/StopPresetCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sleddog.Blink1.Internal; 3 | using Sleddog.Blink1.Internal.Interfaces; 4 | 5 | namespace Sleddog.Blink1.Commands 6 | { 7 | internal class StopPresetCommand : IBlink1Command 8 | { 9 | public byte[] ToHidCommand() 10 | { 11 | return new[] 12 | { 13 | (byte) Blink1Commands.PresetControl, 14 | Convert.ToByte(false) 15 | }; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Commands/VersionQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sleddog.Blink1.Internal; 3 | using Sleddog.Blink1.Internal.Interfaces; 4 | 5 | namespace Sleddog.Blink1.Commands 6 | { 7 | internal class VersionQuery : IBlink1Query 8 | { 9 | public Version ToResponseType(ReadOnlySpan responseData) 10 | { 11 | var major = responseData[3] - '0'; 12 | var minor = responseData[4] - '0'; 13 | 14 | return new Version(major, minor); 15 | } 16 | 17 | public byte[] ToHidCommand() 18 | { 19 | return new[] 20 | { 21 | (byte) Blink1Commands.GetVersion 22 | }; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/IBlink1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace Sleddog.Blink1 5 | { 6 | public interface IBlink1 7 | { 8 | Version Version { get; } 9 | string SerialNumber { get; } 10 | bool EnableGamma { get; set; } 11 | bool Blink(Color color, TimeSpan interval, ushort times); 12 | bool Set(Color color); 13 | bool Fade(Color color, TimeSpan fadeDuration); 14 | bool Show(Color color, TimeSpan visibleTime); 15 | bool Save(Blink1Preset preset, ushort position); 16 | Blink1Preset ReadPreset(ushort position); 17 | bool Play(ushort startPosition); 18 | bool Pause(); 19 | bool EnableInactivityMode(TimeSpan waitDuration); 20 | bool DisableInactivityMode(); 21 | void TurnOff(); 22 | void Dispose(); 23 | } 24 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/IBlink1Mk2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace Sleddog.Blink1 5 | { 6 | public interface IBlink1Mk2 : IBlink1 7 | { 8 | bool Fade(Color color, TimeSpan fadeDuration, LEDPosition ledPosition); 9 | bool Play(ushort startPosition, ushort endPosition, ushort count); 10 | PlaybackStatus ReadPlaybackStatus(); 11 | bool EnabledInactivityMode(TimeSpan waitDuration, bool maintainState, ushort startPosition, ushort endPosition); 12 | bool SavePresets(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Internal/Blink1CommandBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using HidApi; 4 | using Sleddog.Blink1.Internal.Interfaces; 5 | 6 | namespace Sleddog.Blink1.Internal 7 | { 8 | internal class Blink1CommandBus : IDisposable 9 | { 10 | private readonly DeviceInfo deviceInfo; 11 | private Device device; 12 | private readonly byte reportId = 0x01; 13 | private readonly int reportLength = 9; 14 | 15 | public bool IsConnected => device != null; 16 | 17 | public Blink1CommandBus(DeviceInfo deviceInfo) 18 | { 19 | this.deviceInfo = deviceInfo; 20 | } 21 | 22 | internal string ReadSerial() 23 | { 24 | var chars = (from o in deviceInfo.SerialNumber where o != 0 select o).ToArray(); 25 | 26 | return $"0x{string.Join(string.Empty, chars)}"; 27 | } 28 | 29 | internal bool SendCommand(IBlink1MultiCommand multiCommand) 30 | { 31 | if (!IsConnected) 32 | { 33 | Connect(); 34 | } 35 | 36 | var commandResults = (from hc in multiCommand.ToHidCommands() 37 | select WriteData(hc)) 38 | .ToList(); 39 | 40 | return commandResults.Any(success => !success); 41 | } 42 | 43 | internal bool SendCommand(IBlink1Command command) 44 | { 45 | if (!IsConnected) 46 | { 47 | Connect(); 48 | } 49 | 50 | var commandSend = WriteData(command.ToHidCommand()); 51 | 52 | return commandSend; 53 | } 54 | 55 | internal T SendQuery(IBlink1Query query) where T : class 56 | { 57 | if (!IsConnected) 58 | { 59 | Connect(); 60 | } 61 | 62 | var commandSend = WriteData(query.ToHidCommand()); 63 | 64 | if (commandSend) 65 | { 66 | var output = device.GetFeatureReport(reportId, reportLength); 67 | 68 | return query.ToResponseType(output); 69 | } 70 | 71 | return default; 72 | } 73 | 74 | private bool WriteData(byte[] data) 75 | { 76 | var writeData = new byte[reportLength]; 77 | 78 | writeData[0] = reportId; 79 | 80 | var length = Math.Min(data.Length, writeData.Length - 1); 81 | 82 | Array.Copy(data, 0, writeData, 1, length); 83 | 84 | device.SendFeatureReport(writeData); 85 | 86 | return true; 87 | } 88 | 89 | public void Connect() 90 | { 91 | device = deviceInfo.ConnectToDevice(); 92 | } 93 | 94 | public void Dispose() 95 | { 96 | device?.Dispose(); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Internal/Blink1Commands.cs: -------------------------------------------------------------------------------- 1 | namespace Sleddog.Blink1.Internal 2 | { 3 | internal enum Blink1Commands : byte 4 | { 5 | Test = (byte) '!', 6 | GetVersion = (byte) 'v', 7 | SetColor = (byte) 'n', 8 | FadeToColor = (byte) 'c', 9 | InactivityMode = (byte) 'D', 10 | PresetControl = (byte) 'p', 11 | ReadPreset = (byte) 'R', 12 | SavePreset = (byte) 'P', 13 | SavePresetMk2 = (byte) 'W', 14 | ReadPlaybackState = (byte) 'S', 15 | } 16 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Internal/Blink1Duration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | [assembly: InternalsVisibleTo("Sleddog.Blink1.Tests")] 5 | 6 | namespace Sleddog.Blink1.Internal 7 | { 8 | internal class Blink1Duration 9 | { 10 | public byte High { get; } 11 | public byte Low { get; } 12 | 13 | public Blink1Duration(TimeSpan duration) 14 | { 15 | var blinkTime = Convert.ToUInt32(duration.TotalMilliseconds/10); 16 | 17 | High = Convert.ToByte(blinkTime >> 8); 18 | Low = Convert.ToByte(blinkTime & 0xFF); 19 | } 20 | 21 | internal Blink1Duration(byte high, byte low) 22 | { 23 | High = high; 24 | Low = low; 25 | } 26 | 27 | protected bool Equals(Blink1Duration other) 28 | { 29 | return High == other.High && Low == other.Low; 30 | } 31 | 32 | public override bool Equals(object obj) 33 | { 34 | if (ReferenceEquals(null, obj)) 35 | { 36 | return false; 37 | } 38 | 39 | if (ReferenceEquals(this, obj)) 40 | { 41 | return true; 42 | } 43 | 44 | if (obj.GetType() != GetType()) 45 | { 46 | return false; 47 | } 48 | 49 | return Equals((Blink1Duration) obj); 50 | } 51 | 52 | public override int GetHashCode() 53 | { 54 | unchecked 55 | { 56 | return (High*397) ^ Low; 57 | } 58 | } 59 | 60 | public static bool operator ==(Blink1Duration left, Blink1Duration right) 61 | { 62 | return Equals(left, right); 63 | } 64 | 65 | public static bool operator !=(Blink1Duration left, Blink1Duration right) 66 | { 67 | return !Equals(left, right); 68 | } 69 | 70 | public static implicit operator TimeSpan(Blink1Duration duration) 71 | { 72 | var low = duration.Low; 73 | var high = duration.High; 74 | 75 | var blinkTime = Convert.ToUInt32((high << 8) | low); 76 | 77 | return TimeSpan.FromMilliseconds(blinkTime*10d); 78 | } 79 | 80 | public static implicit operator Blink1Duration(TimeSpan duration) 81 | { 82 | return new Blink1Duration(duration); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Internal/Interfaces/IBlink1Command.cs: -------------------------------------------------------------------------------- 1 | namespace Sleddog.Blink1.Internal.Interfaces 2 | { 3 | internal interface IBlink1Command 4 | { 5 | byte[] ToHidCommand(); 6 | } 7 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Internal/Interfaces/IBlink1MultiCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Sleddog.Blink1.Internal.Interfaces 4 | { 5 | internal interface IBlink1MultiCommand 6 | { 7 | IEnumerable ToHidCommands(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Internal/Interfaces/IBlink1Query.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sleddog.Blink1.Internal.Interfaces 4 | { 5 | internal interface IBlink1Query : IBlink1Command 6 | { 7 | } 8 | 9 | internal interface IBlink1Query : IBlink1Query where T : class 10 | { 11 | T ToResponseType(ReadOnlySpan responseData); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Internal/ObservableExt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | 4 | namespace Sleddog.Blink1.Internal 5 | { 6 | internal static class ObservableExt 7 | { 8 | public static IObservable TimerMaxTick(int numberOfTicks, TimeSpan dueTime, TimeSpan interval) 9 | { 10 | return Observable.Generate( 11 | 0L, 12 | i => i <= numberOfTicks, 13 | i => i + 1, 14 | i => i, 15 | i => i == 0 ? dueTime : interval); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/LEDPosition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sleddog.Blink1 4 | { 5 | [Flags] 6 | public enum LEDPosition 7 | { 8 | Both = 0, 9 | Top = 1, 10 | Bottom = 2 11 | } 12 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/PlaybackStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Sleddog.Blink1 2 | { 3 | public class PlaybackStatus 4 | { 5 | public bool IsPlaying { get; } 6 | public ushort StartPosition { get; } 7 | public ushort EndPosition { get; } 8 | public ushort Count { get; } 9 | public ushort Position { get; } 10 | 11 | internal PlaybackStatus(bool isPlaying, ushort startPosition, ushort endPosition, ushort count, ushort position) 12 | { 13 | IsPlaying = isPlaying; 14 | StartPosition = startPosition; 15 | EndPosition = endPosition; 16 | Count = count; 17 | Position = position; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Sleddog.Blink1/Sleddog.Blink1.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | Library 5 | true 6 | false 7 | https://github.com/SleddogSoftwareDevelopment/blink1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Sleddog.Blink1/hidapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleddogSoftwareDevelopment/blink1/036ee2e7cac69dcfbc910a9b277e5911f6627899/src/Sleddog.Blink1/hidapi.dll -------------------------------------------------------------------------------- /src/Sleddog.Blink1/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net9.0": { 5 | "HidApi.Net": { 6 | "type": "Direct", 7 | "requested": "[1.1.0, )", 8 | "resolved": "1.1.0", 9 | "contentHash": "PrIvZYB8zPWahCPNiapJ9r2e1GC8wPgjlX8W2H+39wSDuDzKG3t5cMzM0SrupT4Cws4x95C8PkAzRdEoglMo0Q==", 10 | "dependencies": { 11 | "WCharT.Net": "0.1.2" 12 | } 13 | }, 14 | "System.Reactive": { 15 | "type": "Direct", 16 | "requested": "[6.0.1, )", 17 | "resolved": "6.0.1", 18 | "contentHash": "rHaWtKDwCi9qJ3ObKo8LHPMuuwv33YbmQi7TcUK1C264V3MFnOr5Im7QgCTdLniztP3GJyeiSg5x8NqYJFqRmg==" 19 | }, 20 | "WCharT.Net": { 21 | "type": "Transitive", 22 | "resolved": "0.1.2", 23 | "contentHash": "WAGMmSxbejfwy2Po750WzyCgE5+o8B//2oUbLgfgOiWRU3U3uwd1f3OG//oyjfln4NLEuLVUp80vqiv+R5JD+w==" 24 | } 25 | } 26 | } 27 | } --------------------------------------------------------------------------------