diff --git a/.gitignore b/.gitignore index e6421fb..ead600d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /.vscode /build /extensions/nanoled/build -/extensions/grayscale/extension.so \ No newline at end of file +/extensions/grayscale/extension.so +/examples/csharp/obj +/examples/csharp/bin \ No newline at end of file diff --git a/examples/csharp/PanelPlayerExample.cs b/examples/csharp/PanelPlayerExample.cs new file mode 100644 index 0000000..c326bcf --- /dev/null +++ b/examples/csharp/PanelPlayerExample.cs @@ -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."); + } + } + } +} \ No newline at end of file diff --git a/examples/csharp/PanelPlayerExample.csproj b/examples/csharp/PanelPlayerExample.csproj new file mode 100644 index 0000000..9a3cc67 --- /dev/null +++ b/examples/csharp/PanelPlayerExample.csproj @@ -0,0 +1,16 @@ + + + + Exe + net9.0 + enable + true + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/examples/csharp/README.md b/examples/csharp/README.md new file mode 100644 index 0000000..2bf6bbc --- /dev/null +++ b/examples/csharp/README.md @@ -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`. \ No newline at end of file diff --git a/makefile b/makefile index 6fd70a0..5690ca8 100644 --- a/makefile +++ b/makefile @@ -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) \ No newline at end of file diff --git a/source/panelplayer_api.c b/source/panelplayer_api.c new file mode 100644 index 0000000..4afffbd --- /dev/null +++ b/source/panelplayer_api.c @@ -0,0 +1,312 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "panelplayer_api.h" +#include "colorlight.h" +#include "loader.h" + +#define MIX_MAXIMUM 100 +#define UPDATE_DELAY 10 + +typedef struct { + colorlight *colorlight; + uint8_t *buffer; + int width; + int height; + int brightness; + int mix; + int rate; + void *extension; + void (*update_func)(int width, int height, uint8_t *frame); + bool initialized; +} panelplayer_state; + +static panelplayer_state g_state = {0}; + +static long get_time(void) { + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + return time.tv_sec * 1000 + time.tv_nsec / 1000000; +} + +static void await(long time) { + long delay = time - get_time(); + if (delay > 0) { + usleep(delay * 1000); + } +} + +int panelplayer_init(const char* interface_name, int width, int height, int brightness) { + if (g_state.initialized) { + return PANELPLAYER_ALREADY_INITIALIZED; + } + + if (!interface_name || width <= 0 || height <= 0 || brightness < 0 || brightness > 255) { + return PANELPLAYER_INVALID_PARAM; + } + + g_state.colorlight = colorlight_init((char*)interface_name); + if (!g_state.colorlight) { + return PANELPLAYER_ERROR; + } + + g_state.buffer = malloc(width * height * 3); + if (!g_state.buffer) { + colorlight_destroy(g_state.colorlight); + return PANELPLAYER_ERROR; + } + + g_state.width = width; + g_state.height = height; + g_state.brightness = brightness; + g_state.mix = 0; + g_state.rate = 0; + g_state.extension = NULL; + g_state.update_func = NULL; + g_state.initialized = true; + + return PANELPLAYER_SUCCESS; +} + +int panelplayer_set_mix(int mix_percentage) { + if (!g_state.initialized) { + return PANELPLAYER_NOT_INITIALIZED; + } + + if (mix_percentage < 0 || mix_percentage >= MIX_MAXIMUM) { + return PANELPLAYER_INVALID_PARAM; + } + + g_state.mix = mix_percentage; + return PANELPLAYER_SUCCESS; +} + +int panelplayer_set_rate(int frame_rate) { + if (!g_state.initialized) { + return PANELPLAYER_NOT_INITIALIZED; + } + + if (frame_rate < 0) { + return PANELPLAYER_INVALID_PARAM; + } + + g_state.rate = frame_rate; + return PANELPLAYER_SUCCESS; +} + +int panelplayer_load_extension(const char* extension_path) { + if (!g_state.initialized) { + return PANELPLAYER_NOT_INITIALIZED; + } + + if (!extension_path) { + return PANELPLAYER_INVALID_PARAM; + } + + if (g_state.extension) { + void (*destroy)() = dlsym(g_state.extension, "destroy"); + if (destroy) { + destroy(); + } + dlclose(g_state.extension); + g_state.extension = NULL; + g_state.update_func = NULL; + } + + g_state.extension = dlopen(extension_path, RTLD_NOW); + if (!g_state.extension) { + return PANELPLAYER_ERROR; + } + + bool (*init)() = dlsym(g_state.extension, "init"); + if (init && init()) { + dlclose(g_state.extension); + g_state.extension = NULL; + return PANELPLAYER_ERROR; + } + + g_state.update_func = dlsym(g_state.extension, "update"); + if (!g_state.update_func) { + dlclose(g_state.extension); + g_state.extension = NULL; + return PANELPLAYER_ERROR; + } + + return PANELPLAYER_SUCCESS; +} + +int panelplayer_play_webp_file(const char* file_path) { + if (!g_state.initialized) { + return PANELPLAYER_NOT_INITIALIZED; + } + + if (!file_path) { + return PANELPLAYER_INVALID_PARAM; + } + + FILE *file = fopen(file_path, "rb"); + if (!file) { + return PANELPLAYER_ERROR; + } + + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + uint8_t *file_data = malloc(file_size); + if (!file_data) { + fclose(file); + return PANELPLAYER_ERROR; + } + + if (fread(file_data, 1, file_size, file) != file_size) { + free(file_data); + fclose(file); + return PANELPLAYER_ERROR; + } + fclose(file); + + WebPData data = { + .bytes = file_data, + .size = file_size + }; + + WebPAnimDecoder *decoder = WebPAnimDecoderNew(&data, NULL); + if (!decoder) { + free(file_data); + return PANELPLAYER_ERROR; + } + + WebPAnimInfo info; + WebPAnimDecoderGetInfo(decoder, &info); + + if (info.canvas_width < g_state.width || info.canvas_height < g_state.height) { + WebPAnimDecoderDelete(decoder); + free(file_data); + return PANELPLAYER_ERROR; + } + + long next = get_time(); + int previous = 0; + bool initial = true; + + while (WebPAnimDecoderHasMoreFrames(decoder)) { + uint8_t *decoded; + int timestamp; + + WebPAnimDecoderGetNext(decoder, &decoded, ×tamp); + + for (int y = 0; y < g_state.height; y++) { + for (int x = 0; x < g_state.width; x++) { + int source = (y * info.canvas_width + x) * 4; + int destination = (y * g_state.width + x) * 3; + + int oldFactor = initial ? 0 : g_state.mix; + int newFactor = MIX_MAXIMUM - oldFactor; + + g_state.buffer[destination] = (g_state.buffer[destination] * oldFactor + decoded[source + 2] * newFactor) / MIX_MAXIMUM; + g_state.buffer[destination + 1] = (g_state.buffer[destination + 1] * oldFactor + decoded[source + 1] * newFactor) / MIX_MAXIMUM; + g_state.buffer[destination + 2] = (g_state.buffer[destination + 2] * oldFactor + decoded[source] * newFactor) / MIX_MAXIMUM; + } + } + + if (g_state.update_func) { + g_state.update_func(g_state.width, g_state.height, g_state.buffer); + } + + for (int y = 0; y < g_state.height; y++) { + colorlight_send_row(g_state.colorlight, y, g_state.width, g_state.buffer + y * g_state.width * 3); + } + + if (next - get_time() < UPDATE_DELAY) { + next = get_time() + UPDATE_DELAY; + } + + await(next); + colorlight_send_update(g_state.colorlight, g_state.brightness, g_state.brightness, g_state.brightness); + + if (g_state.rate > 0) { + next = get_time() + 1000 / g_state.rate; + } else { + next = get_time() + timestamp - previous; + previous = timestamp; + } + + initial = false; + } + + WebPAnimDecoderDelete(decoder); + free(file_data); + + return PANELPLAYER_SUCCESS; +} + +int panelplayer_play_frame_bgr(const uint8_t* bgr_data, int width, int height) { + if (!g_state.initialized) { + return PANELPLAYER_NOT_INITIALIZED; + } + + if (!bgr_data || width != g_state.width || height != g_state.height) { + return PANELPLAYER_INVALID_PARAM; + } + + memcpy(g_state.buffer, bgr_data, width * height * 3); + + if (g_state.update_func) { + g_state.update_func(g_state.width, g_state.height, g_state.buffer); + } + + for (int y = 0; y < g_state.height; y++) { + colorlight_send_row(g_state.colorlight, y, g_state.width, g_state.buffer + y * g_state.width * 3); + } + + long next = get_time() + UPDATE_DELAY; + await(next); + colorlight_send_update(g_state.colorlight, g_state.brightness, g_state.brightness, g_state.brightness); + + return PANELPLAYER_SUCCESS; +} + +int panelplayer_send_brightness(uint8_t red, uint8_t green, uint8_t blue) { + if (!g_state.initialized) { + return PANELPLAYER_NOT_INITIALIZED; + } + + colorlight_send_brightness(g_state.colorlight, red, green, blue); + return PANELPLAYER_SUCCESS; +} + +bool panelplayer_is_initialized(void) { + return g_state.initialized; +} + +void panelplayer_cleanup(void) { + if (!g_state.initialized) { + return; + } + + if (g_state.extension) { + void (*destroy)() = dlsym(g_state.extension, "destroy"); + if (destroy) { + destroy(); + } + dlclose(g_state.extension); + } + + if (g_state.colorlight) { + colorlight_destroy(g_state.colorlight); + } + + if (g_state.buffer) { + free(g_state.buffer); + } + + memset(&g_state, 0, sizeof(g_state)); +} \ No newline at end of file diff --git a/source/panelplayer_api.h b/source/panelplayer_api.h new file mode 100644 index 0000000..289b179 --- /dev/null +++ b/source/panelplayer_api.h @@ -0,0 +1,169 @@ +/** + * @file panelplayer_api.h + * @brief PanelPlayer C API for controlling Colorlight LED panels + * + * This header provides a C library interface for the PanelPlayer application, + * allowing programmatic control of Colorlight LED receiving cards via raw ethernet. + */ + +#ifndef PANELPLAYER_API_H +#define PANELPLAYER_API_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup ReturnCodes Return Codes + * @{ + */ +#define PANELPLAYER_SUCCESS 0 /**< Operation completed successfully */ +#define PANELPLAYER_ERROR -1 /**< General error occurred */ +#define PANELPLAYER_INVALID_PARAM -2 /**< Invalid parameter provided */ +#define PANELPLAYER_NOT_INITIALIZED -3 /**< Library not initialized */ +#define PANELPLAYER_ALREADY_INITIALIZED -4 /**< Library already initialized */ +/** @} */ + +typedef struct panelplayer_instance panelplayer_instance; + +/** + * @brief Initialize the PanelPlayer library + * + * Initializes the PanelPlayer library with the specified LED panel parameters. + * This function must be called before any other API functions. + * + * @param interface_name Network interface name (e.g., "eth0") + * @param width Panel width in pixels + * @param height Panel height in pixels + * @param brightness Initial brightness value (0-255) + * + * @return PANELPLAYER_SUCCESS on success, error code otherwise + * @retval PANELPLAYER_SUCCESS Initialization successful + * @retval PANELPLAYER_INVALID_PARAM Invalid parameters provided + * @retval PANELPLAYER_ALREADY_INITIALIZED Library already initialized + * @retval PANELPLAYER_ERROR Failed to initialize network interface or allocate memory + */ +int panelplayer_init(const char* interface_name, int width, int height, int brightness); + +/** + * @brief Set frame mixing percentage + * + * Controls how new frames are blended with existing content on the panel. + * A value of 0 completely replaces the current frame, while higher values + * blend the new frame with the existing content. + * + * @param mix_percentage Mixing percentage (0-99) + * + * @return PANELPLAYER_SUCCESS on success, error code otherwise + * @retval PANELPLAYER_SUCCESS Mix percentage set successfully + * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized + * @retval PANELPLAYER_INVALID_PARAM Mix percentage out of range + */ +int panelplayer_set_mix(int mix_percentage); + +/** + * @brief Set playback frame rate + * + * Sets the frame rate for WebP animation playback. When set to 0, + * uses the timing information embedded in the WebP file. + * + * @param frame_rate Target frame rate in frames per second (0 for auto) + * + * @return PANELPLAYER_SUCCESS on success, error code otherwise + * @retval PANELPLAYER_SUCCESS Frame rate set successfully + * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized + * @retval PANELPLAYER_INVALID_PARAM Negative frame rate provided + */ +int panelplayer_set_rate(int frame_rate); + +/** + * @brief Load a frame processing extension + * + * Loads a shared library extension that can modify frame data before + * it is sent to the LED panel. Extensions must implement the required + * interface with update() function. + * + * @param extension_path Path to the shared library (.so file) + * + * @return PANELPLAYER_SUCCESS on success, error code otherwise + * @retval PANELPLAYER_SUCCESS Extension loaded successfully + * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized + * @retval PANELPLAYER_INVALID_PARAM NULL extension path provided + * @retval PANELPLAYER_ERROR Failed to load extension or missing required functions + */ +int panelplayer_load_extension(const char* extension_path); + +/** + * @brief Play an animated WebP file + * + * Loads and plays an animated WebP file on the LED panel. The function + * handles frame timing and loops through all frames in the animation. + * + * @param file_path Path to the WebP file to play + * + * @return PANELPLAYER_SUCCESS on success, error code otherwise + * @retval PANELPLAYER_SUCCESS WebP file played successfully + * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized + * @retval PANELPLAYER_INVALID_PARAM NULL file path provided + * @retval PANELPLAYER_ERROR Failed to open file, decode WebP, or incompatible dimensions + */ +int panelplayer_play_webp_file(const char* file_path); + +/** + * @brief Display a single frame from BGR data + * + * Displays a single frame on the LED panel using raw BGR pixel data. + * The data must be in BGR format with 3 bytes per pixel. + * + * @param bgr_data Pointer to BGR pixel data (Blue, Green, Red order) + * @param width Frame width in pixels (must match initialized width) + * @param height Frame height in pixels (must match initialized height) + * + * @return PANELPLAYER_SUCCESS on success, error code otherwise + * @retval PANELPLAYER_SUCCESS Frame displayed successfully + * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized + * @retval PANELPLAYER_INVALID_PARAM NULL data pointer or mismatched dimensions + */ +int panelplayer_play_frame_bgr(const uint8_t* bgr_data, int width, int height); + +/** + * @brief Send brightness/color balance command + * + * Sends a brightness and color balance command directly to the LED panel. + * This allows independent control of red, green, and blue channel brightness. + * + * @param red Red channel brightness (0-255) + * @param green Green channel brightness (0-255) + * @param blue Blue channel brightness (0-255) + * + * @return PANELPLAYER_SUCCESS on success, error code otherwise + * @retval PANELPLAYER_SUCCESS Brightness command sent successfully + * @retval PANELPLAYER_NOT_INITIALIZED Library not initialized + */ +int panelplayer_send_brightness(uint8_t red, uint8_t green, uint8_t blue); + +/** + * @brief Check if library is initialized + * + * Returns the current initialization state of the PanelPlayer library. + * + * @return true if library is initialized, false otherwise + */ +bool panelplayer_is_initialized(void); + +/** + * @brief Cleanup and shutdown the library + * + * Cleans up all resources, closes network connections, unloads extensions, + * and resets the library to an uninitialized state. Should be called + * when finished using the library. + */ +void panelplayer_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file