Skip to content
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/.vscode
/build
/extensions/nanoled/build
/extensions/grayscale/extension.so
/extensions/grayscale/extension.so
/examples/csharp/obj
/examples/csharp/bin
215 changes: 215 additions & 0 deletions examples/csharp/PanelPlayerExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
using System;
using System.Runtime.InteropServices;
using System.IO;

namespace PanelPlayerExample
{
public static class PanelPlayerNative
{
private const string LibraryName = "libpanelplayer.so";

public const int PANELPLAYER_SUCCESS = 0;
public const int PANELPLAYER_ERROR = -1;
public const int PANELPLAYER_INVALID_PARAM = -2;
public const int PANELPLAYER_NOT_INITIALIZED = -3;
public const int PANELPLAYER_ALREADY_INITIALIZED = -4;

[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern int panelplayer_init(
[MarshalAs(UnmanagedType.LPStr)] string interface_name,
int width,
int height,
int brightness
);

[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern int panelplayer_set_mix(int mix_percentage);

[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern int panelplayer_set_rate(int frame_rate);

[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern int panelplayer_load_extension(
[MarshalAs(UnmanagedType.LPStr)] string extension_path
);

[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern int panelplayer_play_webp_file(
[MarshalAs(UnmanagedType.LPStr)] string file_path
);

[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern int panelplayer_play_frame_bgr(
[MarshalAs(UnmanagedType.LPArray)] byte[] bgr_data,
int width,
int height
);

[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern int panelplayer_send_brightness(
byte red,
byte green,
byte blue
);

[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool panelplayer_is_initialized();

[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]
public static extern void panelplayer_cleanup();
}

public class PanelPlayer : IDisposable
{
private bool disposed = false;

public PanelPlayer(string interfaceName, int width, int height, int brightness = 255)
{
int result = PanelPlayerNative.panelplayer_init(interfaceName, width, height, brightness);
if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
{
throw new Exception($"Failed to initialize PanelPlayer: {result}");
}
}

public void SetMix(int mixPercentage)
{
int result = PanelPlayerNative.panelplayer_set_mix(mixPercentage);
if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
{
throw new Exception($"Failed to set mix: {result}");
}
}

public void SetFrameRate(int frameRate)
{
int result = PanelPlayerNative.panelplayer_set_rate(frameRate);
if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
{
throw new Exception($"Failed to set frame rate: {result}");
}
}

public void LoadExtension(string extensionPath)
{
int result = PanelPlayerNative.panelplayer_load_extension(extensionPath);
if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
{
throw new Exception($"Failed to load extension: {result}");
}
}

public void PlayWebPFile(string filePath)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"WebP file not found: {filePath}");
}

int result = PanelPlayerNative.panelplayer_play_webp_file(filePath);
if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
{
throw new Exception($"Failed to play WebP file: {result}");
}
}

public void PlayFrameBGR(byte[] bgrData, int width, int height)
{
if (bgrData == null)
{
throw new ArgumentNullException(nameof(bgrData));
}

if (bgrData.Length != width * height * 3)
{
throw new ArgumentException("BGR data length doesn't match width * height * 3");
}

int result = PanelPlayerNative.panelplayer_play_frame_bgr(bgrData, width, height);
if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
{
throw new Exception($"Failed to play frame: {result}");
}
}

public void SetBrightness(byte red, byte green, byte blue)
{
int result = PanelPlayerNative.panelplayer_send_brightness(red, green, blue);
if (result != PanelPlayerNative.PANELPLAYER_SUCCESS)
{
throw new Exception($"Failed to set brightness: {result}");
}
}

public bool IsInitialized => PanelPlayerNative.panelplayer_is_initialized();

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
PanelPlayerNative.panelplayer_cleanup();
disposed = true;
}
}

~PanelPlayer()
{
Dispose(false);
}
}

class Program
{
static void Main(string[] args)
{

try
{
string interfaceName = "end0"; // Network interface (usually eth0)
int width = 192;
int height = 64;
int brightness = 255;
// Basic use example (Ejemplo básico de uso)
using (var player = new PanelPlayer(interfaceName, width, height, brightness))
{
Console.WriteLine("PanelPlayer initialized successfully!");

// Play WebP File (Reproducir archivo WebP)
if (args.Length > 0)
{
player.PlayWebPFile(args[0]);
Console.WriteLine($"Played WebP file: {args[0]}");
System.Threading.Thread.Sleep(10000); // Wait for 10 seconds
}

// Manualy send blue frame (Ejemplo de frame manual (cuadrado azul))
byte[] blueFrame = new byte[width * height * 3];
for (int i = 0; i < blueFrame.Length; i += 3)
{
blueFrame[i] = 255; // Blue
blueFrame[i + 1] = 0; // Green
blueFrame[i + 2] = 0; // Red
}

player.PlayFrameBGR(blueFrame, width, height);
Console.WriteLine("Displayed blue frame");

// Wait before exit. (Esperar un poco antes de salir)
System.Threading.Thread.Sleep(2000);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine("Note: This example requires root privileges and a valid ethernet interface.");
}
}
}
}
16 changes: 16 additions & 0 deletions examples/csharp/PanelPlayerExample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<None Update="../../build/libpanelplayer.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
112 changes: 112 additions & 0 deletions examples/csharp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# PanelPlayer C# Example

This directory contains a C# example application that demonstrates how to use the PanelPlayer library from .NET applications.

## Prerequisites

- .NET 9.0 or later
- libpanelplayer.so compiled and available
- Root privileges (required for raw ethernet access)

## Building

### 1. Build the PanelPlayer Library

First, you need to build the shared library from the main project directory:

```bash
cd /path/to/PanelPlayer
make library
```

This will create `build/libpanelplayer.so`.

### 2. Copy Library to Output Directory

The C# project is configured to automatically copy the library to the output directory during build, but you can also copy it manually:

```bash
cp ../../build/libpanelplayer.so ./bin/Debug/net9.0/
```

### 3. Build the C# Application

```bash
dotnet build
```

## Running

The application must be run as root due to raw ethernet socket requirements:

```bash
sudo dotnet run [webp-file]
```

### Examples

Play a WebP file:
```bash
sudo dotnet run animation.webp
```

Run without arguments to see the blue frame demonstration:
```bash
sudo dotnet run
```

## Publish

Make a self contained app
```bash
dotnet publish -r linux-arm64 --self-contained true -c Release
```
## Usage

The example demonstrates:

1. **Basic initialization** - Setting up the panel with network interface and dimensions
2. **WebP file playback** - Playing WebP files (if provided as argument)
3. **Manual frame display** - Sending raw BGR pixel data to create a blue frame
4. **Proper cleanup** - Using `using` statement for automatic resource disposal

## Configuration

The example is configured for:
- Network interface: `end0` (eth0 is more common)
- Panel dimensions: 192x64 pixels
- Brightness: 255 (maximum)

Modify these values in `PanelPlayerExample.cs` to match your setup.

## Tested Environment

This example has been tested on:
- **Hardware**: Orange Pi Zero 3
- **OS**: Debian Bookworm
- **Runtime**: .NET 9.0.302

## Troubleshooting

### Permission Errors
Make sure you're running with `sudo` - raw ethernet access requires root privileges.

### Library Not Found
Ensure `libpanelplayer.so` is in the output directory or in your system's library path.

### Network Interface Issues
Verify that the network interface name (`end0`) matches your system's ethernet interface.

## API Reference

The C# wrapper provides these main methods:

- `PanelPlayer(interface, width, height, brightness)` - Initialize the panel
- `PlayWebPFile(path)` - Play animated WebP file
- `PlayFrameBGR(data, width, height)` - Display raw BGR frame data
- `SetBrightness(red, green, blue)` - Adjust color balance
- `LoadExtension(path)` - Load processing extensions
- `SetMix(percentage)` - Control frame blending
- `SetFrameRate(fps)` - Override animation timing

For detailed API documentation, see the native library header file `source/panelplayer_api.h`.
19 changes: 14 additions & 5 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,29 @@ LDLIBS = -ldl -lwebpdemux
SOURCE = ./source
BUILD = ./build
TARGET = $(BUILD)/panelplayer
LIBRARY = $(BUILD)/libpanelplayer.so

HEADERS = $(wildcard $(SOURCE)/*.h)
OBJECTS = $(patsubst $(SOURCE)/%.c,$(BUILD)/%.o,$(wildcard $(SOURCE)/*.c))
MAIN_OBJECTS = $(patsubst $(SOURCE)/%.c,$(BUILD)/%.o,$(filter-out $(SOURCE)/panelplayer_api.c,$(wildcard $(SOURCE)/*.c)))
API_OBJECTS = $(patsubst $(SOURCE)/%.c,$(BUILD)/%.o,$(filter-out $(SOURCE)/main.c,$(wildcard $(SOURCE)/*.c)))

.PHONY: clean
.PHONY: clean all library

$(TARGET): $(BUILD) $(OBJECTS)
$(CC) $(LDFLAGS) $(OBJECTS) $(LDLIBS) -o $@
all: $(TARGET) $(LIBRARY)

$(TARGET): $(BUILD) $(MAIN_OBJECTS)
$(CC) $(LDFLAGS) $(MAIN_OBJECTS) $(LDLIBS) -o $@

$(LIBRARY): $(BUILD) $(API_OBJECTS)
$(CC) $(LDFLAGS) -shared -fPIC $(API_OBJECTS) $(LDLIBS) -o $@

library: $(LIBRARY)

$(BUILD):
mkdir $(BUILD)

$(BUILD)/%.o: $(SOURCE)/%.c $(HEADERS) makefile
$(CC) $(CFLAGS) -c $< -o $@
$(CC) $(CFLAGS) -fPIC -c $< -o $@

clean:
rm -r $(BUILD)
Loading