///usr/bin/env jbang "$0" "$@" ; exit $? //DEPS org.slf4j:slf4j-api:2.0.3 //DEPS org.slf4j:slf4j-simple:2.0.3 //DEPS com.github.lalyos:jfiglet:0.0.8 //DEPS com.pi4j:pi4j-core:2.2.1 //DEPS com.pi4j:pi4j-plugin-raspberrypi:2.2.1 //DEPS com.pi4j:pi4j-plugin-pigpio:2.2.1 //DEPS com.pi4j:pi4j-plugin-linuxfs:2.2.1 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; import com.pi4j.Pi4J; import com.pi4j.context.Context; import com.pi4j.io.spi.Spi; import com.pi4j.io.spi.SpiConfig; import com.pi4j.io.spi.SpiMode; import com.pi4j.library.pigpio.PiGpio; import com.pi4j.plugin.linuxfs.provider.i2c.LinuxFsI2CProvider; import com.pi4j.plugin.pigpio.provider.gpio.digital.PiGpioDigitalInputProvider; import com.pi4j.plugin.pigpio.provider.gpio.digital.PiGpioDigitalOutputProvider; import com.pi4j.plugin.pigpio.provider.pwm.PiGpioPwmProvider; import com.pi4j.plugin.pigpio.provider.serial.PiGpioSerialProvider; import com.pi4j.plugin.pigpio.provider.spi.PiGpioSpiProvider; import com.pi4j.plugin.raspberrypi.platform.RaspberryPiPlatform; class LedStripSimple { public static void main(String[] args) { final var piGpio = PiGpio.newNativeInstance(); var pi4j = Pi4J.newContextBuilder() .noAutoDetect() .add(new RaspberryPiPlatform() { @Override protected String[] getProviders() { return new String[] {}; } }) .add(PiGpioSpiProvider.newInstance(piGpio)) .build(); Thread.currentThread().setPriority(Thread.MIN_PRIORITY); int pixels = 30; var ledStrip = new LedStripSimple(pi4j, pixels, 0.2); waitForKey("Initialized"); waitForKey("AllOff"); ledStrip.allOff(); while(true) { //waitForKey("Set led strip to ORANGE"); ledStrip.setStripColor(PixelColor.ORANGE); ledStrip.render(); delay(200); //waitForKey("Set led strip to PINK"); ledStrip.setStripColor(PixelColor.PINK); ledStrip.render(); delay(200); } } public static void waitForKey(String context) { System.out.println(context + ": Waiting..."); try { new BufferedReader(new InputStreamReader(System.in)).readLine(); } catch (IOException e) { throw new RuntimeException(e); } } static void delay(long milliseconds) { try { Thread.sleep(milliseconds); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * Default Channel of the SPI Pins */ protected static final int DEFAULT_SPI_CHANNEL = 0; /** * Minimum time to wait for reset to occur in nanoseconds. */ private static final long LED_RESET_WAIT_TIME = 300_000; /** * The PI4J SPI */ protected final Spi spi; /** * The PI4J context */ protected final Context context; /** * The amount of all LEDs */ private final int numLeds; /** * Default frequency of a WS2812 Neopixel Strip */ private static final int FREQUENCY = 800_000; /** * between each rendering of the strip, there has to be a reset-time where nothing is written to the SPI */ private final long renderWaitTime; /** * The array of all pixels */ private final int[] leds; /** * The raw-data of all pixels, each int of LEDs is split into bits and converted to bytes to write */ private final byte[] pixelRaw; /** * the conversion from bit's of an integer to a byte we can write on the SPI */ private static final byte Bit_0 = (byte) 0b11000000;// 192 in Decimal private static final byte Bit_1 = (byte) 0b11111000;// 248 in Decimal private static final byte Bit_Reset = (byte) 0b00000000;// 0 in Decimal /** * Brightness value between 0 and 1 */ private double brightness; /** * The time, when the last rendering happened */ private long lastRenderTime; /** * Creates a new simpleLed component with a custom BCM pin. * * @param pi4j * Pi4J context * @param numLeds * How many LEDs are on this Strand * @param brightness * How bright the leds can be at max, Range 0 - 255 */ public LedStripSimple(Context pi4j, int numLeds, double brightness) { this(pi4j, numLeds, brightness, DEFAULT_SPI_CHANNEL); } /** * Creates a new simpleLed component with a custom BCM pin. * * @param pi4j * Pi4J context * @param numLeds * How many LEDs are on this Strand * @param brightness * How bright the leds can be at max, range 0 - 1 * @param channel * which channel to use */ public LedStripSimple(Context pi4j, int numLeds, double brightness, int channel) { if (numLeds < 1 || brightness < 0 || brightness > 1 || channel < 0 || channel > 1) { throw new IllegalArgumentException("Illegal Constructor"); } System.out.println("initialising a ledStrip with " + numLeds + " leds"); this.numLeds = numLeds; this.leds = new int[numLeds]; this.brightness = brightness; this.context = pi4j; this.spi = pi4j.create(buildSpiConfig(pi4j, channel, FREQUENCY)); // The raw bytes that get sent to the ledStrip // 3 Color channels per led, at 8 bytes each, with 2 reset bytes pixelRaw = new byte[(3 * numLeds * 8) + 2]; // 1.25us per bit (1250ns) renderWaitTime = numLeds * 3L * 8L * 1250L + LED_RESET_WAIT_TIME; } /** * Builds a new SPI instance for the LED matrix * * @param pi4j * Pi4J context * * @return SPI instance */ private SpiConfig buildSpiConfig(Context pi4j, int channel, int frequency) { return Spi.newConfigBuilder(pi4j) .id("SPI" + 1) .name("LED Matrix") .address(channel) .mode(SpiMode.MODE_0) .baud(8 * frequency) //bitbanging from Bit to SPI-Byte .build(); } /** * Setting all LEDS off and closing the strip */ public void close() { System.out.println("Turning all leds off before close"); allOff(); } /** * function to get the amount of the leds on the strip * * @return int with the amount of pixels */ public int getNumPixels() { return numLeds; } /** * function to get the color (as an int) of a specified led * * @param pixel * which position on the ledStrip, range 0 - numLEDS-1 * * @return the color of the specified led on the strip */ public int getPixelColor(int pixel) { return leds[pixel]; } /** * setting the color of a specified led on the strip * * @param pixel * which position on the strip, range 0 - numLEDS-1 * @param color * the color that is set */ public void setPixelColor(int pixel, int color) { leds[pixel] = color; } /** * Setting all leds to the same color * * @param color * the color that is set */ public void setStripColor(int color) { Arrays.fill(leds, color); } /** * Pixels are sent as follows: - The first transmitted pixel is the pixel closest to the transmitter. - The most * significant bit is always sent first. *

* g7,g6,g5,g4,g3,g2,g1,g0,r7,r6,r5,r4,r3,r2,r1,r0,b7,b6,b5,b4,b3,b2,b1,b0 * \_____________________________________________________________________/ | _________________... | / * __________________... | / / ___________________... | / / / GRB,GRB,GRB,GRB,... */ public void render() { //beginning at 1, because the first byte is a reset int counter = 1; for (int i = 0; i < numLeds; i++) { //Scaling the color to the max brightness leds[i] = PixelColor.setRedComponent(leds[i], (int) (PixelColor.getRedComponent(leds[i]) * brightness)); leds[i] = PixelColor.setGreenComponent(leds[i], (int) (PixelColor.getGreenComponent(leds[i]) * brightness)); leds[i] = PixelColor.setBlueComponent(leds[i], (int) (PixelColor.getBlueComponent(leds[i]) * brightness)); // Calculating GRB from RGB for (int j = 15; j >= 8; j--) { if (((leds[i] >> j) & 1) == 1) { pixelRaw[counter++] = Bit_1; } else { pixelRaw[counter++] = Bit_0; } } for (int j = 23; j >= 16; j--) { if (((leds[i] >> j) & 1) == 1) { pixelRaw[counter++] = Bit_1; } else { pixelRaw[counter++] = Bit_0; } } for (int j = 7; j >= 0; j--) { if (((leds[i] >> j) & 1) == 1) { pixelRaw[counter++] = Bit_1; } else { pixelRaw[counter++] = Bit_0; } } } // While bitbanging, the first and last byte have to be a reset pixelRaw[0] = Bit_Reset; pixelRaw[pixelRaw.length - 1] = Bit_Reset; // waiting since last render time long diff = Math.abs(System.nanoTime()) - lastRenderTime; if (renderWaitTime - diff > 0) { long wait = renderWaitTime - diff; long millis = wait / 1_000_000L; int nanos = (int) (wait % 1_000_000); System.out.println("Waiting " + (millis) + "ms and " + nanos + "ns"); sleep(millis, nanos); } //writing on the PIN spi.write(pixelRaw); System.out.println("finished rendering"); lastRenderTime = Math.abs(System.nanoTime()); } /** * setting all LEDs off */ public void allOff() { Arrays.fill(leds, 0); render(); } /** * Utility function to sleep for the specified amount of milliseconds. An {@link InterruptedException} will be * caught and ignored while setting the interrupt flag again. */ protected void sleep(long millis, int nanos) { try { Thread.sleep(millis, nanos); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * @return the current brightness */ public double getBrightness() { return this.brightness; } /** * Set the brightness of all LEDs * * @param brightness * new max. brightness, range 0 - 1 */ public void setBrightness(double brightness) { if (brightness < 0 || brightness > 1) { throw new IllegalArgumentException("Illegal Brightness Value. Must be between 0 and 1"); } this.brightness = brightness; } public class PixelColor { public static final int WHITE = 0xFFFFFF; public static final int RED = 0xFF0000; public static final int ORANGE = 0xFFA500; public static final int YELLOW = 0xFFFF00; public static final int GREEN = 0x00FF00; public static final int LIGHT_BLUE = 0xadd8e6; public static final int BLUE = 0x0000FF; public static final int PURPLE = 0x800080; public static final int PINK = 0xFFC0CB; public static final int Color_COMPONENT_MAX = 0xff; private static final int WHITE_MASK = 0xffffff; private static final int RED_MASK = 0xff0000; private static final int GREEN_MASK = 0x00ff00; private static final int BLUE_MASK = 0x0000ff; private static final int RED_OFF_MASK = 0x00ffff; private static final int GREEN_OFF_MASK = 0xff00ff; private static final int BLUE_OFF_MASK = 0xffff00; /** * validate if the color channel is in a valid range * * @param color * the color which is to check * @param value * the color channel value */ public static void validateColorComponent(String color, int value) { if (value < 0 || value >= 256) { throw new IllegalArgumentException( "Illegal Color value (" + value + ") for '" + color + "' - must be 0.." + Color_COMPONENT_MAX); } } /** * Get the red value of a color * * @param color * provide the color * * @return the red value */ public static int getRedComponent(int color) { return (color & RED_MASK) >> 16; } /** * Set the red value of a color * * @param color * provide the color * @param red * provide the desired red value * * @return the new color */ public static int setRedComponent(final int color, int red) { validateColorComponent("Red", red); int new_Color = color & RED_OFF_MASK; new_Color |= red << 16; return new_Color; } /** * Get the green value of a color * * @param color * provide the color * * @return the green value */ public static int getGreenComponent(int color) { return (color & GREEN_MASK) >> 8; } /** * Set the green value of a color * * @param color * provide the color * @param green * provide the desired red value * * @return the new color */ public static int setGreenComponent(final int color, int green) { validateColorComponent("Green", green); int new_Color = color & GREEN_OFF_MASK; new_Color |= green << 8; return new_Color; } /** * Get the blue value of a color * * @param color * provide the color * * @return the blue value */ public static int getBlueComponent(int color) { return color & BLUE_MASK; } /** * Set the blue value of a color * * @param color * provide the color * @param blue * provide the desired red value * * @return the new color */ public static int setBlueComponent(final int color, int blue) { validateColorComponent("Blue", blue); int new_Color = color & BLUE_OFF_MASK; new_Color |= blue; return new_Color; } } }