B4J Question PI4J V2 Wrapper

max123

Well-Known Member
Licensed User
Longtime User
Hi all,

I see that PI4J V2 now have only the DigitalInput and DigitalOutput classes exposed,
so starting from B4J opensource code on github I searched to implement some other not exposed classes.

I want to implement some others interfaces like SPI, I2C and PWM.

I started to implement SPI interface, implemented SPI.Initialize and SPI.write, but I'm not sure if the code is correct, on Eclipse there are no errors, but actually
I don't have one Raspberry PI to test with some external SPI devices.

On PI4J Documentation I've foud it is powerful, just implement these classes in B4J permits to develop JavaFX applications (and libraries) to interact with external devices, IOExpanders, ADC, TFT, OLEDS, Shift Registers 74HC595 and more.

I'm an'electronic maker and these days I still experiment read a caliper over SPI and show readings on a TFT screen, I actually use a microcontroller, like ESP8266 and ESP32, but this can be done as well by a GUI app on a Raspberry that read the caliper from SPI bus. Note that in both cases need a voltage converter 3.3v-1.5v on SPI bus lines.

This is just an example, but you can do some sophisticated things. Using PWM DC motors and Servos can be controlled by code or by user interface in realtime.

Is someone interested to help to expand it and have Java acknowledge to help expand this library ?
I wrote some B4J and B4A libraries in java on Eclipse, but I'm not an expert.

The community that may want this need your help, and this add another good motivation to use B4J as well.

Here is my last java code with original code and an attempt to implement SPI output.
I even post two links where some users use PI4J V2 to control something over SPI.
https://github.com/Pi4J/pi4j-v2/discussions/167
https://pi4j.com/documentation/io-examples/spi/
Java:
package com.pi4j.b4j;

import com.pi4j.Pi4J;
import com.pi4j.io.gpio.digital.DigitalInput;
import com.pi4j.io.gpio.digital.DigitalInputConfigBuilder;
import com.pi4j.io.gpio.digital.DigitalOutput;
import com.pi4j.io.gpio.digital.DigitalOutputConfigBuilder;
import com.pi4j.io.gpio.digital.DigitalState;
import com.pi4j.io.gpio.digital.DigitalStateChangeEvent;
import com.pi4j.io.gpio.digital.DigitalStateChangeListener;
import com.pi4j.io.gpio.digital.PullResistance;

import com.pi4j.context.Context;

import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiConfig;
//import com.pi4j.io.spi.SpiConfigBuilder;
import com.pi4j.library.pigpio.PiGpio;

import com.pi4j.io.i2c.I2C;
import com.pi4j.io.i2c.I2CConfig;

//import com.pi4j.plugin.pigpio.provider.gpio.digital.PiGpioDigitalInputProvider;
//import com.pi4j.plugin.pigpio.provider.gpio.digital.PiGpioDigitalOutputProvider;
//import com.pi4j.plugin.pigpio.provider.i2c.PiGpioI2CProvider;
//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 java.io.IOException;
import java.io.InputStream;

import anywheresoftware.b4a.AbsObjectWrapper;
import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.DependsOn;
import anywheresoftware.b4a.BA.Events;
import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.BA.Version;

@Version(1.1f)
@ShortName("Pi4J2")
@DependsOn(values= {"pi4j-plugin-raspberrypi-2.1.0", "pi4j-core-2.1.0", "slf4j-api-1.7.30", "slf4j-jdk14-1.7.25",
        "pi4j-plugin-pigpio-2.1.0", "pi4j-library-pigpio"})

public class Pi4JWrapper extends AbsObjectWrapper<Context>{
 
    protected static Spi spi = null;
 
    public void Initialize(String EventName) {
        setObject((Context) Pi4J.newAutoContext()); // Added Context cast
    }
 
    @ShortName("DigitalInput")
    @Events(values = {"StateChange (State As Boolean)"})
    public static class DigitalInputWrapper extends AbsObjectWrapper<DigitalInput> {
        /**
         * Initializes the DigitalInput.
         *Pi4J - Pi4J object.
         *EventName - Sets the sub that will handle the StateChange event.
         *Pin - Pin BCM address.
         *PullResistance - OFF, PULL_UP or PULL_DOWN
         *DebounceMicroSeconds - Debounce duration in microseconds.
         */
        public void Initialize(BA ba, Pi4JWrapper Pi4J, String EventName, int Pin, String PullResistance, long DebounceMicroSeconds) {
            DigitalInputConfigBuilder builder = DigitalInput.newConfigBuilder((com.pi4j.context.Context) Pi4J.getObject());// Added Context cast
            builder.address(Pin);
            builder.debounce(DebounceMicroSeconds).pull(Enum.valueOf(PullResistance.class, PullResistance)).provider("pigpio-digital-input");
            Intiailize2(ba, Pi4J, EventName, builder);
        }
        public void Intiailize2(BA ba, Pi4JWrapper Pi4J, String EventName, DigitalInputConfigBuilder ConfigurationBuilder) {
            final DigitalInput di = ((com.pi4j.context.Context) Pi4J.getObject()).create(ConfigurationBuilder);  // Added (com.pi4j.context.Context) cast
            setObject(di);
            final String eventName = EventName.toLowerCase(BA.cul);
            getObject().addListener(new DigitalStateChangeListener() {
    
                @Override
                public void onDigitalStateChange(@SuppressWarnings("rawtypes") DigitalStateChangeEvent event) {
                    ba.raiseEventFromDifferentThread(di, null, 0, eventName + "_statechange", false, new Object[] {
                            event.state() == DigitalState.HIGH});
                }
            });
        }
        /**
         * Gets the current pin state.
         */
        public boolean getState() {
            return getObject().state() == DigitalState.HIGH;
        }
    }
 
    @ShortName("DigitalOutput")
    public static class DigitalOutputWrapper extends AbsObjectWrapper<DigitalOutput> {
        /**
         * Initializes the DigitalOutput.
         *Pi4J - Pi4J object.
         *Pin - Pin BCM address.
         */
        public void Initialize(BA ba, Pi4JWrapper Pi4J, int Pin) {
 
            DigitalOutputConfigBuilder buttonConfig = DigitalOutput.newConfigBuilder((com.pi4j.context.Context) Pi4J.getObject())  // added cast
              .address(Pin)
              .shutdown(DigitalState.LOW)
              .initial(DigitalState.LOW)
              .provider("pigpio-digital-output");
 
            setObject(((com.pi4j.context.Context)Pi4J.getObject()).create(buttonConfig));  // Added (com.pi4j.context.Context) cast
        }
        /**
         * Gets or sets Sets the pin state.
         */
        public void setState(boolean s) {
            getObject().setState(s);
        }
        public boolean getState() {
            return getObject().state() == DigitalState.HIGH;
        }
    }
 
    @ShortName("SPI")
    @Events(values = {"DataReceived (Data() As Byte)"})
    public static class SPIInterfaceWrapper extends AbsObjectWrapper<Spi> {
        /**
         * Initializes the SPI port.
         *Pi4J - Pi4J object.
         *EventName - Sets the sub that will handle the DataReceived event.
         *Channel - The bus channel (eg. 0-1).
         *Baud - The bus speed in Hz
         *
         *'Example code
         *<code>
         *Dim pi4j As Pi4J2
         *Dim SPI As SPI
         *Dim Channel As Int = 0
         *Dim BaudRate As Int = 8000000
         *SPI.Initialize(pi4j, "SPI", Channel, BaudRate)
         *Dim Bytes = "This is a first SPI test with Raspberry SPI".getBytes("UTF-8")
         *SPI.write(Bytes & CRLF)</code>
         */
        public void Initialize(BA ba, Pi4JWrapper Pi4J, String EventName, int channel, int baud) {
 
             final PiGpio piGpio = PiGpio.newNativeInstance();
//                 com.pi4j.context.Context builder = com.pi4j.Pi4J.newContextBuilder()
//                            .noAutoDetect()
//                            .add(
//                                PiGpioDigitalInputProvider.newInstance(piGpio),
//                                PiGpioDigitalOutputProvider.newInstance(piGpio),
//                                PiGpioPwmProvider.newInstance(piGpio),
//                                PiGpioI2CProvider.newInstance(piGpio),
//                                PiGpioSerialProvider.newInstance(piGpio),
//                                PiGpioSpiProvider.newInstance(piGpio)
//                            )
//                            .build();
          
                 com.pi4j.context.Context builder = com.pi4j.Pi4J.newContextBuilder()
                            .noAutoDetect()
                            .add(
                                PiGpioSpiProvider.newInstance(piGpio)
                            )
                            .build();
    
                //Max7219 neu = new Max7219(builder.create(buildSpiConfig(builder, 0, 8000000)));
    
                //spi = builder.create(buildSpiConfig((Context) builder, 0, 8000000)));
                spi = builder.create(buildSpiConfig((Context) Pi4J.getObject(), channel, baud));
                System.out.println("SPI Configured");
    
                //setObject(((com.pi4j.context.Context)Pi4J.getObject()).create(buildSpiConfig((Context) Pi4J.getObject(), channel, baud)));
                setObject(spi);

                //byte bytes[] = {1, 2, 3, 4, 5, 6};
                //spi.write(bytes);           
            }
 
            private static SpiConfig  buildSpiConfig(Context pi4j, int channel, int baud) {
               return Spi.newConfigBuilder((com.pi4j.context.Context) pi4j)
                  .id("SPI" + channel)
                  .name("Matrix")
                  .address(channel)
                  .baud(baud)
                  .build();
           }
 
            public void write(byte data) {
                spi.write(data);
            }
 
            public void write2(char c) {
                spi.write(c);
            }
 
            public void write3(byte command, byte data) {
                spi.write(command, data);
            }
 
            public void write4(byte Data[]) {
                spi.write(Data);
            }
 
            public void write5(byte Data[], int Length) {
                spi.write(Data, Length);
            }
 
            public void write6(char c[], int Length) {
                spi.write(c, Length);
            }
 
            public void write7(InputStream is, int Length) {
                spi.write(is);
            }
 
    }
 
    @ShortName("I2C")
    @Events(values = {"DataReceived (Data() As Byte)"})
    public static class I2CInterfaceWrapper extends AbsObjectWrapper<I2C> {
 
    }
 
//    /**
//     * Override default providers which would otherwise be inherited from {@link RaspberryPiPlatform} with an empty list.
//     * This is required for manually controlling which providers are being loaded as part of {@link #buildNewContext()}.
//     *
//     * @return Empty provider list
//     */
//    protected String[] getProviders() {
//        return new String[]{};
//    }
 
}
 
Last edited:

MicroDrie

Well-Known Member
Licensed User
Longtime User
I certainly wouldn't call myself a Java and Eclipse expert, but I don't understand a few things. I think you make a wrapper to get access to the Java code in a jar file. If you make a Java program for B4X, you can refer to that Java code in the jar file. The advantage of a wrapper is that if there is a new release, you only have to recompile with the new release jar.

PI4J V2 is open source so you could also provide the software source code with the B4X interface routines and give all classes a shortname to prevent the error message about this. The disadvantage of this approach is that with a new release you have to implement all changes manually.

I want to implement some others interfaces like SPI, I2C and PWM.
Since PI4J V2 source code is on Github, I see that the source code there already contains IC2, SPI and PWM sub directory with source code.

I have no Raspberry knowledge but what is your specific challenge because I understand the term 'exposed' exactly?
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Hi @MicroDrie , just I wanted to experiment after I was able to control Raspberry GPIOs from JavaFX apps (UI), activate some of them with on screen buttons and even read some others and show the state with images like a lamp on or off.

Using SPI and I2C bus will extend a lots the possibilities because there are a lots of chips (most of them) that use these base protocols, eg TFT, IO expanders, ADC inputs, servo controllers, sensors and more, and this (expecially SPI that is fast) permits to comunicate with them from desktop side, with or without UI.

I started some time ago to write a java wrapper because it is a best option I think, other options would be use JavaObject or use directly Python with a shell.
 
Last edited:
Upvote 0

MicroDrie

Well-Known Member
Licensed User
Longtime User
Hi @MicroDrie , just I wanted to experiment after I was able to control Raspberry GPIOs from JavaFX apps (UI), activate some of them with on screen buttons and even read some others and show the state with images like a lamp on or off.

Using SPI and I2C bus will extend a lots the possibilities because there are a lots of chips (most of them) that use these base protocols, eg TFT, IO expaners and more, and this (expecially SPI that is fast) permits comunicate with them from desktop side, with or without UI.

I started some time ago to write a java wrapper because it is a best option I think, other options would be use JavaObject or use directly Python with a shell.
I have no Raspberry programming experience, however looking to the Java source at github as I wrote before, there are two additional Java SPI and I2C classes. I ques it shot be working when addressed on the right way.
 
Upvote 0
Top