Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the Pimoroni Blinkt device binding #2370

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions src/devices/Blinkt/Blinkt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Device.Gpio;
using System.Threading;
using System.Drawing;

namespace Iot.Device.Blinkt
{
/// <summary>
/// Driver for the Pimoroni Blinkt LED strip for Raspbery Pi
/// https://shop.pimoroni.com/products/blinkt
///
/// This is a strip of 8 pixels that can be indendently controlled over GPIO.
///
/// Setting the color is a 2-step process:
/// Set the color using Clear, SetPixel, or SetAll
/// Call Show to update the physical pixels
/// </summary>
public class Blinkt : IDisposable
{
private readonly GpioPin _datPin;
private readonly GpioPin _clkPin;

private readonly Color[] _pixels = new Color[NumberOfPixels];
private readonly bool _shouldDispose;
private GpioController _gpioController;

/// <summary>
/// The number of pixels in the Blinkt strip
/// </summary>
public const int NumberOfPixels = 8;

/// <summary>
/// The sleep time in milliseconds between each bit written to the Blinkt.
///
/// The default value of 0 should be enough for most cases and works on the RPi 4 and 5,
/// but you can increase this if you experience issues with the Blinkt on faster CPUs.
/// </summary>
public int SleepTime { get; set; } = 0;

/// <summary>
/// Initializes a new instance of the <see cref="Blinkt"/> class.
/// </summary>
/// <param name="datPin">The GPIO pin number for the data pin. This defaults to 23,
/// and you should only change this if you connect the blinkt using cables instead of sitting it on the GPIO pins using the provided socket.</param>
/// <param name="clkPin">The GPIO pin number for the clock pin. This defaults to 24,
/// and you should only change this if you connect the blinkt using cables instead of sitting it on the GPIO pins using the provided socket.</param>
/// <param name="gpioController">The GPIO controller to use with the Blinkt. If not provided, a new controller is created.</param>
/// <param name="shouldDispose">True (the default) if the GPIO controller shall be disposed when disposing this instance.</param>
public Blinkt(int datPin = 23, int clkPin = 24, GpioController? gpioController = null, bool shouldDispose = true)
{
for (int i = 0; i < NumberOfPixels; i++)
{
_pixels[i] = Color.Empty;
}

_shouldDispose = shouldDispose || gpioController is null;
_gpioController = gpioController ?? new();

_datPin = _gpioController.OpenPin(datPin, PinMode.Output);
_clkPin = _gpioController.OpenPin(clkPin, PinMode.Output);
}

/// <inheritdoc />
public void Dispose()
{
Clear();
Show();

// this condition only applies to GPIO devices
if (_shouldDispose)
{
_gpioController?.Dispose();
_gpioController = null!;
}
}

/// <summary>
/// Sets the brightness of all the pixels
/// </summary>
/// <param name="brightness">The brightess for the pixels</param>
public void SetBrightness(byte brightness)
{
for (var i = 0; i < NumberOfPixels; i++)
{
_pixels[i] = Color.FromArgb(brightness, _pixels[i]);
}
}

/// <summary>
/// Clears all the LEDs by turning them off.
///
/// After calling Clear, you must call Show to update the LEDs.
/// </summary>
public void Clear()
{
SetAll(Color.Empty);
}

/// <summary>
/// Shows the current state of the LEDs by applying the colors and brightness to the LEDs.
/// </summary>
public void Show()
{
Sof();

foreach (Color pixel in _pixels)
{
int r = pixel.R;
int g = pixel.G;
int b = pixel.B;
int brightness = (int)(31.0 * (pixel.A / 255.0)) & 0b11111;
WriteByte(0b11100000 | brightness);
WriteByte(b);
WriteByte(g);
WriteByte(r);
}

Eof();
}

/// <summary>
/// Sets the color of all the pixels.
/// This does not update the physical pixel, you must call Show to display the color.
/// </summary>
/// <param name="color">The color to set the pixel to</param>
public void SetAll(Color color)
{
for (var i = 0; i < NumberOfPixels; i++)
{
SetPixel(i, color);
}
}

/// <summary>
/// Gets the color that the specified pixel is set to.
/// This may not reflect the actual color of the LED if Show has not been called.
/// </summary>
/// <param name="pixel">The index of the pixel to get</param>
/// <returns>The color of the pixel </returns>
public Color GetPixel(int pixel)
{
if (pixel < 0 || pixel >= NumberOfPixels)
{
throw new ArgumentOutOfRangeException(nameof(pixel), $"Pixel must be between 0 and {NumberOfPixels - 1}");
}

return _pixels[pixel];
}

/// <summary>
/// Sets the color of the specified pixel to the specified color.
/// This does not update the physical pixel, you must call Show to display the color.
/// </summary>
/// <param name="pixel">The index of the pixel to update</param>
/// <param name="color">The color to set on the pixel</param>
/// <exception cref="ArgumentOutOfRangeException">The value of pixel must be between 0 and 7, otherwise this exception is thrown</exception>
public void SetPixel(int pixel, Color color)
{
if (pixel < 0 || pixel >= NumberOfPixels)
{
throw new ArgumentOutOfRangeException(nameof(pixel), $"Pixel must be between 0 and {NumberOfPixels - 1}");
}

_pixels[pixel] = color;
}

private void WriteByte(int b)
{
for (var i = 0; i < 8; i++)
{
_datPin.Write((b & 0b10000000) != 0);
_clkPin.Write(PinValue.High);
Thread.Sleep(SleepTime);
b <<= 1;
_clkPin.Write(PinValue.Low);
Thread.Sleep(SleepTime);
}
}

private void Eof()
{
_datPin.Write(PinValue.Low);
for (var i = 0; i < 36; i++)
{
_clkPin.Write(PinValue.High);
Thread.Sleep(SleepTime);
_clkPin.Write(PinValue.Low);
Thread.Sleep(SleepTime);
}
}

private void Sof()
{
_datPin.Write(PinValue.Low);
for (var i = 0; i < 32; i++)
{
_clkPin.Write(PinValue.High);
Thread.Sleep(SleepTime);
_clkPin.Write(PinValue.Low);
Thread.Sleep(SleepTime);
}
}
}
}
15 changes: 15 additions & 0 deletions src/devices/Blinkt/Blinkt.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(DefaultBindingTfms)</TargetFrameworks>
<!--Disabling default items so samples source won't get build by the main library-->
<EnableDefaultItems>false</EnableDefaultItems>
<LangVersion>9</LangVersion>
</PropertyGroup>

<ItemGroup>
<Compile Include="*.cs" />
<None Include="README.md" />
</ItemGroup>

</Project>
67 changes: 67 additions & 0 deletions src/devices/Blinkt/Blinkt.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blinkt", "Blinkt.csproj", "{B82C190A-642B-465B-BD3F-DB56FFF22253}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{6A4DE7B1-03F3-4EE0-BF73-A0BAEF88BA2B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blinkt.Samples", "samples\Blinkt.Samples.csproj", "{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "..\Common\Common.csproj", "{584651BB-91DA-4FFE-B32C-F13DD7BCE100}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|x64.ActiveCfg = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|x64.Build.0 = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|x86.ActiveCfg = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|x86.Build.0 = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|Any CPU.Build.0 = Release|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|x64.ActiveCfg = Release|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|x64.Build.0 = Release|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|x86.ActiveCfg = Release|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|x86.Build.0 = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|x64.ActiveCfg = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|x64.Build.0 = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|x86.ActiveCfg = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|x86.Build.0 = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|Any CPU.Build.0 = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|x64.ActiveCfg = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|x64.Build.0 = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|x86.ActiveCfg = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|x86.Build.0 = Release|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Debug|Any CPU.Build.0 = Debug|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Debug|x64.ActiveCfg = Debug|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Debug|x64.Build.0 = Debug|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Debug|x86.ActiveCfg = Debug|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Debug|x86.Build.0 = Debug|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Release|Any CPU.ActiveCfg = Release|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Release|Any CPU.Build.0 = Release|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Release|x64.ActiveCfg = Release|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Release|x64.Build.0 = Release|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Release|x86.ActiveCfg = Release|Any CPU
{584651BB-91DA-4FFE-B32C-F13DD7BCE100}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF} = {6A4DE7B1-03F3-4EE0-BF73-A0BAEF88BA2B}
EndGlobalSection
EndGlobal
39 changes: 39 additions & 0 deletions src/devices/Blinkt/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Blinkt - 8-LED indicator strip

## Summary

[Blinkt!](https://shop.pimoroni.com/products/blinkt) offers eight APA102 pixels in the smallest (and cheapest) form factor to plug straight onto your Raspberry Pi.

Each pixel on Blinkt! is individually controllable and dimmable allowing you to create gradients, pulsing effects, or just flash them on and off like crazy.

## Binding Notes

The Blinkt has 8 pixels that can be controlled independantly. This library is designed to mimic the functionality of the [Pimoroni Blinkt Python library](https://github.com/pimoroni/blinkt), except using System.Drawing.Color instead of separate R, G, B and brightness values. the Alpha channnel is used to set the brightness of the pixel.

Setting the pixel values does not update the display. You must call the `Show` method to update the display. This allows you to configure how you want all the pixels to look before updating the display.

## Usage

Here is an example how to use the Blinkt:

```csharp
using System.Drawing;
using Iot.Device.Blinkt;

// Create the Blinkt
var blinkt = new Blinkt();

// Set all the pixels to random colors
for (int i = 0; i < Blinkt.NUMBER_OF_PIXELS; i++)
{
var color = Color.FromArgb(new Random().Next(0, 255), new Random().Next(0, 255), new Random().Next(0, 255));
blinkt.SetPixel(i, color);
}

// Update the display to show the new colors
blinkt.Show();
```

## References

[Blinkt on Pimoroni](https://shop.pimoroni.com/products/blinkt)
2 changes: 2 additions & 0 deletions src/devices/Blinkt/category.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
display
led
49 changes: 49 additions & 0 deletions src/devices/Blinkt/samples/Blinkt.Sample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Drawing;
using System.Threading;
using Iot.Device.Blinkt;

// Light up pixels moving backwards and forwards in random colors

// Create a new Blinkt instance
Blinkt blinkt = new Blinkt();

// A helper method to set a single pixel to a given color, clearing all others.
// This also waits 100ms so the color can be seen
static void SetOnePixel(Blinkt blinkt, Color color, int i)
{
// Turn all pixels off first - this is not reflected in the hardware till we call Show
blinkt.Clear();

// Set the pixel at index i to the given color
blinkt.SetPixel(i, color);

// Update the hardware to reflect the changes
blinkt.Show();

// Wait for a bit before moving to the next pixel
Thread.Sleep(100);
}

// Loop forever
while (true)
{
// Generate a random color
Color color = Color.FromArgb(new Random().Next(0, 255), new Random().Next(0, 255), new Random().Next(0, 255));

// Loop through the pixels, lighting them in the given color
for (int i = 0; i < Blinkt.NumberOfPixels; i++)
{
SetOnePixel(blinkt, color, i);
}

// Loop through the pixels in reverse order, lighting them in the given color
// We skip the first pixel so there is a more pleasing bounce effect
for (int i = Blinkt.NumberOfPixels - 2; i >= 0; i--)
{
SetOnePixel(blinkt, color, i);
}
}
12 changes: 12 additions & 0 deletions src/devices/Blinkt/samples/Blinkt.Samples.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(DefaultSampleTfms)</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="../Blinkt.csproj" />
</ItemGroup>

</Project>