├── .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 | 
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