diff --git a/src/TestApp/ColorWipe.cs b/src/TestApp/ColorWipe.cs index 0014ed6..fb0af39 100644 --- a/src/TestApp/ColorWipe.cs +++ b/src/TestApp/ColorWipe.cs @@ -22,7 +22,7 @@ public void Execute(AbortRequest request) //Set brightness to maximum (255) //Use Unknown as strip type. Then the type will be set in the native assembly. - settings.Channels[0] = new Channel(ledCount, 18, 255, false, StripType.WS2812_STRIP); + settings.Channel_1 = new Channel(ledCount, 18, 255, false, StripType.WS2812_STRIP); using (var controller = new WS281x(settings)) { @@ -37,7 +37,7 @@ public void Execute(AbortRequest request) private static void Wipe(WS281x controller, Color color) { - for (int i = 0; i <= controller.Settings.Channels[0].LEDs.Count - 1; i++) + for (int i = 0; i <= controller.Settings.Channel_1.LEDs.Count - 1; i++) { controller.SetLEDColor(0, i, color); controller.Render(); diff --git a/src/TestApp/RainbowColorAnimation.cs b/src/TestApp/RainbowColorAnimation.cs index 40148a6..359b81b 100644 --- a/src/TestApp/RainbowColorAnimation.cs +++ b/src/TestApp/RainbowColorAnimation.cs @@ -24,7 +24,7 @@ public void Execute(AbortRequest request) //Set brightness to maximum (255) //Use Unknown as strip type. Then the type will be set in the native assembly. - settings.Channels[0] = new Channel(ledCount, 18, 255, false, StripType.WS2812_STRIP); + settings.Channel_1 = new Channel(ledCount, 18, 255, false, StripType.WS2812_STRIP); using (var controller = new WS281x(settings)) { @@ -32,7 +32,7 @@ public void Execute(AbortRequest request) while (!request.IsAbortRequested) { - for (int i = 0; i <= controller.Settings.Channels[0].LEDCount - 1; i++) + for (int i = 0; i <= controller.Settings.Channel_1.LEDCount - 1; i++) { var colorIndex = (i + colorOffset) % colors.Count; controller.SetLEDColor(0, i, colors[colorIndex]); diff --git a/src/rpi-ws281x-dotnet.sln b/src/rpi-ws281x-dotnet.sln new file mode 100644 index 0000000..c9c0a84 --- /dev/null +++ b/src/rpi-ws281x-dotnet.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.757 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "rpi-ws281x-dotnet", "rpi_ws281x\rpi-ws281x-dotnet.csproj", "{5432FD49-28E2-40CC-AAE8-E340503981ED}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5432FD49-28E2-40CC-AAE8-E340503981ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5432FD49-28E2-40CC-AAE8-E340503981ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5432FD49-28E2-40CC-AAE8-E340503981ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5432FD49-28E2-40CC-AAE8-E340503981ED}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {05598D32-E9AC-47E9-8CC6-D5EED433D349} + EndGlobalSection +EndGlobal diff --git a/src/rpi_ws281x/Channel.cs b/src/rpi_ws281x/Channel.cs index ade7f4b..4e43109 100644 --- a/src/rpi_ws281x/Channel.cs +++ b/src/rpi_ws281x/Channel.cs @@ -8,30 +8,30 @@ namespace rpi_ws281x /// public class Channel { - public Channel() : this(0, 0) { } - - public Channel(int ledCount, int gpioPin) : this(ledCount, gpioPin, 255, false, rpi_ws281x.StripType.Unknown) { } - - public Channel(int ledCount, int gpioPin, byte brightness, bool invert, StripType stripType) - { - GPIOPin = gpioPin; - Invert = invert; - Brightness = brightness; - StripType = stripType; - - var ledList = new List(); - for(int i= 0; i<= ledCount-1; i++) - { - ledList.Add(new LED(i)); - } - - LEDs = new ReadOnlyCollection(ledList); - } - - /// - /// Returns the GPIO pin which is connected to the LED strip - /// - public int GPIOPin { get; private set; } + public Channel() : this(0, 0) { } + + public Channel(int ledCount, int gpioPin) : this(ledCount, gpioPin, 255, false, StripType.Unknown) { } + + public Channel(int ledCount, int gpioPin, byte brightness, bool invert, StripType stripType) + { + GPIOPin = gpioPin; + Invert = invert; + Brightness = brightness; + StripType = stripType; + + var ledList = new List(); + for (int i = 0; i <= ledCount - 1; i++) + { + ledList.Add(new LED(i)); + } + + LEDs = new ReadOnlyCollection(ledList); + } + + /// + /// Returns the GPIO pin which is connected to the LED strip + /// + public int GPIOPin { get; private set; } /// /// Returns a value which indicates if the signal needs to be inverted. @@ -41,7 +41,7 @@ public Channel(int ledCount, int gpioPin, byte brightness, bool invert, StripTy /// /// Gets or sets the brightness of the LEDs - /// 0 = darkes, 255 = brightest + /// 0 = darkest, 255 = brightest /// public byte Brightness { get; set; } @@ -51,12 +51,11 @@ public Channel(int ledCount, int gpioPin, byte brightness, bool invert, StripTy /// public StripType StripType { get; private set; } - /// - /// Returns all LEDs on this channel - /// - public ReadOnlyCollection LEDs { get; private set; } + /// + /// Returns all LEDs on this channel + /// + public ReadOnlyCollection LEDs { get; private set; } - public int LEDCount { get => LEDs.Count; } - - } + public int LEDCount { get => LEDs.Count; } + } } diff --git a/src/rpi_ws281x/Native/PInvoke.cs b/src/rpi_ws281x/Native/PInvoke.cs index f865429..9ae521d 100644 --- a/src/rpi_ws281x/Native/PInvoke.cs +++ b/src/rpi_ws281x/Native/PInvoke.cs @@ -1,24 +1,28 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -namespace Native +namespace rpi_ws281x.Native { internal class PInvoke { - public const int RPI_PWM_CHANNELS = 2; - + [SuppressMessage("IDE1006", "IDE1006", Justification = "Native methods have different naming conventions.")] [DllImport("ws2811.so")] - public static extern ws2811_return_t ws2811_init(ref ws2811_t ws2811); - + public static extern ws2811_return_t ws2811_init(IntPtr ws2811); + + [SuppressMessage("IDE1006", "IDE1006", Justification = "Native methods have different naming conventions.")] [DllImport("ws2811.so")] - public static extern ws2811_return_t ws2811_render(ref ws2811_t ws2811); + public static extern ws2811_return_t ws2811_render(IntPtr ws2811); + [SuppressMessage("IDE1006", "IDE1006", Justification = "Native methods have different naming conventions.")] [DllImport("ws2811.so")] - public static extern ws2811_return_t ws2811_wait(ref ws2811_t ws2811); - + public static extern ws2811_return_t ws2811_wait(IntPtr ws2811); + + [SuppressMessage("IDE1006", "IDE1006", Justification = "Native methods have different naming conventions.")] [DllImport("ws2811.so")] - public static extern void ws2811_fini(ref ws2811_t ws2811); + public static extern void ws2811_fini(IntPtr ws2811); + [SuppressMessage("IDE1006", "IDE1006", Justification = "Native methods have different naming conventions.")] [DllImport("ws2811.so")] public static extern IntPtr ws2811_get_return_t_str(int state); } diff --git a/src/rpi_ws281x/Native/ws2811_channel_t.cs b/src/rpi_ws281x/Native/ws2811_channel_t.cs index 35ba97d..5d3be16 100644 --- a/src/rpi_ws281x/Native/ws2811_channel_t.cs +++ b/src/rpi_ws281x/Native/ws2811_channel_t.cs @@ -1,13 +1,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -namespace Native +namespace rpi_ws281x.Native { [StructLayout(LayoutKind.Sequential)] + [SuppressMessage("IDE1006", "IDE1006", Justification = "Native methods have different naming conventions.")] internal struct ws2811_channel_t { public int gpionum; diff --git a/src/rpi_ws281x/Native/ws2811_return_t.cs b/src/rpi_ws281x/Native/ws2811_return_t.cs index fb65ac7..2f8d8ca 100644 --- a/src/rpi_ws281x/Native/ws2811_return_t.cs +++ b/src/rpi_ws281x/Native/ws2811_return_t.cs @@ -1,5 +1,8 @@ -namespace Native +using System.Diagnostics.CodeAnalysis; + +namespace rpi_ws281x.Native { + [SuppressMessage("IDE1006", "IDE1006", Justification = "Native methods have different naming conventions.")] internal enum ws2811_return_t { WS2811_SUCCESS = 0, @@ -18,4 +21,4 @@ internal enum ws2811_return_t WS2811_ERROR_SPI_SETUP = -13, WS2811_ERROR_SPI_TRANSFER = -14 } -} \ No newline at end of file +} diff --git a/src/rpi_ws281x/Native/ws2811_t.cs b/src/rpi_ws281x/Native/ws2811_t.cs index 7ba56bf..76d5926 100644 --- a/src/rpi_ws281x/Native/ws2811_t.cs +++ b/src/rpi_ws281x/Native/ws2811_t.cs @@ -1,17 +1,19 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -namespace Native +namespace rpi_ws281x.Native { + [SuppressMessage("IDE1006", "IDE1006", Justification = "Native methods have different naming conventions.")] [StructLayout(LayoutKind.Sequential)] - internal struct ws2811_t + internal class ws2811_t { public long render_wait_time; public IntPtr device; public IntPtr rpi_hw; public uint freq; public int dmanum; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = PInvoke.RPI_PWM_CHANNELS)] - public ws2811_channel_t[] channel; + public ws2811_channel_t channel_1; + public ws2811_channel_t channel_2; } } diff --git a/src/rpi_ws281x/Settings.cs b/src/rpi_ws281x/Settings.cs index e295741..d2051c2 100644 --- a/src/rpi_ws281x/Settings.cs +++ b/src/rpi_ws281x/Settings.cs @@ -1,6 +1,6 @@ -using Native; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.ObjectModel; +using rpi_ws281x.Native; namespace rpi_ws281x { @@ -9,42 +9,65 @@ namespace rpi_ws281x /// public class Settings { - - /// - /// Settings to initialize the WS281x controller - /// - /// Set frequency in Hz - /// Set DMA channel to use - public Settings(uint frequency, int dmaChannel) - { - Frequency = frequency; - DMAChannel = dmaChannel; - Channels = new Channel[PInvoke.RPI_PWM_CHANNELS]; - } - /// + /// + /// Settings to initialize the WS281x controller with one channel + /// + /// Set frequency in Hz + /// Set DMA channel to use + public Settings(Channel channel, uint frequency = 800000, int dmaChannel = 10) : this (channel, null, frequency, dmaChannel) { } + + /// + /// Settings to initialize the WS281x controller with up to two channels + /// + /// Set frequency in Hz + /// Set DMA channel to use + public Settings(Channel channel1, Channel channel2, uint frequency = 800000, int dmaChannel = 10) + { + Channel_1 = channel1; + if (channel2 == null) + ChannelCount = 1; + else { + Channel_2 = channel2; + ChannelCount = 2; + } + Frequency = frequency; + DMAChannel = dmaChannel; + } + + /// /// Returns default settings. /// Use a frequency of 800000 Hz and DMA channel 10 /// /// public static Settings CreateDefaultSettings() - { - return new Settings(800000, 10); - } + { + return new Settings(null, 800000, 10); + } - /// - /// Returns the used frequency in Hz - /// - public uint Frequency { get; private set; } + /// + /// Returns the used frequency in Hz + /// + public uint Frequency { get; private set; } /// /// Returns the DMA channel /// public int DMAChannel { get; private set; } + /// + /// Returns the number of channels being used + /// + public int ChannelCount { get; private set; } + /// - /// Returns the channels which holds the LEDs + /// Returns Channel 1 + /// + public Channel Channel_1 { get; set; } + + /// + /// Returns Channel 1 /// - public Channel[] Channels { get; private set; } - } + public Channel Channel_2 { get; set; } + } } diff --git a/src/rpi_ws281x/StripType.cs b/src/rpi_ws281x/StripType.cs index e895d9b..17f2213 100644 --- a/src/rpi_ws281x/StripType.cs +++ b/src/rpi_ws281x/StripType.cs @@ -8,9 +8,73 @@ namespace rpi_ws281x /// The type of the LED strip defines the ordering of the colors (e. g. RGB, GRB, ...). /// Maybe the RGBValue property of the LED class needs to be changed if there are other strip types. /// - public enum StripType - { + public enum StripType + { + /// + /// Unknown / unset. + /// Unknown = 0, - WS2812_STRIP = 0x00081000 - } + + /// + /// SK6812_STRIP_RGBW + /// + SK6812_STRIP_RGBW = 0x18100800, + + /// + /// SK6812_STRIP_RBGW + /// + SK6812_STRIP_RBGW = 0x18100008, + + /// + /// SK6812_STRIP_GRBW + /// + SK6812_STRIP_GRBW = 0x18081000, + + /// + /// SK6812_STRIP_GBRW + /// + SK6812_STRIP_GBRW = 0x18080010, + + /// + /// SK6812_STRIP_BRGW + /// + SK6812_STRIP_BRGW = 0x18001008, + + /// + /// SK6812_STRIP_BGRW + /// + SK6812_STRIP_BGRW = 0x18000810, + + /// + /// WS2811_STRIP_RGB + /// + WS2811_STRIP_RGB = 0x00100800, + + /// + /// WS2811_STRIP_RBG + /// + WS2811_STRIP_RBG = 0x00100008, + + /// + /// WS2811_STRIP_GRB + /// + WS2811_STRIP_GRB = 0x00081000, + + /// + /// WS2811_STRIP_GBR + /// + WS2811_STRIP_GBR = 0x00080010, + + /// + /// WS2811_STRIP_BRG + /// + WS2811_STRIP_BRG = 0x00001008, + + /// + /// WS2811_STRIP_BGR + /// + WS2811_STRIP_BGR = 0x00000810, + + WS2812_STRIP = 0x00081000 + } } diff --git a/src/rpi_ws281x/WS281x.cs b/src/rpi_ws281x/WS281x.cs index dc167e6..dd619fa 100644 --- a/src/rpi_ws281x/WS281x.cs +++ b/src/rpi_ws281x/WS281x.cs @@ -1,8 +1,8 @@ -using Native; -using System; +using System; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; +using rpi_ws281x.Native; namespace rpi_ws281x { @@ -22,33 +22,28 @@ public class WS281x : IDisposable public WS281x(Settings settings) { _ws2811 = new ws2811_t(); - //Pin the object in memory. Otherwies GC will probably move the object to another memory location. - //This would cause errors because the native library has a pointer on the memory location of the object. _ws2811Handle = GCHandle.Alloc(_ws2811, GCHandleType.Pinned); _ws2811.dmanum = settings.DMAChannel; _ws2811.freq = settings.Frequency; - _ws2811.channel = new ws2811_channel_t[PInvoke.RPI_PWM_CHANNELS]; + _ws2811.channel_1 = default(ws2811_channel_t); + _ws2811.channel_2 = default(ws2811_channel_t); + if (settings.Channel_1 != null) + InitChannel(ref _ws2811.channel_1, settings.Channel_1); + if (settings.Channel_2 != null) + InitChannel(ref _ws2811.channel_2, settings.Channel_2); - for(int i=0; i<= _ws2811.channel.Length -1; i++) - { - if(settings.Channels[i] != null) - { - InitChannel(i, settings.Channels[i]); - } - } + Settings = settings; - Settings = settings; - - var initResult = PInvoke.ws2811_init(ref _ws2811); + var initResult = PInvoke.ws2811_init(_ws2811Handle.AddrOfPinnedObject()); if (initResult != ws2811_return_t.WS2811_SUCCESS) { var returnMessage = GetMessageForStatusCode(initResult); - throw new Exception(String.Format("Error while initializing.{0}Error code: {1}{0}Message: {2}", Environment.NewLine, initResult.ToString(), returnMessage)); - } + throw new Exception($"Error while initializing.{Environment.NewLine}Error code: {initResult.ToString()}{Environment.NewLine}Message: {returnMessage}"); + } //Disposing is only allowed if the init was successfull. - //Otherwise the native cleanup function throws an error. + //Otherwise the native cleanUp function throws an error. _isDisposingAllowed = true; } @@ -57,20 +52,22 @@ public WS281x(Settings settings) /// public void Render() { - for(int i=0; i<= Settings.Channels.Length -1; i++) - { - if (Settings.Channels[i] != null) - { - var ledColor = Settings.Channels[i].LEDs.Select(x => x.RGBValue).ToArray(); - Marshal.Copy(ledColor, 0, _ws2811.channel[i].leds, ledColor.Count()); - } - } - - var result = PInvoke.ws2811_render(ref _ws2811); + if (Settings.Channel_1 != null) { + var ledColor = Settings.Channel_1.LEDs.Select(x => x.RGBValue).ToArray(); + Marshal.Copy(ledColor, 0, _ws2811.channel_1.leds, ledColor.Count()); + } + if (Settings.Channel_2 != null) + { + var ledColor = Settings.Channel_2.LEDs.Select(x => x.RGBValue).ToArray(); + Marshal.Copy(ledColor, 0, _ws2811.channel_2.leds, ledColor.Count()); + } + + + var result = PInvoke.ws2811_render(_ws2811Handle.AddrOfPinnedObject()); if (result != ws2811_return_t.WS2811_SUCCESS) { var returnMessage = GetMessageForStatusCode(result); - throw new Exception(String.Format("Error while rendering.{0}Error code: {1}{0}Message: {2}", Environment.NewLine, result.ToString(), returnMessage)); + throw new Exception($"Error while rendering.{Environment.NewLine}Error code: {result.ToString()}{Environment.NewLine}Message: {returnMessage}"); } } @@ -82,8 +79,11 @@ public void Render() /// New color public void SetLEDColor(int channelIndex, int ledID, Color color) { - Settings.Channels[channelIndex].LEDs[ledID].Color = color; - } + if (channelIndex == 0) + Settings.Channel_1.LEDs[ledID].Color = color; + else if (channelIndex == 1) + Settings.Channel_2.LEDs[ledID].Color = color; + } /// /// Returns the settings which are used to initialize the component @@ -91,22 +91,22 @@ public void SetLEDColor(int channelIndex, int ledID, Color color) public Settings Settings { get; private set; } /// - /// Initialize the channel propierties + /// Initialize the channel properties. /// - /// Index of the channel tu initialize - /// Settings for the channel - private void InitChannel(int channelIndex, Channel channelSettings) + /// Channel to initialize. + /// Settings for the channel. + private void InitChannel(ref ws2811_channel_t channel, Channel channelSettings) { - _ws2811.channel[channelIndex].count = channelSettings.LEDs.Count; - _ws2811.channel[channelIndex].gpionum = channelSettings.GPIOPin; - _ws2811.channel[channelIndex].brightness = channelSettings.Brightness; - _ws2811.channel[channelIndex].invert = Convert.ToInt32(channelSettings.Invert); + channel.count = channelSettings.LEDCount; + channel.gpionum = channelSettings.GPIOPin; + channel.brightness = channelSettings.Brightness; + channel.invert = Convert.ToInt32(channelSettings.Invert); - if(channelSettings.StripType != StripType.Unknown) + if (channelSettings.StripType != StripType.Unknown) { - //Strip type is set by the native assembly if not explicitly set. - //This type defines the ordering of the colors e. g. RGB or GRB, ... - _ws2811.channel[channelIndex].strip_type = (int)channelSettings.StripType; + //Strip type is set by the native assembly if not explicitly set. + //This type defines the ordering of the colors e. g. RGB or GRB, ... + channel.strip_type = (int)channelSettings.StripType; } } @@ -121,8 +121,8 @@ private string GetMessageForStatusCode(ws2811_return_t statusCode) return Marshal.PtrToStringAuto(strPointer); } - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls +#region IDisposable Support + private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { @@ -138,31 +138,30 @@ protected virtual void Dispose(bool disposing) if(_isDisposingAllowed) { - PInvoke.ws2811_fini(ref _ws2811); - _ws2811Handle.Free(); - + PInvoke.ws2811_fini(_ws2811Handle.AddrOfPinnedObject()); + if(_ws2811Handle.IsAllocated) + { + _ws2811Handle.Free(); + } + _isDisposingAllowed = false; } - + disposedValue = true; } } - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. ~WS281x() { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(false); } - // This code added to correctly implement the disposable pattern. public void Dispose() { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); + GC.SuppressFinalize(this); } - #endregion - } + #endregion + } } diff --git a/src/rpi_ws281x/rpi-ws281x-dotnet.csproj b/src/rpi_ws281x/rpi-ws281x-dotnet.csproj new file mode 100644 index 0000000..e36c2be --- /dev/null +++ b/src/rpi_ws281x/rpi-ws281x-dotnet.csproj @@ -0,0 +1,9 @@ + + + + netcoreapp3.0 + rpi_ws281x_dotnet + 7.1 + + +