diff --git a/src/devices/Blinkt/Blinkt.cs b/src/devices/Blinkt/Blinkt.cs new file mode 100644 index 0000000000..3df9f2dc6e --- /dev/null +++ b/src/devices/Blinkt/Blinkt.cs @@ -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 +{ + /// + /// 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 + /// + 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; + + /// + /// The number of pixels in the Blinkt strip + /// + public const int NumberOfPixels = 8; + + /// + /// 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. + /// + public int SleepTime { get; set; } = 0; + + /// + /// Initializes a new instance of the class. + /// + /// 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. + /// 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. + /// The GPIO controller to use with the Blinkt. If not provided, a new controller is created. + /// True (the default) if the GPIO controller shall be disposed when disposing this instance. + 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); + } + + /// + public void Dispose() + { + Clear(); + Show(); + + // this condition only applies to GPIO devices + if (_shouldDispose) + { + _gpioController?.Dispose(); + _gpioController = null!; + } + } + + /// + /// Sets the brightness of all the pixels + /// + /// The brightess for the pixels + public void SetBrightness(byte brightness) + { + for (var i = 0; i < NumberOfPixels; i++) + { + _pixels[i] = Color.FromArgb(brightness, _pixels[i]); + } + } + + /// + /// Clears all the LEDs by turning them off. + /// + /// After calling Clear, you must call Show to update the LEDs. + /// + public void Clear() + { + SetAll(Color.Empty); + } + + /// + /// Shows the current state of the LEDs by applying the colors and brightness to the LEDs. + /// + 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(); + } + + /// + /// Sets the color of all the pixels. + /// This does not update the physical pixel, you must call Show to display the color. + /// + /// The color to set the pixel to + public void SetAll(Color color) + { + for (var i = 0; i < NumberOfPixels; i++) + { + SetPixel(i, color); + } + } + + /// + /// 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. + /// + /// The index of the pixel to get + /// The color of the pixel + 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]; + } + + /// + /// 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. + /// + /// The index of the pixel to update + /// The color to set on the pixel + /// The value of pixel must be between 0 and 7, otherwise this exception is thrown + 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); + } + } + } +} diff --git a/src/devices/Blinkt/Blinkt.csproj b/src/devices/Blinkt/Blinkt.csproj new file mode 100644 index 0000000000..2b43b907b7 --- /dev/null +++ b/src/devices/Blinkt/Blinkt.csproj @@ -0,0 +1,15 @@ + + + + $(DefaultBindingTfms) + + false + 9 + + + + + + + + diff --git a/src/devices/Blinkt/Blinkt.sln b/src/devices/Blinkt/Blinkt.sln new file mode 100644 index 0000000000..0fa92f4c56 --- /dev/null +++ b/src/devices/Blinkt/Blinkt.sln @@ -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 diff --git a/src/devices/Blinkt/README.md b/src/devices/Blinkt/README.md new file mode 100644 index 0000000000..23b4954004 --- /dev/null +++ b/src/devices/Blinkt/README.md @@ -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) diff --git a/src/devices/Blinkt/category.txt b/src/devices/Blinkt/category.txt new file mode 100644 index 0000000000..79a960891c --- /dev/null +++ b/src/devices/Blinkt/category.txt @@ -0,0 +1,2 @@ +display +led diff --git a/src/devices/Blinkt/samples/Blinkt.Sample.cs b/src/devices/Blinkt/samples/Blinkt.Sample.cs new file mode 100644 index 0000000000..54a4e224c9 --- /dev/null +++ b/src/devices/Blinkt/samples/Blinkt.Sample.cs @@ -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); + } +} diff --git a/src/devices/Blinkt/samples/Blinkt.Samples.csproj b/src/devices/Blinkt/samples/Blinkt.Samples.csproj new file mode 100644 index 0000000000..859e440ecc --- /dev/null +++ b/src/devices/Blinkt/samples/Blinkt.Samples.csproj @@ -0,0 +1,12 @@ + + + + Exe + $(DefaultSampleTfms) + + + + + + +