This commit is contained in:
5
depends/rpi-rgb-led-matrix/bindings/c#/.gitignore
vendored
Normal file
5
depends/rpi-rgb-led-matrix/bindings/c#/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
**/.vs
|
||||
**/bin
|
||||
**/obj
|
||||
**/*.sln
|
||||
**/*.csproj.user
|
||||
85
depends/rpi-rgb-led-matrix/bindings/c#/Bindings.cs
Normal file
85
depends/rpi-rgb-led-matrix/bindings/c#/Bindings.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
global using static RPiRgbLEDMatrix.Bindings;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RPiRgbLEDMatrix;
|
||||
|
||||
/*
|
||||
Some of the extern methods listed below are marked with [SuppressGCTransition].
|
||||
This disables some GC checks that may take a long time. But such methods should
|
||||
be fast and trivial, otherwise the managed code may become unstable (see docs).
|
||||
Keep this in mind when changing the C/C++ side.
|
||||
|
||||
https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.suppressgctransitionattribute
|
||||
*/
|
||||
internal static class Bindings
|
||||
{
|
||||
private const string Lib = "librgbmatrix.so.1";
|
||||
|
||||
[DllImport(Lib)]
|
||||
public static extern IntPtr led_matrix_create(int rows, int chained, int parallel);
|
||||
|
||||
[DllImport(Lib, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr led_matrix_create_from_options_const_argv(
|
||||
ref InternalRGBLedMatrixOptions options,
|
||||
int argc,
|
||||
string[] argv);
|
||||
|
||||
[DllImport(Lib)]
|
||||
public static extern void led_matrix_delete(IntPtr matrix);
|
||||
|
||||
[DllImport(Lib)]
|
||||
public static extern IntPtr led_matrix_create_offscreen_canvas(IntPtr matrix);
|
||||
|
||||
[DllImport(Lib)]
|
||||
public static extern IntPtr led_matrix_swap_on_vsync(IntPtr matrix, IntPtr canvas);
|
||||
|
||||
[DllImport(Lib)]
|
||||
public static extern IntPtr led_matrix_get_canvas(IntPtr matrix);
|
||||
|
||||
[DllImport(Lib)]
|
||||
[SuppressGCTransition]
|
||||
public static extern byte led_matrix_get_brightness(IntPtr matrix);
|
||||
|
||||
[DllImport(Lib)]
|
||||
[SuppressGCTransition]
|
||||
public static extern void led_matrix_set_brightness(IntPtr matrix, byte brightness);
|
||||
|
||||
[DllImport(Lib, CharSet = CharSet.Ansi)]
|
||||
public static extern IntPtr load_font(string bdf_font_file);
|
||||
|
||||
[DllImport(Lib, CharSet = CharSet.Ansi)]
|
||||
public static extern int draw_text(IntPtr canvas, IntPtr font, int x, int y, byte r, byte g, byte b,
|
||||
string utf8_text, int extra_spacing);
|
||||
|
||||
[DllImport(Lib, CharSet = CharSet.Ansi)]
|
||||
public static extern int vertical_draw_text(IntPtr canvas, IntPtr font, int x, int y, byte r, byte g, byte b,
|
||||
string utf8_text, int kerning_offset);
|
||||
|
||||
[DllImport(Lib, CharSet = CharSet.Ansi)]
|
||||
public static extern void delete_font(IntPtr font);
|
||||
|
||||
[DllImport(Lib)]
|
||||
[SuppressGCTransition]
|
||||
public static extern void led_canvas_get_size(IntPtr canvas, out int width, out int height);
|
||||
|
||||
[DllImport(Lib)]
|
||||
[SuppressGCTransition]
|
||||
public static extern void led_canvas_set_pixel(IntPtr canvas, int x, int y, byte r, byte g, byte b);
|
||||
|
||||
[DllImport(Lib)]
|
||||
public static extern void led_canvas_set_pixels(IntPtr canvas, int x, int y, int width, int height,
|
||||
ref Color colors);
|
||||
|
||||
[DllImport(Lib)]
|
||||
public static extern void led_canvas_clear(IntPtr canvas);
|
||||
|
||||
[DllImport(Lib)]
|
||||
public static extern void led_canvas_fill(IntPtr canvas, byte r, byte g, byte b);
|
||||
|
||||
[DllImport(Lib)]
|
||||
public static extern void draw_circle(IntPtr canvas, int xx, int y, int radius, byte r, byte g, byte b);
|
||||
|
||||
[DllImport(Lib)]
|
||||
public static extern void draw_line(IntPtr canvas, int x0, int y0, int x1, int y1, byte r, byte g, byte b);
|
||||
}
|
||||
38
depends/rpi-rgb-led-matrix/bindings/c#/Color.cs
Normal file
38
depends/rpi-rgb-led-matrix/bindings/c#/Color.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace RPiRgbLEDMatrix;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an RGB (red, green, blue) color
|
||||
/// </summary>
|
||||
public struct Color
|
||||
{
|
||||
/// <summary>
|
||||
/// The red component value of this instance.
|
||||
/// </summary>
|
||||
public byte R;
|
||||
|
||||
/// <summary>
|
||||
/// The green component value of this instance.
|
||||
/// </summary>
|
||||
public byte G;
|
||||
|
||||
/// <summary>
|
||||
/// The blue component value of this instance.
|
||||
/// </summary>
|
||||
public byte B;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new color from the specified color values (red, green, and blue).
|
||||
/// </summary>
|
||||
/// <param name="r">The red component value.</param>
|
||||
/// <param name="g">The green component value.</param>
|
||||
/// <param name="b">The blue component value.</param>
|
||||
public Color(int r, int g, int b) : this((byte)r, (byte)g, (byte)b) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new color from the specified color values (red, green, and blue).
|
||||
/// </summary>
|
||||
/// <param name="r">The red component value.</param>
|
||||
/// <param name="g">The green component value.</param>
|
||||
/// <param name="b">The blue component value.</param>
|
||||
public Color(byte r, byte g, byte b) => (R, G, B) = (r, g, b);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RPiRgbLEDMatrix;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
internal struct InternalRGBLedMatrixOptions
|
||||
{
|
||||
public IntPtr hardware_mapping;
|
||||
public int rows;
|
||||
public int cols;
|
||||
public int chain_length;
|
||||
public int parallel;
|
||||
public int pwm_bits;
|
||||
public int pwm_lsb_nanoseconds;
|
||||
public int pwm_dither_bits;
|
||||
public int brightness;
|
||||
public int scan_mode;
|
||||
public int row_address_type;
|
||||
public int multiplexing;
|
||||
public IntPtr led_rgb_sequence;
|
||||
public IntPtr pixel_mapper_config;
|
||||
public IntPtr panel_type;
|
||||
public byte disable_hardware_pulsing;
|
||||
public byte show_refresh_rate;
|
||||
public byte inverse_colors;
|
||||
public int limit_refresh_rate_hz;
|
||||
|
||||
public InternalRGBLedMatrixOptions(RGBLedMatrixOptions opt)
|
||||
{
|
||||
chain_length = opt.ChainLength;
|
||||
rows = opt.Rows;
|
||||
cols = opt.Cols;
|
||||
hardware_mapping = Marshal.StringToHGlobalAnsi(opt.HardwareMapping);
|
||||
inverse_colors = (byte)(opt.InverseColors ? 1 : 0);
|
||||
led_rgb_sequence = Marshal.StringToHGlobalAnsi(opt.LedRgbSequence);
|
||||
pixel_mapper_config = Marshal.StringToHGlobalAnsi(opt.PixelMapperConfig);
|
||||
panel_type = Marshal.StringToHGlobalAnsi(opt.PanelType);
|
||||
parallel = opt.Parallel;
|
||||
multiplexing = (int)opt.Multiplexing;
|
||||
pwm_bits = opt.PwmBits;
|
||||
pwm_lsb_nanoseconds = opt.PwmLsbNanoseconds;
|
||||
pwm_dither_bits = opt.PwmDitherBits;
|
||||
scan_mode = (int)opt.ScanMode;
|
||||
show_refresh_rate = (byte)(opt.ShowRefreshRate ? 1 : 0);
|
||||
limit_refresh_rate_hz = opt.LimitRefreshRateHz;
|
||||
brightness = opt.Brightness;
|
||||
disable_hardware_pulsing = (byte)(opt.DisableHardwarePulsing ? 1 : 0);
|
||||
row_address_type = opt.RowAddressType;
|
||||
}
|
||||
};
|
||||
34
depends/rpi-rgb-led-matrix/bindings/c#/Makefile
Normal file
34
depends/rpi-rgb-led-matrix/bindings/c#/Makefile
Normal file
@@ -0,0 +1,34 @@
|
||||
# This Makefile is intended to be used only by the toplevel Makefile.
|
||||
# For any other purposes, use .NET SDK build tools directly
|
||||
|
||||
# Don't forget to synchronize these variables with the 'RPiRgbLEDMatrix.csproj' file
|
||||
RGB_LIBDIR=../../lib
|
||||
RGB_LIBRARY_NAME=rgbmatrix
|
||||
RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).so.1
|
||||
|
||||
NUGET_VERSION = 1.0.0
|
||||
NUGET_ID = HZeller.RPiRgbLEDMatrix
|
||||
NUGET_CONFIG = Release
|
||||
|
||||
NUGET_PACKAGE = /bin/$(NUGET_CONFIG)/$(NUGET_ID).$(NUGET_VERSION).nupkg
|
||||
|
||||
$(NUGET_PACKAGE): $(RGB_LIBRARY)
|
||||
dotnet pack -c $(NUGET_CONFIG) -p:SkipNative=false -p:PackageId=$(NUGET_ID) -p:Version=$(NUGET_VERSION)
|
||||
|
||||
# The examples also depend on the 'RPiRgbLEDMatrix.csproj', but this will be handled by 'dotnet'
|
||||
build: $(RGB_LIBRARY)
|
||||
dotnet build examples/FontExample/FontExample.csproj -p:SkipNative=false
|
||||
dotnet build examples/MatrixRain/MatrixRain.csproj -p:SkipNative=false
|
||||
dotnet build examples/MinimalExample/MinimalExample.csproj -p:SkipNative=false
|
||||
dotnet build examples/PulsingBrightness/PulsingBrightness.csproj -p:SkipNative=false
|
||||
dotnet build examples/Rotating3DCube/Rotating3DCube.csproj -p:SkipNative=false
|
||||
dotnet build examples/PlayGIF/PlayGIF.csproj -p:SkipNative=false
|
||||
|
||||
$(RGB_LIBRARY):
|
||||
$(MAKE) -C $(RGB_LIBDIR)
|
||||
|
||||
# Used by toplevel Makefile
|
||||
nuget: $(NUGET_PACKAGE)
|
||||
|
||||
# Used in 'RPiRgbLEDMatrix.csproj'
|
||||
library: $(RGB_LIBRARY)
|
||||
11
depends/rpi-rgb-led-matrix/bindings/c#/Multiplexing.cs
Normal file
11
depends/rpi-rgb-led-matrix/bindings/c#/Multiplexing.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace RPiRgbLEDMatrix;
|
||||
|
||||
/// <summary>
|
||||
/// Type of multiplexing.
|
||||
/// </summary>
|
||||
public enum Multiplexing : int
|
||||
{
|
||||
Direct = 0,
|
||||
Stripe = 1,
|
||||
Checker = 2
|
||||
}
|
||||
16
depends/rpi-rgb-led-matrix/bindings/c#/README.md
Normal file
16
depends/rpi-rgb-led-matrix/bindings/c#/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
C# bindings for RGB Matrix library
|
||||
======================================
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
To build the C# wrapper for the RGB Matrix C library you need to first have __.NET SDK__ installed.
|
||||
|
||||
### Install .NET SDK
|
||||
|
||||
`sudo apt install dotnet6` should work in most cases.
|
||||
For some old distributions, read [docs](https://learn.microsoft.com/dotnet/core/install/linux)
|
||||
|
||||
Then, in the `bindings/c#` directory type: `dotnet build`
|
||||
|
||||
To run the example applications in the c#\examples\EXAMPLE folder: `sudo dotnet run`
|
||||
100
depends/rpi-rgb-led-matrix/bindings/c#/RGBLedCanvas.cs
Normal file
100
depends/rpi-rgb-led-matrix/bindings/c#/RGBLedCanvas.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
namespace RPiRgbLEDMatrix;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a canvas whose pixels can be manipulated.
|
||||
/// </summary>
|
||||
public class RGBLedCanvas
|
||||
{
|
||||
// This is a wrapper for canvas no need to implement IDisposable here
|
||||
// because RGBLedMatrix has ownership and takes care of disposing canvases
|
||||
internal IntPtr _canvas;
|
||||
|
||||
// this is not called directly by the consumer code,
|
||||
// consumer uses factory methods in RGBLedMatrix
|
||||
internal RGBLedCanvas(IntPtr canvas)
|
||||
{
|
||||
_canvas = canvas;
|
||||
led_canvas_get_size(_canvas, out var width, out var height);
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The width of the canvas in pixels.
|
||||
/// </summary>
|
||||
public int Width { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The height of the canvas in pixels.
|
||||
/// </summary>
|
||||
public int Height { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color of a specific pixel.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate of the pixel.</param>
|
||||
/// <param name="y">The Y coordinate of the pixel.</param>
|
||||
/// <param name="color">New pixel color.</param>
|
||||
public void SetPixel(int x, int y, Color color) => led_canvas_set_pixel(_canvas, x, y, color.R, color.G, color.B);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the colors from the specified buffer to a rectangle on the canvas.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate of the top-left pixel of the rectangle.</param>
|
||||
/// <param name="y">The Y coordinate of the top-left pixel of the rectangle.</param>
|
||||
/// <param name="width">Width of the rectangle.</param>
|
||||
/// <param name="height">Height of the rectangle.</param>
|
||||
/// <param name="colors">Buffer containing the colors to copy.</param>
|
||||
public void SetPixels(int x, int y, int width, int height, Span<Color> colors)
|
||||
{
|
||||
if (colors.Length < width * height)
|
||||
throw new ArgumentOutOfRangeException(nameof(colors));
|
||||
led_canvas_set_pixels(_canvas, x, y, width, height, ref colors[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color of the entire canvas.
|
||||
/// </summary>
|
||||
/// <param name="color">New canvas color.</param>
|
||||
public void Fill(Color color) => led_canvas_fill(_canvas, color.R, color.G, color.B);
|
||||
|
||||
/// <summary>
|
||||
/// Cleans the entire canvas.
|
||||
/// </summary>
|
||||
public void Clear() => led_canvas_clear(_canvas);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a circle of the specified color.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate of the center.</param>
|
||||
/// <param name="y">The Y coordinate of the center.</param>
|
||||
/// <param name="radius">The radius of the circle, in pixels.</param>
|
||||
/// <param name="color">The color of the circle.</param>
|
||||
public void DrawCircle(int x, int y, int radius, Color color) =>
|
||||
draw_circle(_canvas, x, y, radius, color.R, color.G, color.B);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a line of the specified color.
|
||||
/// </summary>
|
||||
/// <param name="x0">The X coordinate of the first point.</param>
|
||||
/// <param name="y0">The Y coordinate of the first point.</param>
|
||||
/// <param name="x1">The X coordinate of the second point.</param>
|
||||
/// <param name="y1">The Y coordinate of the second point.</param>
|
||||
/// <param name="color">The color of the line.</param>
|
||||
public void DrawLine(int x0, int y0, int x1, int y1, Color color) =>
|
||||
draw_line(_canvas, x0, y0, x1, y1, color.R, color.G, color.B);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the text with the specified color.
|
||||
/// </summary>
|
||||
/// <param name="font">Font to draw text with.</param>
|
||||
/// <param name="x">The X coordinate of the starting point.</param>
|
||||
/// <param name="y">The Y coordinate of the starting point.</param>
|
||||
/// <param name="color">The color of the text.</param>
|
||||
/// <param name="text">Text to draw.</param>
|
||||
/// <param name="spacing">Additional spacing between characters.</param>
|
||||
/// <param name="vertical">Whether to draw the text vertically.</param>
|
||||
/// <returns>How many pixels was advanced on the screen.</returns>
|
||||
public int DrawText(RGBLedFont font, int x, int y, Color color, string text, int spacing = 0, bool vertical = false) =>
|
||||
font.DrawText(_canvas, x, y, color, text, spacing, vertical);
|
||||
}
|
||||
40
depends/rpi-rgb-led-matrix/bindings/c#/RGBLedFont.cs
Normal file
40
depends/rpi-rgb-led-matrix/bindings/c#/RGBLedFont.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace RPiRgbLEDMatrix;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a <c>.BDF</c> font.
|
||||
/// </summary>
|
||||
public class RGBLedFont : IDisposable
|
||||
{
|
||||
internal IntPtr _font;
|
||||
private bool disposedValue = false;
|
||||
|
||||
/// <summary>
|
||||
/// Loads the BDF font from the specified file.
|
||||
/// </summary>
|
||||
/// <param name="bdfFontPath">The path to the BDF file to load.</param>
|
||||
public RGBLedFont(string bdfFontPath) => _font = load_font(bdfFontPath);
|
||||
|
||||
internal int DrawText(IntPtr canvas, int x, int y, Color color, string text, int spacing = 0, bool vertical = false)
|
||||
{
|
||||
if (!vertical)
|
||||
return draw_text(canvas, _font, x, y, color.R, color.G, color.B, text, spacing);
|
||||
else
|
||||
return vertical_draw_text(canvas, _font, x, y, color.R, color.G, color.B, text, spacing);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposedValue) return;
|
||||
delete_font(_font);
|
||||
disposedValue = true;
|
||||
}
|
||||
|
||||
~RGBLedFont() => Dispose(false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
112
depends/rpi-rgb-led-matrix/bindings/c#/RGBLedMatrix.cs
Normal file
112
depends/rpi-rgb-led-matrix/bindings/c#/RGBLedMatrix.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RPiRgbLEDMatrix;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a RGB matrix.
|
||||
/// </summary>
|
||||
public class RGBLedMatrix : IDisposable
|
||||
{
|
||||
private IntPtr matrix;
|
||||
private bool disposedValue = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new matrix.
|
||||
/// </summary>
|
||||
/// <param name="rows">Size of a single module. Can be 32, 16 or 8.</param>
|
||||
/// <param name="chained">How many modules are connected in a chain.</param>
|
||||
/// <param name="parallel">How many modules are connected in a parallel.</param>
|
||||
public RGBLedMatrix(int rows, int chained, int parallel)
|
||||
{
|
||||
matrix = led_matrix_create(rows, chained, parallel);
|
||||
if (matrix == (IntPtr)0)
|
||||
throw new ArgumentException("Could not initialize a new matrix");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new matrix.
|
||||
/// </summary>
|
||||
/// <param name="options">A configuration of a matrix.</param>
|
||||
public RGBLedMatrix(RGBLedMatrixOptions options)
|
||||
{
|
||||
InternalRGBLedMatrixOptions opt = default;
|
||||
try
|
||||
{
|
||||
opt = new(options);
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
|
||||
// Because gpio-slowdown is not provided in the options struct,
|
||||
// we manually add it.
|
||||
// Let's add it first to the command-line we pass to the
|
||||
// matrix constructor, so that it can be overridden with the
|
||||
// users' commandline.
|
||||
// As always, as the _very_ first, we need to provide the
|
||||
// program name argv[0].
|
||||
var argv = new string[args.Length + 1];
|
||||
argv[0] = args[0];
|
||||
argv[1] = $"--led-slowdown-gpio={options.GpioSlowdown}";
|
||||
Array.Copy(args, 1, argv, 2, args.Length - 1);
|
||||
|
||||
matrix = led_matrix_create_from_options_const_argv(ref opt, argv.Length, argv);
|
||||
if (matrix == (IntPtr)0)
|
||||
throw new ArgumentException("Could not initialize a new matrix");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(options.HardwareMapping is not null) Marshal.FreeHGlobal(opt.hardware_mapping);
|
||||
if(options.LedRgbSequence is not null) Marshal.FreeHGlobal(opt.led_rgb_sequence);
|
||||
if(options.PixelMapperConfig is not null) Marshal.FreeHGlobal(opt.pixel_mapper_config);
|
||||
if(options.PanelType is not null) Marshal.FreeHGlobal(opt.panel_type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new backbuffer canvas for drawing on.
|
||||
/// </summary>
|
||||
/// <returns>An instance of <see cref="RGBLedCanvas"/> representing the canvas.</returns>
|
||||
public RGBLedCanvas CreateOffscreenCanvas() => new(led_matrix_create_offscreen_canvas(matrix));
|
||||
|
||||
/// <summary>
|
||||
/// Returns a canvas representing the current frame buffer.
|
||||
/// </summary>
|
||||
/// <returns>An instance of <see cref="RGBLedCanvas"/> representing the canvas.</returns>
|
||||
/// <remarks>Consider using <see cref="CreateOffscreenCanvas"/> instead.</remarks>
|
||||
public RGBLedCanvas GetCanvas() => new(led_matrix_get_canvas(matrix));
|
||||
|
||||
/// <summary>
|
||||
/// Swaps this canvas with the currently active canvas. The active canvas
|
||||
/// becomes a backbuffer and is mapped to <paramref name="canvas"/> instance.
|
||||
/// <br/>
|
||||
/// This operation guarantees vertical synchronization.
|
||||
/// </summary>
|
||||
/// <param name="canvas">Backbuffer canvas to swap.</param>
|
||||
public void SwapOnVsync(RGBLedCanvas canvas) =>
|
||||
canvas._canvas = led_matrix_swap_on_vsync(matrix, canvas._canvas);
|
||||
|
||||
/// <summary>
|
||||
/// The general brightness of the matrix.
|
||||
/// </summary>
|
||||
public byte Brightness
|
||||
{
|
||||
get => led_matrix_get_brightness(matrix);
|
||||
set => led_matrix_set_brightness(matrix, value);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposedValue) return;
|
||||
|
||||
led_matrix_delete(matrix);
|
||||
disposedValue = true;
|
||||
}
|
||||
|
||||
~RGBLedMatrix() => Dispose(false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
124
depends/rpi-rgb-led-matrix/bindings/c#/RGBLedMatrixOptions.cs
Normal file
124
depends/rpi-rgb-led-matrix/bindings/c#/RGBLedMatrixOptions.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
namespace RPiRgbLEDMatrix;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the matrix settings.
|
||||
/// </summary>
|
||||
public struct RGBLedMatrixOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the hardware mapping used. If passed
|
||||
/// <see langword="null"/> here, the default is used.
|
||||
/// </summary>
|
||||
public string? HardwareMapping = null;
|
||||
|
||||
/// <summary>
|
||||
/// The "rows" are the number of rows supported by the display, so 32 or 16.
|
||||
/// Default: 32.
|
||||
/// </summary>
|
||||
public int Rows = 32;
|
||||
|
||||
/// <summary>
|
||||
/// The "cols" are the number of columns per panel. Typically something
|
||||
/// like 32, but also 64 is possible. Sometimes even 40.
|
||||
/// <c>cols * chain_length</c> is the total length of the display, so you can
|
||||
/// represent a 64 wide display as cols=32, chain=2 or cols=64, chain=1;
|
||||
/// same thing, but more convenient to think of.
|
||||
/// </summary>
|
||||
public int Cols = 32;
|
||||
|
||||
/// <summary>
|
||||
/// The chain_length is the number of displays daisy-chained together
|
||||
/// (output of one connected to input of next). Default: 1
|
||||
/// </summary>
|
||||
public int ChainLength = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The number of parallel chains connected to the Pi; in old Pis with 26
|
||||
/// GPIO pins, that is 1, in newer Pis with 40 interfaces pins, that can also
|
||||
/// be 2 or 3. The effective number of pixels in vertical direction is then
|
||||
/// thus <c>rows * parallel</c>. Default: 1
|
||||
/// </summary>
|
||||
public int Parallel = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Set PWM bits used for output. Default is 11, but if you only deal with limited
|
||||
/// comic-colors, 1 might be sufficient. Lower require less CPU and increases refresh-rate.
|
||||
/// </summary>
|
||||
public int PwmBits = 11;
|
||||
|
||||
/// <summary>
|
||||
/// Change the base time-unit for the on-time in the lowest significant bit in
|
||||
/// nanoseconds. Higher numbers provide better quality (more accurate color, less
|
||||
/// ghosting), but have a negative impact on the frame rate.
|
||||
/// </summary>
|
||||
public int PwmLsbNanoseconds = 130;
|
||||
|
||||
/// <summary>
|
||||
/// The lower bits can be time-dithered for higher refresh rate.
|
||||
/// </summary>
|
||||
public int PwmDitherBits = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The initial brightness of the panel in percent. Valid range is 1..100
|
||||
/// </summary>
|
||||
public int Brightness = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Scan mode.
|
||||
/// </summary>
|
||||
public ScanModes ScanMode = ScanModes.Progressive;
|
||||
|
||||
/// <summary>
|
||||
/// Default row address type is 0, corresponding to direct setting of the
|
||||
/// row, while row address type 1 is used for panels that only have A/B,
|
||||
/// typically some 64x64 panels
|
||||
/// </summary>
|
||||
public int RowAddressType = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Type of multiplexing.
|
||||
/// </summary>
|
||||
public Multiplexing Multiplexing = Multiplexing.Direct;
|
||||
|
||||
/// <summary>
|
||||
/// In case the internal sequence of mapping is not <c>"RGB"</c>, this
|
||||
/// contains the real mapping. Some panels mix up these colors.
|
||||
/// </summary>
|
||||
public string? LedRgbSequence = null;
|
||||
|
||||
/// <summary>
|
||||
/// A string describing a sequence of pixel mappers that should be applied
|
||||
/// to this matrix. A semicolon-separated list of pixel-mappers with optional
|
||||
/// parameter.
|
||||
public string? PixelMapperConfig = null;
|
||||
|
||||
/// <summary>
|
||||
/// Panel type. Typically just empty, but certain panels (FM6126)
|
||||
/// requie an initialization sequence
|
||||
/// </summary>
|
||||
public string? PanelType = null;
|
||||
|
||||
/// <summary>
|
||||
/// Allow to use the hardware subsystem to create pulses. This won't do
|
||||
/// anything if output enable is not connected to GPIO 18.
|
||||
/// </summary>
|
||||
public bool DisableHardwarePulsing = false;
|
||||
public bool ShowRefreshRate = false;
|
||||
public bool InverseColors = false;
|
||||
|
||||
/// <summary>
|
||||
/// Limit refresh rate of LED panel. This will help on a loaded system
|
||||
/// to keep a constant refresh rate. <= 0 for no limit.
|
||||
/// </summary>
|
||||
public int LimitRefreshRateHz = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Slowdown GPIO. Needed for faster Pis/slower panels.
|
||||
/// </summary>
|
||||
public int GpioSlowdown = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Creates default matrix settings.
|
||||
/// </summary>
|
||||
public RGBLedMatrixOptions() { }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Skip builing native libraries by default on Windows -->
|
||||
<PropertyGroup Condition="$(SkipNative) == '' and $(OS) != 'Unix'">
|
||||
<SkipNative>true</SkipNative>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="examples\**" />
|
||||
<None Remove="examples\**" />
|
||||
<None Condition="$(SkipNative) != 'true'" Pack="true"
|
||||
Include="..\..\lib\librgbmatrix.so.1"
|
||||
PackagePath="\runtimes\linux-arm64\native" >
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CompileNative" BeforeTargets="Compile" Condition="$(SkipNative) != 'true'">
|
||||
<Message Text="Building native libraries" />
|
||||
<Exec Command="make library" />
|
||||
</Target>
|
||||
</Project>
|
||||
10
depends/rpi-rgb-led-matrix/bindings/c#/ScanModes.cs
Normal file
10
depends/rpi-rgb-led-matrix/bindings/c#/ScanModes.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace RPiRgbLEDMatrix;
|
||||
|
||||
/// <summary>
|
||||
/// Scan modes.
|
||||
/// </summary>
|
||||
public enum ScanModes
|
||||
{
|
||||
Progressive = 0,
|
||||
Interlaced = 1
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RPiRgbLEDMatrix.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,29 @@
|
||||
using RPiRgbLEDMatrix;
|
||||
|
||||
if (args.Length < 1)
|
||||
{
|
||||
Console.WriteLine("font-example.exe [font_path] <text>");
|
||||
return -1;
|
||||
}
|
||||
string text = "Hello World!";
|
||||
if (args.Length > 1)
|
||||
text = args[1];
|
||||
|
||||
|
||||
using var matrix = new RGBLedMatrix(32, 2, 1);
|
||||
var canvas = matrix.CreateOffscreenCanvas();
|
||||
using var font = new RGBLedFont(args[0]);
|
||||
|
||||
canvas.DrawText(font, 1, 6, new Color(0, 255, 0), text);
|
||||
matrix.SwapOnVsync(canvas);
|
||||
|
||||
// run until user presses Ctrl+C
|
||||
var running = true;
|
||||
Console.CancelKeyPress += (_, e) =>
|
||||
{
|
||||
running = false;
|
||||
e.Cancel = true; // do not terminate program with Ctrl+C, we need to dispose
|
||||
};
|
||||
while (running) Thread.Yield();
|
||||
|
||||
return 0;
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RPiRgbLEDMatrix.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,74 @@
|
||||
using RPiRgbLEDMatrix;
|
||||
|
||||
const int MaxHeight = 16;
|
||||
const int ColorStep = 15;
|
||||
const int FrameStep = 1;
|
||||
|
||||
using var matrix = new RGBLedMatrix(new RGBLedMatrixOptions { ChainLength = 2 });
|
||||
var canvas = matrix.CreateOffscreenCanvas();
|
||||
|
||||
var rnd = new Random();
|
||||
var points = new List<Point>();
|
||||
var recycled = new Stack<Point>();
|
||||
var frame = 0;
|
||||
|
||||
var running = true;
|
||||
Console.CancelKeyPress += (s, e) =>
|
||||
{
|
||||
running = false;
|
||||
e.Cancel = true; // don't terminate, we need to dispose
|
||||
};
|
||||
|
||||
// run until user presses Ctrl+C
|
||||
while (running)
|
||||
{
|
||||
var frameStart = Environment.TickCount64;
|
||||
frame++;
|
||||
|
||||
if (frame % FrameStep == 0)
|
||||
{
|
||||
if (recycled.Count == 0)
|
||||
points.Add(new Point(rnd.Next(0, canvas.Width - 1), 0));
|
||||
else
|
||||
{
|
||||
var point = recycled.Pop();
|
||||
point.X = rnd.Next(0, canvas.Width - 1);
|
||||
point.Y = 0;
|
||||
point.Recycled = false;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Clear();
|
||||
|
||||
foreach (var point in points)
|
||||
{
|
||||
if (point.Recycled) continue;
|
||||
point.Y++;
|
||||
|
||||
if (point.Y - MaxHeight > canvas.Height)
|
||||
{
|
||||
point.Recycled = true;
|
||||
recycled.Push(point);
|
||||
}
|
||||
|
||||
for (var i = 0; i < MaxHeight; i++)
|
||||
{
|
||||
canvas.SetPixel(point.X, point.Y - i, new Color(0, 255 - i * ColorStep, 0));
|
||||
}
|
||||
}
|
||||
|
||||
matrix.SwapOnVsync(canvas);
|
||||
|
||||
// force 30 FPS
|
||||
var elapsed = Environment.TickCount64 - frameStart;
|
||||
if (elapsed < 33) Thread.Sleep(33 - (int)elapsed);
|
||||
}
|
||||
|
||||
class Point
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
public bool Recycled = false;
|
||||
|
||||
public Point(int x, int y) => (X, Y) = (x, y);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RPiRgbLEDMatrix.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
using RPiRgbLEDMatrix;
|
||||
|
||||
using var matrix = new RGBLedMatrix(32, 2, 1);
|
||||
var canvas = matrix.CreateOffscreenCanvas();
|
||||
|
||||
var centerX = canvas.Width / 2;
|
||||
var centerY = canvas.Height / 2;
|
||||
for (var i = 0; i < 1000; ++i)
|
||||
{
|
||||
for (var y = 0; y < canvas.Height; ++y)
|
||||
for (var x = 0; x < canvas.Width; ++x)
|
||||
canvas.SetPixel(x, y, new Color(i & 0xFF, x, y));
|
||||
|
||||
canvas.DrawCircle(centerX, centerY, 6, new Color(0, 0, 255));
|
||||
canvas.DrawLine(centerX - 3, centerY - 3, centerX + 3, centerY + 3, new Color(0, 0, 255));
|
||||
canvas.DrawLine(centerX - 3, centerY + 3, centerX + 3, centerY - 3, new Color(0, 0, 255));
|
||||
|
||||
matrix.SwapOnVsync(canvas);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RPiRgbLEDMatrix.csproj" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,40 @@
|
||||
using RPiRgbLEDMatrix;
|
||||
using System.Runtime.InteropServices;
|
||||
using Color = RPiRgbLEDMatrix.Color;
|
||||
|
||||
Console.Write("GIF path: ");
|
||||
var path = Console.ReadLine()!;
|
||||
|
||||
using var matrix = new RGBLedMatrix(32, 2, 1);
|
||||
var canvas = matrix.CreateOffscreenCanvas();
|
||||
|
||||
Configuration.Default.PreferContiguousImageBuffers = true;
|
||||
using var image = Image.Load<Rgb24>(path);
|
||||
image.Mutate(o => o.Resize(canvas.Width, canvas.Height));
|
||||
|
||||
var running = true;
|
||||
Console.CancelKeyPress += (s, e) =>
|
||||
{
|
||||
running = false;
|
||||
e.Cancel = true; // don't terminate, we need to dispose
|
||||
};
|
||||
|
||||
var frame = -1;
|
||||
// preprocess frames to get delays and pixel buffers
|
||||
var frames = image.Frames
|
||||
.Select(f => (
|
||||
Pixels: f.DangerousTryGetSinglePixelMemory(out var memory) ? memory : throw new("Could not get pixel buffer"),
|
||||
Delay: f.Metadata.GetGifMetadata().FrameDelay * 10
|
||||
)).ToArray();
|
||||
|
||||
// run until user presses Ctrl+C
|
||||
while (running)
|
||||
{
|
||||
frame = (frame + 1) % frames.Length;
|
||||
|
||||
var data = MemoryMarshal.Cast<Rgb24, Color>(frames[frame].Pixels.Span);
|
||||
canvas.SetPixels(0, 0, canvas.Width, canvas.Height, data);
|
||||
|
||||
matrix.SwapOnVsync(canvas);
|
||||
Thread.Sleep(frames[frame].Delay);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using RPiRgbLEDMatrix;
|
||||
|
||||
using var matrix = new RGBLedMatrix(new RGBLedMatrixOptions { Rows = 32, Cols = 64 });
|
||||
var canvas = matrix.CreateOffscreenCanvas();
|
||||
|
||||
var maxBrightness = matrix.Brightness;
|
||||
var rnd = new Random();
|
||||
|
||||
// run until user presses Ctrl+C
|
||||
var running = true;
|
||||
Console.CancelKeyPress += (_, e) =>
|
||||
{
|
||||
running = false;
|
||||
e.Cancel = true; // do not terminate program with Ctrl+C, we need to dispose
|
||||
};
|
||||
|
||||
var color = new Color(rnd.Next(0, 256), rnd.Next(0, 256), rnd.Next(0, 256));
|
||||
while (running)
|
||||
{
|
||||
if (matrix.Brightness < 1)
|
||||
{
|
||||
matrix.Brightness = maxBrightness;
|
||||
color = new Color(rnd.Next(0, 256), rnd.Next(0, 256), rnd.Next(0, 256));
|
||||
}
|
||||
else
|
||||
{
|
||||
matrix.Brightness--;
|
||||
}
|
||||
|
||||
canvas.Fill(color);
|
||||
matrix.SwapOnVsync(canvas);
|
||||
Thread.Sleep(20);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RPiRgbLEDMatrix.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,90 @@
|
||||
using RPiRgbLEDMatrix;
|
||||
using System.Numerics;
|
||||
|
||||
const float MaxModuleSpeed = 0.1f;
|
||||
const float FOV = 60f;
|
||||
const float Scale = 1.1f;
|
||||
const float LerpPow = 0.002f;
|
||||
const int ChangePerFrames = 50;
|
||||
|
||||
using var leds = new RGBLedMatrix(32, 1, 1);
|
||||
var canvas = leds.CreateOffscreenCanvas();
|
||||
|
||||
var (centerX, centerY) = (canvas.Width / 2, canvas.Height / 2);
|
||||
|
||||
var rnd = new Random();
|
||||
var angleSpeed = new Vector3();
|
||||
var nextAngleSpeed = new Vector3();
|
||||
var frame = -1;
|
||||
|
||||
var rotateMatrix = Matrix4x4.Identity;
|
||||
var scaleMatrix = Matrix4x4.CreateScale(Scale);
|
||||
var projectMatrix = Matrix4x4.CreatePerspectiveFieldOfView(FOV / 180 * MathF.PI, 1, 0.1f, 100f);
|
||||
var cameraMatrix = Matrix4x4.CreateLookAt(new(0, 0, 4), new(0, 0, 0), new(0, 1, 0));
|
||||
|
||||
// run until user presses Ctrl+C
|
||||
var running = true;
|
||||
Console.CancelKeyPress += (_, e) =>
|
||||
{
|
||||
running = false;
|
||||
e.Cancel = true; // do not terminate program with Ctrl+C, we need to dispose
|
||||
};
|
||||
while (running)
|
||||
{
|
||||
var frameStart = Environment.TickCount64;
|
||||
|
||||
// update angle speed
|
||||
frame = (frame + 1) % ChangePerFrames;
|
||||
if(frame == 0)
|
||||
nextAngleSpeed = new Vector3(
|
||||
(rnd.NextSingle() * 2 - 1) * MaxModuleSpeed,
|
||||
(rnd.NextSingle() * 2 - 1) * MaxModuleSpeed,
|
||||
(rnd.NextSingle() * 2 - 1) * MaxModuleSpeed
|
||||
);
|
||||
|
||||
angleSpeed = Vector3.Lerp(angleSpeed, nextAngleSpeed, LerpPow);
|
||||
|
||||
// update matrices
|
||||
rotateMatrix *= Matrix4x4.CreateRotationX(angleSpeed.X);
|
||||
rotateMatrix *= Matrix4x4.CreateRotationY(angleSpeed.Y);
|
||||
rotateMatrix *= Matrix4x4.CreateRotationZ(angleSpeed.Z);
|
||||
var matrix = scaleMatrix * rotateMatrix * cameraMatrix * projectMatrix;
|
||||
|
||||
// calculate points
|
||||
var top1 = Vector4.Transform(new Vector3( 1, 1, 1), matrix);
|
||||
var top2 = Vector4.Transform(new Vector3(-1, 1, 1), matrix);
|
||||
var top3 = Vector4.Transform(new Vector3(-1, 1, -1), matrix);
|
||||
var top4 = Vector4.Transform(new Vector3( 1, 1, -1), matrix);
|
||||
|
||||
var bot1 = Vector4.Transform(new Vector3( 1, -1, 1), matrix);
|
||||
var bot2 = Vector4.Transform(new Vector3(-1, -1, 1), matrix);
|
||||
var bot3 = Vector4.Transform(new Vector3(-1, -1, -1), matrix);
|
||||
var bot4 = Vector4.Transform(new Vector3( 1, -1, -1), matrix);
|
||||
|
||||
// draw
|
||||
canvas.Fill(new(0, 0, 0));
|
||||
DrawLine(top1, top2);
|
||||
DrawLine(top2, top3);
|
||||
DrawLine(top3, top4);
|
||||
DrawLine(top4, top1);
|
||||
|
||||
DrawLine(bot1, bot2);
|
||||
DrawLine(bot2, bot3);
|
||||
DrawLine(bot3, bot4);
|
||||
DrawLine(bot4, bot1);
|
||||
|
||||
DrawLine(top1, bot1);
|
||||
DrawLine(top2, bot2);
|
||||
DrawLine(top3, bot3);
|
||||
DrawLine(top4, bot4);
|
||||
|
||||
leds.SwapOnVsync(canvas);
|
||||
// force 30 FPS
|
||||
var elapsed = Environment.TickCount64 - frameStart;
|
||||
if (elapsed < 33) Thread.Sleep(33 - (int)elapsed);
|
||||
}
|
||||
|
||||
void DrawLine(Vector4 a, Vector4 b) => canvas.DrawLine(
|
||||
(int)(a.X * a.W + centerX), (int)(a.Y * a.W + centerY),
|
||||
(int)(b.X * b.W + centerX), (int)(b.Y * b.W + centerY),
|
||||
new(255, 255, 255));
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RPiRgbLEDMatrix.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user