Skip to content

Commit

Permalink
[Major] I2C extensions, fixed and cleanup
Browse files Browse the repository at this point in the history
Added a I2C.execute(Callable<T>) method, allowing to atomically execute multiple I2C calls in a thread safe way

Added I2C.writeRead(byte[], byte[]) methods to atomically perform a write and then immediately afterwards a read on the I2C bus.

Fixed an issue where the LinuxFsI2CBus was closed when closing an I2C device - This was wrong, as another device might still be open on the same bus, and an operation on the underlying RandomAccessFile would lead to exceptions

Fixed the workaround requiring an I2C.read() on a newly created LinuxFsI2C device, if the first call was an ioctl - The device was not selected prior to the ioctl call.

Additional code cleanup, more to come in an additional commit

diff --git a/pi4j-core/src/main/java/com/pi4j/common/Action.java b/pi4j-core/src/main/java/com/pi4j/common/Action.java
deleted file mode 100644
index 06c6905a..00000000
--- a/pi4j-core/src/main/java/com/pi4j/common/Action.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.pi4j.common;
-
-public interface Action {
-
-    void execute() throws Exception;
-}
diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/CheckedFunction.java b/pi4j-core/src/main/java/com/pi4j/common/CheckedFunction.java
similarity index 67%
rename from plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/CheckedFunction.java
rename to pi4j-core/src/main/java/com/pi4j/common/CheckedFunction.java
index 60f31150..f70747a2 100644
--- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/CheckedFunction.java
+++ b/pi4j-core/src/main/java/com/pi4j/common/CheckedFunction.java
@@ -1,4 +1,4 @@
-package com.pi4j.plugin.linuxfs.provider.i2c;
+package com.pi4j.common;

 @FunctionalInterface
 public interface CheckedFunction<T, R> {
diff --git a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java
index bfb64f9c..b3415836 100644
--- a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java
+++ b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java
@@ -25,27 +25,24 @@ package com.pi4j.io.i2c;
  * #L%
  */

-import com.pi4j.common.Action;
 import com.pi4j.context.Context;
 import com.pi4j.io.IO;
 import com.pi4j.io.IODataReader;
 import com.pi4j.io.IODataWriter;

+import java.util.concurrent.Callable;
+
 /**
  * I2C I/O Interface for Pi4J I2C Bus/Device Communications
  *
  * @author Robert Savage
- *
- * Based on previous contributions from:
- *        Daniel Sendula,
- *        <a href="http://raspelikan.blogspot.co.at">RasPelikan</a>
+ * <p>
+ * Based on previous contributions from: Daniel Sendula,
+ * <a href="http://raspelikan.blogspot.co.at">RasPelikan</a>
  * @version $Id: $Id
  */
-public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
-        IODataWriter,
-        IODataReader,
-        I2CRegisterDataReaderWriter,
-        AutoCloseable {
+public interface I2C
+    extends IO<I2C, I2CConfig, I2CProvider>, IODataWriter, IODataReader, I2CRegisterDataReaderWriter, AutoCloseable {

     /**
      * <p>close.</p>
@@ -58,9 +55,10 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
      * <p>newConfigBuilder.</p>
      *
      * @param context {@link Context}
+     *
      * @return a {@link com.pi4j.io.i2c.I2CConfigBuilder} object.
      */
-    static I2CConfigBuilder newConfigBuilder(Context context){
+    static I2CConfigBuilder newConfigBuilder(Context context) {
         return I2CConfigBuilder.newInstance(context);
     }

@@ -69,7 +67,7 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
      *
      * @return The I2C device address for which this instance is constructed for.
      */
-    default int device(){
+    default int device() {
         return config().device();
     }

@@ -78,7 +76,7 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
      *
      * @return The I2C bus address for which this instance is constructed for.
      */
-    default int bus(){
+    default int bus() {
         return config().bus();
     }

@@ -94,7 +92,7 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
      *
      * @return The I2C bus address for which this instance is constructed for.
      */
-    default int getBus(){
+    default int getBus() {
         return bus();
     }

@@ -103,33 +101,69 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
      *
      * @return The I2C device address for which this instance is constructed for.
      */
-    default int getDevice(){
+    default int getDevice() {
         return device();
     }

+    /**
+     * Method to perform a write of the given buffer, and then a read into the given buffer
+     *
+     * @param writeBuffer the buffer to write
+     * @param readBuffer  the buffer to read into
+     *
+     * @return the number of bytes read
+     */
+    default int writeRead(byte[] writeBuffer, byte[] readBuffer) {
+        return writeRead(writeBuffer, writeBuffer.length, 0, readBuffer, readBuffer.length, 0);
+    }
+
+    /**
+     * Method to perform a write of the given buffer, and then a read into the given buffer
+     *
+     * @param writeSize   the number of bytes to write
+     * @param writeOffset the offset of the array to write
+     * @param writeBuffer the buffer to write respecting the given length and offset
+     * @param readSize    the number of bytes to read
+     * @param readOffset  the offset in the read buffer at which to insert the read bytes
+     * @param readBuffer  the buffer into which to read the bytes
+     *
+     * @return the number of bytes read
+     */
+    default int writeRead(byte[] writeBuffer, int writeSize, int writeOffset, byte[] readBuffer, int readSize,
+        int readOffset) {
+        return execute(() -> {
+            int written = write(writeBuffer, writeOffset, writeSize);
+            if (written != writeOffset)
+                throw new IllegalStateException(
+                    "Expected to write " + writeOffset + " bytes but only wrote " + written + " bytes");
+            return read(readBuffer, readOffset, readSize);
+        });
+    }
+
     /**
      * Get an encapsulated interface for reading and writing to a specific I2C device register
      *
      * @param address a int.
+     *
      * @return a {@link com.pi4j.io.i2c.I2CRegister} object.
      */
     I2CRegister getRegister(int address);

     /**
-     * I2C Device Register
-     * Get an encapsulated interface for reading and writing to a specific I2C device register
+     * I2C Device Register Get an encapsulated interface for reading and writing to a specific I2C device register
      *
      * @param address the (16-bit) device register address
+     *
      * @return an instance of I2CRegister for the provided register address
      */
-    default I2CRegister register(int address){
+    default I2CRegister register(int address) {
         return getRegister(address);
     }

     /**
      * Executes the given runnable on the I2C bus, locking the bus for the duration of the given task
      *
-     * @param action the action to perform
+     * @param action the action to perform, returning a value
      */
-    void execute(Action action);
+    <T> T execute(Callable<T> action);
 }
diff --git a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBase.java b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBase.java
index eb977f6f..b480c4cd 100644
--- a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBase.java
+++ b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBase.java
@@ -30,34 +30,43 @@ import com.pi4j.exception.ShutdownException;
 import com.pi4j.io.IOBase;
 import com.pi4j.io.i2c.impl.DefaultI2CRegister;

+import java.util.concurrent.Callable;
+
 /**
  * <p>Abstract I2CBase class.</p>
  *
  * @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
  * @version $Id: $Id
  */
-public abstract class I2CBase extends IOBase<I2C, I2CConfig, I2CProvider> implements I2C {
+public abstract class I2CBase<T extends I2CBus> extends IOBase<I2C, I2CConfig, I2CProvider> implements I2C {

-    protected boolean isOpen = false;
+    protected boolean isOpen;
+    protected final T i2CBus;

     /**
      * <p>Constructor for I2CBase.</p>
      *
-     * @param provider a {@link com.pi4j.io.i2c.I2CProvider} object.
-     * @param config a {@link com.pi4j.io.i2c.I2CConfig} object.
+     * @param provider a {@link I2CProvider} object.
+     * @param config   a {@link I2CConfig} object.
+     * @param i2CBus   a {@link I2CBus} object.
      */
-    public I2CBase(I2CProvider provider, I2CConfig config) {
+    public I2CBase(I2CProvider provider, I2CConfig config, T i2CBus) {
         super(provider, config);
         this.isOpen = true;
+        this.i2CBus = i2CBus;
     }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public boolean isOpen() {
         return this.isOpen;
     }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void close() {
         this.isOpen = false;
@@ -65,24 +74,33 @@ public abstract class I2CBase extends IOBase<I2C, I2CConfig, I2CProvider> implem

     /**
      * {@inheritDoc}
-     *
+     * <p>
      * Get an encapsulated interface for reading and writing to a specific I2C device register
      */
-    public I2CRegister getRegister(int address){
+    public I2CRegister getRegister(int address) {
         return new DefaultI2CRegister(this, address);
     }

-    /** {@inheritDoc} */
+    @Override
+    public <V> V execute(Callable<V> action) {
+        if (action == null)
+            throw new NullPointerException("Parameter 'action' is mandatory!");
+        return this.i2CBus.execute(this, action);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public I2C shutdown(Context context) throws ShutdownException {
         // if this I2C device is still open, then we need to close it since we are shutting down
-        if(this.isOpen()) {
+        if (this.isOpen()) {
             try {
-                this.close();
+                close();
             } catch (Exception e) {
                 throw new ShutdownException(e);
             }
         }
-        return (I2C)this;
+        return this;
     }
 }
diff --git a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBus.java b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBus.java
new file mode 100644
index 00000000..1052c0ad
--- /dev/null
+++ b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBus.java
@@ -0,0 +1,22 @@
+package com.pi4j.io.i2c;
+
+import java.util.concurrent.Callable;
+
+/**
+ * This interface defines method to be performed on an I2C bus. Most importantly the {@link #execute(I2C, Callable)}
+ * allows to perform bulk operations on the bus in a thread safe manner.
+ */
+public interface I2CBus {
+
+    /**
+     * Executes the given action, which typically performs multiple I2C reads and/or writes on the I2C bus in a thread
+     * safe manner, i.e. the bus is blocked till the action is completed.
+     *
+     * @param i2c    the device for which to perform the action
+     * @param action the action to perform
+     * @param <R>    the result type of the action, if any
+     *
+     * @return the result of the action
+     */
+    <R> R execute(I2C i2c, Callable<R> action);
+}
diff --git a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBusBase.java b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBusBase.java
new file mode 100644
index 00000000..bd1dbef4
--- /dev/null
+++ b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBusBase.java
@@ -0,0 +1,60 @@
+package com.pi4j.io.i2c;
+
+import com.pi4j.exception.Pi4JException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static java.text.MessageFormat.format;
+
+public abstract class I2CBusBase implements I2CBus {
+
+    private static final Logger logger = LoggerFactory.getLogger(I2CBusBase.class);
+
+    public static final long DEFAULT_LOCK_ACQUIRE_TIMEOUT = 1000;
+    public static final TimeUnit DEFAULT_LOCK_ACQUIRE_TIMEOUT_UNITS = TimeUnit.MILLISECONDS;
+
+    protected final int bus;
+
+    protected final long lockAquireTimeout;
+    protected final TimeUnit lockAquireTimeoutUnit;
+    private final ReentrantLock lock = new ReentrantLock(true);
+
+    public I2CBusBase(I2CConfig config) {
+        if (config.bus() == null)
+            throw new IllegalArgumentException("I2C bus must be specified");
+
+        this.bus = config.getBus();
+
+        this.lockAquireTimeout = DEFAULT_LOCK_ACQUIRE_TIMEOUT;
+        this.lockAquireTimeoutUnit = DEFAULT_LOCK_ACQUIRE_TIMEOUT_UNITS;
+    }
+
+    protected <R> R _execute(I2C i2c, Callable<R> action) {
+        if (i2c == null)
+            throw new NullPointerException("Parameter 'i2c' is mandatory!");
+        if (action == null)
+            throw new NullPointerException("Parameter 'action' is mandatory!");
+        try {
+            if (this.lock.tryLock() || this.lock.tryLock(this.lockAquireTimeout, this.lockAquireTimeoutUnit)) {
+                try {
+                    return action.call();
+                } finally {
+                    this.lock.unlock();
+                }
+            } else {
+                throw new Pi4JException(
+                    format("Failed to get I2C lock on bus {0} after {1} {2}", this.bus, this.lockAquireTimeout,
+                        this.lockAquireTimeoutUnit));
+            }
+        } catch (InterruptedException e) {
+            logger.error("Failed locking {}-{}", getClass().getSimpleName(), this.bus, e);
+            throw new RuntimeException("Could not obtain an access-lock!", e);
+        } catch (Exception e) {
+            throw new Pi4JException("Failed to execute action for device " + i2c.device() + " on bus " + this.bus, e);
+        }
+    }
+}
diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2C.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2C.java
index b85a5e83..54b4b6e9 100644
--- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2C.java
+++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2C.java
@@ -40,7 +40,7 @@ import java.util.Objects;
  * @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
  * @version $Id: $Id
  */
-public class LinuxFsI2C extends I2CBase implements I2C {
+public class LinuxFsI2C extends I2CBase<LinuxFsI2CBus> implements I2C {

     private final LinuxFsI2CBus i2CBus;

@@ -53,7 +53,7 @@ public class LinuxFsI2C extends I2CBase implements I2C {
      *     a {@link I2CConfig} object.
      */
     public LinuxFsI2C(LinuxFsI2CBus i2CBus, I2CProvider provider, I2CConfig config) {
-        super(provider, config);
+        super(provider, config, i2CBus);
         this.i2CBus = i2CBus;
     }

@@ -332,19 +332,4 @@ public class LinuxFsI2C extends I2CBase implements I2C {
         return word;

     }
-
-    @Override
-    public void execute(Action action) {
-        this.i2CBus.execute(this, file -> {
-            action.execute();
-            return null;
-        });
-    }
-
-    @Override
-    public void close() {
-        if (this.i2CBus != null)
-            this.i2CBus.close();
-        super.close();
-    }
 }
diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CBus.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CBus.java
index a6d18651..48124cef 100644
--- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CBus.java
+++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CBus.java
@@ -1,24 +1,21 @@
 package com.pi4j.plugin.linuxfs.provider.i2c;

-import java.io.File;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-
+import com.pi4j.common.CheckedFunction;
 import com.pi4j.exception.Pi4JException;
 import com.pi4j.io.i2c.I2C;
+import com.pi4j.io.i2c.I2CBusBase;
 import com.pi4j.io.i2c.I2CConfig;
 import com.pi4j.library.linuxfs.LinuxFile;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

-public class LinuxFsI2CBus {
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.concurrent.Callable;

-    public static final long DEFAULT_LOCK_ACQUIRE_TIMEOUT = 1000;
-    public static final TimeUnit DEFAULT_LOCK_ACQUIRE_TIMEOUT_UNITS = TimeUnit.MILLISECONDS;
-    private final Integer bus;
+public class LinuxFsI2CBus extends I2CBusBase {

     protected Logger logger = LoggerFactory.getLogger(this.getClass());

@@ -28,13 +25,9 @@ public class LinuxFsI2CBus {
     protected LinuxFile file;
     private int lastAddress;

-    protected long lockAquireTimeout;
-    protected TimeUnit lockAquireTimeoutUnit;
-    private final ReentrantLock lock = new ReentrantLock(true);
-
     public LinuxFsI2CBus(I2CConfig config) {
+        super(config);

-        this.bus = config.getBus();
         final File sysfs = new File("/sys/bus/i2c/devices/i2c-" + this.bus);
         if (!sysfs.exists() || !sysfs.isDirectory())
             throw new Pi4JException("I2C bus " + this.bus + " does not exist.");
@@ -49,16 +42,63 @@ public class LinuxFsI2CBus {
         } catch (IOException e) {
             throw new Pi4JException(e);
         }
+    }

-        this.lockAquireTimeout = DEFAULT_LOCK_ACQUIRE_TIMEOUT;
-        this.lockAquireTimeoutUnit = DEFAULT_LOCK_ACQUIRE_TIMEOUT_UNITS;
+    @Override
+    public <R> R execute(I2C i2c, Callable<R> action) {
+        return _execute(i2c, () -> {
+            try {
+                selectBusSlave(i2c);
+                return action.call();
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new Pi4JException("Failed to execute action for device " + i2c.device() + " on bus " + this.bus,
+                    e);
+            }
+        });
+    }
+
+    public <R> R execute(final I2C i2c, final CheckedFunction<LinuxFile, R> action) {
+        return _execute(i2c, () -> {
+            try {
+                selectBusSlave(i2c);
+                return action.apply(this.file);
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new Pi4JException("Failed to execute action for device " + i2c.device() + " on bus " + this.bus,
+                    e);
+            }
+        });
+    }
+
+    /**
+     * @param i2c     the device to select before performing the ioctl command
+     * @param command From I2CConstants
+     * @param data    values in bytes for all structures, with 4 or 8 byte alignment enforced by filling holes before
+     *                pointers
+     * @param offsets ByteBuffer: offsets of pointer/ byte offset of pointedToData
+     */
+    public void executeIOCTL(final I2C i2c, long command, ByteBuffer data, IntBuffer offsets) {
+        _execute(i2c, () -> {
+            try {
+                selectBusSlave(i2c);
+                this.file.ioctl(command, data, offsets);
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new Pi4JException("Failed to execute ioctl for device " + i2c.device() + " on bus " + this.bus,
+                    e);
+            }
+            return null;
+        });
     }

     /**
      * Selects the slave device if not already selected on this bus. Runs the required ioctl's via JNI.
      *
-     * @param i2c
-     *     Device to select
+     * @param i2c Device to select
      */
     protected void selectBusSlave(I2C i2c) throws IOException {
         if (this.lastAddress == i2c.device())
@@ -68,84 +108,12 @@ public class LinuxFsI2CBus {
         this.file.ioctl(I2CConstants.I2C_SLAVE, i2c.device() & 0xFF);
     }

-    /**
-     *
-     * @param i2c
-     * @param command   From I2CConstants
-     * @param data  values in bytes for all structures, with 4 or 8 byte alignment enforced by filling holes before pointers
-     * @param offsets   ByteBuffer: offsets of pointer/ byte offset of pointedToData
-     *
-     * @return    0 if success, else -1
-     */
-    public int executeIOCTL(final I2C i2c, long command, ByteBuffer data, IntBuffer offsets){
-        int rc = -1;
-        if (this.lastAddress != i2c.device()) {
-            this.lastAddress = i2c.device();
-        }
-        try {
-            if (this.lock.tryLock() || this.lock.tryLock(this.lockAquireTimeout, this.lockAquireTimeoutUnit)) {
-
-                try {
-                    selectBusSlave(i2c);
-                    this.file.ioctl( command, data, offsets);
-                    rc = 0; //had there been any failure an exception would bypass this statement
-                    } finally {
-                    while (this.lock.isHeldByCurrentThread())
-                        this.lock.unlock();
-                }
-
-            } else {
-                throw new Pi4JException(
-                    "Failed to get I2C lock on bus " + this.bus + " after " + this.lockAquireTimeout + " "
-                        + this.lockAquireTimeoutUnit);
-            }
-        } catch (InterruptedException e) {
-            logger.error("Failed locking " + getClass().getSimpleName() + "-" + this.bus, e);
-            throw new RuntimeException("Could not obtain an access-lock!", e);
-        } catch (Exception e) {
-            throw new Pi4JException("Failed to execute action for device " + i2c.device() + " on bus " + this.bus, e);
-        }
-
-        return rc;
-    }
-
-    public <R> R execute(final I2C i2c, final CheckedFunction<LinuxFile, R> action) {
-        if (i2c == null)
-            throw new NullPointerException("Parameter 'i2c' is mandatory!");
-        if (action == null)
-            throw new NullPointerException("Parameter 'action' is mandatory!");
-
-        try {
-            if (this.lock.tryLock() || this.lock.tryLock(this.lockAquireTimeout, this.lockAquireTimeoutUnit)) {
-
-                try {
-                    selectBusSlave(i2c);
-                    return action.apply(this.file);
-                } finally {
-                    while (this.lock.isHeldByCurrentThread())
-                        this.lock.unlock();
-                }
-
-            } else {
-                throw new Pi4JException(
-                    "Failed to get I2C lock on bus " + this.bus + " after " + this.lockAquireTimeout + " "
-                        + this.lockAquireTimeoutUnit);
-            }
-        } catch (InterruptedException e) {
-            logger.error("Failed locking " + getClass().getSimpleName() + "-" + this.bus, e);
-            throw new RuntimeException("Could not obtain an access-lock!", e);
-        } catch (Exception e) {
-            throw new Pi4JException("Failed to execute action for device " + i2c.device() + " on bus " + this.bus, e);
-        }
-    }
-
     public void close() {
         if (this.file != null) {
             try {
                 this.file.close();
             } catch (IOException e) {
-                logger.error(
-                        "Failed to close file " + this.file + " for " + getClass().getSimpleName() + "-" + this.bus, e);
+                logger.error("Failed to close file {} for {}-{}", this.file, getClass().getSimpleName(), this.bus, e);
             }
         }
     }
diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CProviderImpl.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CProviderImpl.java
index d7d7db50..210c74e5 100644
--- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CProviderImpl.java
+++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CProviderImpl.java
@@ -29,8 +29,11 @@ package com.pi4j.plugin.linuxfs.provider.i2c;

 import com.pi4j.boardinfo.util.BoardInfoHelper;
+import com.pi4j.context.Context;
+import com.pi4j.exception.ShutdownException;
 import com.pi4j.io.i2c.I2C;
 import com.pi4j.io.i2c.I2CConfig;
+import com.pi4j.io.i2c.I2CProvider;
 import com.pi4j.io.i2c.I2CProviderBase;

 import java.util.HashMap;
@@ -57,9 +60,14 @@ public class LinuxFsI2CProviderImpl extends I2CProviderBase implements LinuxFsI2
         LinuxFsI2CBus i2CBus = this.i2CBusMap.computeIfAbsent(config.getBus(), busNr -> new LinuxFsI2CBus(config));
         // create new I/O instance based on I/O config
         LinuxFsI2C i2C = new LinuxFsI2C(i2CBus, this, config);
-        // Workaround, needed if first LinuxFsI2C usage is ioctl (readRegister or writeRegister)
-        i2C.read();
         this.context.registry().add(i2C);
         return i2C;
     }
+
+    @Override
+    public I2CProvider shutdown(Context context) throws ShutdownException {
+        this.i2CBusMap.forEach(((busNr, bus) -> bus.close()));
+        this.i2CBusMap.clear();
+        return super.shutdown(context);
+    }
 }
diff --git a/plugins/pi4j-plugin-mock/src/main/java/com/pi4j/plugin/mock/provider/i2c/MockI2C.java b/plugins/pi4j-plugin-mock/src/main/java/com/pi4j/plugin/mock/provider/i2c/MockI2C.java
index 5e3caac6..73fce54b 100644
--- a/plugins/pi4j-plugin-mock/src/main/java/com/pi4j/plugin/mock/provider/i2c/MockI2C.java
+++ b/plugins/pi4j-plugin-mock/src/main/java/com/pi4j/plugin/mock/provider/i2c/MockI2C.java
@@ -44,7 +44,7 @@ import java.util.Objects;
  * @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
  * @version $Id: $Id
  */
-public class MockI2C extends I2CBase implements I2C, I2CRegisterDataReader, I2CRegisterDataWriter {
+public class MockI2C extends I2CBase<MockI2CBus> implements I2C, I2CRegisterDataReader, I2CRegisterDataWriter {

     private static final Logger logger = LoggerFactory.getLogger(MockI2C.class);

@@ -58,7 +58,6 @@ public class MockI2C extends I2CBase implements I2C, I2CRegisterDataReader, I2CR
     protected ArrayDeque<Byte>[] registers = new ArrayDeque[512]; // 512 supported registers (0-511)
     protected ArrayDeque<Byte> raw = new ArrayDeque<>();

-
     /**
      * <p>Constructor for MockI2C.</p>
      *
@@ -66,12 +65,12 @@ public class MockI2C extends I2CBase implements I2C, I2CRegisterDataReader, I2CR
      * @param config a {@link com.pi4j.io.i2c.I2CConfig} object.
      */
     public MockI2C(I2CProvider provider, I2CConfig config){
-        super(provider, config);
+        super(provider, config, new MockI2CBus(config));
         logger.info(" [");
         logger.info(Mock.I2C_PROVIDER_NAME);
         logger.info("::");
         logger.info(this.id);
-        logger.info("] :: CREATE(BUS=" + config.bus() + "; DEVICE=" + config.device() + ")");
+        logger.info("] :: CREATE(BUS={}; DEVICE={})", config.bus(), config.device());

         logger.info("");
     }
@@ -83,7 +82,7 @@ public class MockI2C extends I2CBase implements I2C, I2CRegisterDataReader, I2CR
         logger.info(Mock.I2C_PROVIDER_NAME);
         logger.info("::");
         logger.info(this.id);
-        logger.info("] :: CLOSE(BUS=" + config.bus() + "; DEVICE=" + config.device() + ")");
+        logger.info("] :: CLOSE(BUS={}; DEVICE={})", config.bus(), config.device());
         logger.info("");
         super.close();
     }
@@ -129,8 +128,8 @@ public class MockI2C extends I2CBase implements I2C, I2CRegisterDataReader, I2CR
     @Override
     public int write(Charset charset, CharSequence data) {
         byte[] buffer = data.toString().getBytes(charset);
-        for(int p = 0; p < buffer.length; p++){
-            raw.add(buffer[p]); // add to internal buffer
+        for (byte b : buffer) {
+            raw.add(b); // add to internal buffer
         }
         logger.info(" [");
         logger.info(Mock.I2C_PROVIDER_NAME);
@@ -246,9 +245,9 @@ public class MockI2C extends I2CBase implements I2C, I2CRegisterDataReader, I2CR
         logger.info("::");
         logger.info(this.id);
         logger.info("] :: WRITEREGISTER(");
-        logger.info("Chip register offset Decimal : " + register + "  Hex : " +  String.format("%02X", register));
-        logger.info("offset = " + String.format("%02X", offset));
-        logger.info(",User data:     0x   "+ StringUtil.toHexString(data, offset, length));
+        logger.info("Chip register offset Decimal : {}  Hex : {}", register, String.format("%02X", register));
+        logger.info("offset = {}", String.format("%02X", offset));
+        logger.info(",User data:     0x   {}", StringUtil.toHexString(data, offset, length));
         logger.info(")");
         return length;
     }
@@ -270,9 +269,9 @@ public class MockI2C extends I2CBase implements I2C, I2CRegisterDataReader, I2CR
         logger.info("] :: WRITEREGISTER(");
         logger.info("REG= (two byte offset LSB first)");
         logger.info(StringUtil.toHexString(register, 0, register.length));
-        logger.info("Chip register offset Decimal : " + internalOffset + "  Hex : " +  String.format("%02X", internalOffset));
-        logger.info("offset = " + String.format("%02X", offset));
-        logger.info(",User data:     0x   "+ StringUtil.toHexString(data, offset, length));
+        logger.info("Chip register offset Decimal : {}  Hex : {}", internalOffset, String.format("%02X", internalOffset));
+        logger.info("offset = {}", String.format("%02X", offset));
+        logger.info(",User data:     0x   {}", StringUtil.toHexString(data, offset, length));
         logger.info(")");
         return length;
     }
@@ -285,8 +284,8 @@ public class MockI2C extends I2CBase implements I2C, I2CRegisterDataReader, I2CR

         if(registers[register] == null) registers[register] = new ArrayDeque<Byte>();
         byte[] buffer = data.toString().getBytes(charset);
-        for(int p = 0; p < buffer.length; p++){
-            registers[register].add(buffer[p]); // add to internal buffer
+        for (byte b : buffer) {
+            registers[register].add(b); // add to internal buffer
         }

         logger.info(" [");
@@ -348,8 +347,8 @@ public class MockI2C extends I2CBase implements I2C, I2CRegisterDataReader, I2CR
         logger.info("] :: READREGISTER(");
         logger.info("REG= (two byte offset LSB first)");
         logger.info(StringUtil.toHexString(register, 0, register.length));
-        logger.info("offset = " + String.format("%02X", offset));
-        logger.info("Chip register offset Decimal : " + internalOffset + "  Hex : " +  String.format("%02X", internalOffset));
+        logger.info("offset = {}", String.format("%02X", offset));
+        logger.info("Chip register offset Decimal : {}  Hex : {}", internalOffset, String.format("%02X", internalOffset));
         logger.info(", 0x");
         logger.info(StringUtil.toHexString(buffer, offset, length));
         logger.info(")");
@@ -374,10 +373,10 @@ public class MockI2C extends I2CBase implements I2C, I2CRegisterDataReader, I2CR
         logger.info("::");
         logger.info(this.id);
         logger.info("] :: READREGISTER(");
-        logger.info("offset = " + String.format("%02X", offset));
-        logger.info("Chip register offset Decimal : " + register + "  Hex : " +  String.format("%02X", register));
+        logger.info("offset = {}", String.format("%02X", offset));
+        logger.info("Chip register offset Decimal : {}  Hex : {}", register, String.format("%02X", register));
         logger.info(String.valueOf(register));
-         logger.info(", 0x");
+        logger.info(", 0x");
         logger.info(StringUtil.toHexString(buffer, offset, length));
         logger.info(")");
         return counter;
diff --git a/plugins/pi4j-plugin-mock/src/main/java/com/pi4j/plugin/mock/provider/i2c/MockI2CBus.java b/plugins/pi4j-plugin-mock/src/main/java/com/pi4j/plugin/mock/provider/i2c/MockI2CBus.java
new file mode 100644
index 00000000..993c12ba
--- /dev/null
+++ b/plugins/pi4j-plugin-mock/src/main/java/com/pi4j/plugin/mock/provider/i2c/MockI2CBus.java
@@ -0,0 +1,19 @@
+package com.pi4j.plugin.mock.provider.i2c;
+
+import com.pi4j.io.i2c.I2C;
+import com.pi4j.io.i2c.I2CBusBase;
+import com.pi4j.io.i2c.I2CConfig;
+
+import java.util.concurrent.Callable;
+
+public class MockI2CBus extends I2CBusBase {
+
+    public MockI2CBus(I2CConfig config) {
+        super(config);
+    }
+
+    @Override
+    public <R> R execute(I2C i2c, Callable<R> action) {
+        return _execute(i2c, action);
+    }
+}
diff --git a/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2C.java b/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2C.java
index 8885e386..b7d6d6bf 100644
--- a/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2C.java
+++ b/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2C.java
@@ -27,7 +27,6 @@ package com.pi4j.plugin.pigpio.provider.i2c;
  * #L%
  */

-
 import com.pi4j.context.Context;
 import com.pi4j.exception.InitializeException;
 import com.pi4j.io.i2c.I2C;
@@ -45,7 +44,7 @@ import java.util.Objects;
  * @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
  * @version $Id: $Id
  */
-public class PiGpioI2C extends I2CBase implements I2C {
+public class PiGpioI2C extends I2CBase<PiGpioI2CBus> implements I2C {

     protected final PiGpio piGpio;
     protected final int handle;
@@ -53,18 +52,19 @@ public class PiGpioI2C extends I2CBase implements I2C {
     /**
      * <p>Constructor for PiGpioI2C.</p>
      *
-     * @param piGpio a {@link com.pi4j.library.pigpio.PiGpio} object.
-     * @param provider a {@link com.pi4j.io.i2c.I2CProvider} object.
-     * @param config a {@link com.pi4j.io.i2c.I2CConfig} object.
+     * @param piGpio   a {@link PiGpio} object.
+     * @param i2CBus   a {@link PiGpioI2CBus} object.
+     * @param provider a {@link I2CProvider} object.
+     * @param config   a {@link I2CConfig} object.
      */
-    public PiGpioI2C(PiGpio piGpio, I2CProvider provider, I2CConfig config) {
-        super(provider, config);
+    public PiGpioI2C(PiGpio piGpio, PiGpioI2CBus i2CBus, I2CProvider provider, I2CConfig config) {
+        super(provider, config, i2CBus);

         // set local reference instance
         this.piGpio = piGpio;

         // set pin ALT0 modes for I2C BUS<1> or BUS<2> usage on RPI3B
-        switch(config.bus()) {
+        switch (config.bus()) {
             case 0: {
                 piGpio.gpioSetMode(0, PiGpioMode.ALT0);
                 piGpio.gpioSetMode(1, PiGpioMode.ALT0);
@@ -83,14 +83,18 @@ public class PiGpioI2C extends I2CBase implements I2C {
         this.isOpen = true;
     }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public I2C initialize(Context context) throws InitializeException {
         super.initialize(context);
         return this;
     }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void close() {
         piGpio.i2cClose(this.handle);
@@ -101,13 +105,17 @@ public class PiGpioI2C extends I2CBase implements I2C {
     // RAW DEVICE WRITE FUNCTIONS
     // -------------------------------------------------------------------

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public int write(byte b) {
         return piGpio.i2cWriteByte(this.handle, b);
     }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public int write(byte[] data, int offset, int length) {
         Objects.checkFromIndexSize(offset, length, data.length);
@@ -119,13 +127,17 @@ public class PiGpioI2C extends I2CBase implements I2C {
     // RAW DEVICE READ FUNCTIONS
     // -------------------------------------------------------------------

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public int read() {
         return piGpio.i2cReadByte(this.handle);
     }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public int read(byte[] buffer, int offset, int length) {
         Objects.checkFromIndexSize(offset, length, buffer.length);
@@ -136,13 +148,17 @@ public class PiGpioI2C extends I2CBase implements I2C {
     // DEVICE REGISTER WRITE FUNCTIONS
     // -------------------------------------------------------------------

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public int writeRegister(int register, byte b) {
         return piGpio.i2cWriteByteData(this.handle, register, b);
     }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public int writeRegister(int register, byte[] data, int offset, int length) {
         Objects.checkFromIndexSize(offset, length, data.length);
@@ -150,13 +166,13 @@ public class PiGpioI2C extends I2CBase implements I2C {
         return length;
     }

-    @Override
     /**
      * {@inheritDoc}
      * <p> Note: Function not supported with PIGPIO provider.
      * This method 'is' supported in the LinuxFS provider
      * </p>
      */
+    @Override
     public int writeRegister(byte[] register, byte[] data, int offset, int length) {
         throw new IllegalStateException("Not supported, please use LinuxFS plugin");
     }
@@ -165,31 +181,37 @@ public class PiGpioI2C extends I2CBase implements I2C {
     // DEVICE REGISTER READ FUNCTIONS
     // -------------------------------------------------------------------

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public int readRegister(int register) {
         return piGpio.i2cReadByteData(this.handle, register);
     }

-    @Override
     /**
      * {@inheritDoc}
      * <p> Note: Function not supported with PIGPIO provider.
      * This method 'is' supported in the LinuxFS provider
      * </p>
      */
+    @Override
     public int readRegister(byte[] register, byte[] buffer, int offset, int length) {
         throw new IllegalStateException("Not supported, please use LinuxFS plugin");
-     }
+    }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public int readRegister(int register, byte[] buffer, int offset, int length) {
         Objects.checkFromIndexSize(offset, length, buffer.length);
         return piGpio.i2cReadI2CBlockData(this.handle, register, buffer, offset, length);
     }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public int writeReadRegisterWord(int register, int word) {
         return piGpio.i2cProcessCall(this.handle, register, word);
diff --git a/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2CBus.java b/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2CBus.java
new file mode 100644
index 00000000..3a96a8bc
--- /dev/null
+++ b/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2CBus.java
@@ -0,0 +1,19 @@
+package com.pi4j.plugin.pigpio.provider.i2c;
+
+import com.pi4j.io.i2c.I2C;
+import com.pi4j.io.i2c.I2CBusBase;
+import com.pi4j.io.i2c.I2CConfig;
+
+import java.util.concurrent.Callable;
+
+public class PiGpioI2CBus extends I2CBusBase {
+
+    public PiGpioI2CBus(I2CConfig config) {
+        super(config);
+    }
+
+    @Override
+    public <R> R execute(I2C i2c, Callable<R> action) {
+        return _execute(i2c, action);
+    }
+}
diff --git a/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2CProviderImpl.java b/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2CProviderImpl.java
index 80bfb7c9..47921e40 100644
--- a/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2CProviderImpl.java
+++ b/plugins/pi4j-plugin-pigpio/src/main/java/com/pi4j/plugin/pigpio/provider/i2c/PiGpioI2CProviderImpl.java
@@ -34,6 +34,9 @@ import com.pi4j.io.i2c.I2CConfig;
 import com.pi4j.io.i2c.I2CProviderBase;
 import com.pi4j.library.pigpio.PiGpio;

+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * <p>PiGpioI2CProviderImpl class.</p>
  *
@@ -43,6 +46,7 @@ import com.pi4j.library.pigpio.PiGpio;
 public class PiGpioI2CProviderImpl extends I2CProviderBase implements PiGpioI2CProvider {

     final PiGpio piGpio;
+    private final Map<Integer, PiGpioI2CBus> i2CBusMap;

     /**
      * <p>Constructor for PiGpioI2CProviderImpl.</p>
@@ -53,6 +57,7 @@ public class PiGpioI2CProviderImpl extends I2CProviderBase implements PiGpioI2CP
         this.id = ID;
         this.name = NAME;
         this.piGpio = piGpio;
+        this.i2CBusMap = new HashMap<>();
     }

     @Override
@@ -68,11 +73,13 @@ public class PiGpioI2CProviderImpl extends I2CProviderBase implements PiGpioI2CP
     public I2C create(I2CConfig config) {
         synchronized (this.piGpio) {
             // initialize the PIGPIO library
-            if (!piGpio.isInitialized())
-                piGpio.initialize();
+            if (!this.piGpio.isInitialized())
+                this.piGpio.initialize();
+
+            PiGpioI2CBus i2CBus = this.i2CBusMap.computeIfAbsent(config.getBus(), busNr -> new PiGpioI2CBus(config));

             // create new I/O instance based on I/O config
-            PiGpioI2C i2C = new PiGpioI2C(piGpio, this, config);
+            PiGpioI2C i2C = new PiGpioI2C(this.piGpio, i2CBus, this, config);
             this.context.registry().add(i2C);
             return i2C;
         }
diff --git a/plugins/pi4j-plugin-raspberrypi/src/main/java/com/pi4j/plugin/raspberrypi/provider/i2c/RpiI2C.java b/plugins/pi4j-plugin-raspberrypi/src/main/java/com/pi4j/plugin/raspberrypi/provider/i2c/RpiI2C.java
index 18bfebee..1836f344 100644
--- a/plugins/pi4j-plugin-raspberrypi/src/main/java/com/pi4j/plugin/raspberrypi/provider/i2c/RpiI2C.java
+++ b/plugins/pi4j-plugin-raspberrypi/src/main/java/com/pi4j/plugin/raspberrypi/provider/i2c/RpiI2C.java
@@ -39,7 +39,7 @@ import com.pi4j.io.i2c.I2CProvider;
  * @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
  * @version $Id: $Id
  */
-public class RpiI2C extends I2CBase implements I2C {
+public class RpiI2C extends I2CBase<RpiI2CBus> implements I2C {

     /**
      * <p>Constructor for RpiI2C.</p>
@@ -48,7 +48,7 @@ public class RpiI2C extends I2CBase implements I2C {
      * @param config a {@link com.pi4j.io.i2c.I2CConfig} object.
      */
     public RpiI2C(I2CProvider provider, I2CConfig config){
-        super(provider, config);
+        super(provider, config, new RpiI2CBus(config));
     }

     /** {@inheritDoc} */
diff --git a/plugins/pi4j-plugin-raspberrypi/src/main/java/com/pi4j/plugin/raspberrypi/provider/i2c/RpiI2CBus.java b/plugins/pi4j-plugin-raspberrypi/src/main/java/com/pi4j/plugin/raspberrypi/provider/i2c/RpiI2CBus.java
new file mode 100644
index 00000000..26612f7b
--- /dev/null
+++ b/plugins/pi4j-plugin-raspberrypi/src/main/java/com/pi4j/plugin/raspberrypi/provider/i2c/RpiI2CBus.java
@@ -0,0 +1,19 @@
+package com.pi4j.plugin.raspberrypi.provider.i2c;
+
+import com.pi4j.io.i2c.I2C;
+import com.pi4j.io.i2c.I2CBusBase;
+import com.pi4j.io.i2c.I2CConfig;
+
+import java.util.concurrent.Callable;
+
+public class RpiI2CBus extends I2CBusBase {
+
+    public RpiI2CBus(I2CConfig config) {
+        super(config);
+    }
+
+    @Override
+    public <R> R execute(I2C i2c, Callable<R> action) {
+        return _execute(i2c, action);
+    }
+}

diff --git a/pi4j-core/src/main/java/com/pi4j/common/Action.java b/pi4j-core/src/main/java/com/pi4j/common/Action.java
deleted file mode 100644
index 06c6905a..00000000
--- a/pi4j-core/src/main/java/com/pi4j/common/Action.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.pi4j.common;
-
-public interface Action {
-
-    void execute() throws Exception;
-}
diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/CheckedFunction.java b/pi4j-core/src/main/java/com/pi4j/common/CheckedFunction.java
similarity index 67%
rename from plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/CheckedFunction.java
rename to pi4j-core/src/main/java/com/pi4j/common/CheckedFunction.java
index 60f31150..f70747a2 100644
--- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/CheckedFunction.java
+++ b/pi4j-core/src/main/java/com/pi4j/common/CheckedFunction.java
@@ -1,4 +1,4 @@
-package com.pi4j.plugin.linuxfs.provider.i2c;
+package com.pi4j.common;

 @FunctionalInterface
 public interface CheckedFunction<T, R> {
diff --git a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java
index bfb64f9c..b3415836 100644
--- a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java
+++ b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java
@@ -25,27 +25,24 @@ package com.pi4j.io.i2c;
  * #L%
  */

-import com.pi4j.common.Action;
 import com.pi4j.context.Context;
 import com.pi4j.io.IO;
 import com.pi4j.io.IODataReader;
 import com.pi4j.io.IODataWriter;

+import java.util.concurrent.Callable;
+
 /**
  * I2C I/O Interface for Pi4J I2C Bus/Device Communications
  *
  * @author Robert Savage
- *
- * Based on previous contributions from:
- *        Daniel Sendula,
- *        <a href="http://raspelikan.blogspot.co.at">RasPelikan</a>
+ * <p>
+ * Based on previous contributions from: Daniel Sendula,
+ * <a href="http://raspelikan.blogspot.co.at">RasPelikan</a>
  * @version $Id: $Id
  */
-public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
-        IODataWriter,
-        IODataReader,
-        I2CRegisterDataReaderWriter,
-        AutoCloseable {
+public interface I2C
+    extends IO<I2C, I2CConfig, I2CProvider>, IODataWriter, IODataReader, I2CRegisterDataReaderWriter, AutoCloseable {

     /**
      * <p>close.</p>
@@ -58,9 +55,10 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
      * <p>newConfigBuilder.</p>
      *
      * @param context {@link Context}
+     *
      * @return a {@link com.pi4j.io.i2c.I2CConfigBuilder} object.
      */
-    static I2CConfigBuilder newConfigBuilder(Context context){
+    static I2CConfigBuilder newConfigBuilder(Context context) {
         return I2CConfigBuilder.newInstance(context);
     }

@@ -69,7 +67,7 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
      *
      * @return The I2C device address for which this instance is constructed for.
      */
-    default int device(){
+    default int device() {
         return config().device();
     }

@@ -78,7 +76,7 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
      *
      * @return The I2C bus address for which this instance is constructed for.
      */
-    default int bus(){
+    default int bus() {
         return config().bus();
     }

@@ -94,7 +92,7 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
      *
      * @return The I2C bus address for which this instance is constructed for.
      */
-    default int getBus(){
+    default int getBus() {
         return bus();
     }

@@ -103,33 +101,69 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
      *
      * @return The I2C device address for which this instance is constructed for.
      */
-    default int getDevice(){
+    default int getDevice() {
         return device();
     }

+    /**
+     * Method to perform a write of the given buffer, and then a read into the given buffer
+     *
+     * @param writeBuffer the buffer to write
+     * @param readBuffer  the buffer to read into
+     *
+     * @return the number of bytes read
+     */
+    default int writeRead(byte[] writeBuffer, byte[] readBuffer) {
+        return writeRead(writeBuffer, writeBuffer.length, 0, readBuffer, readBuffer.length, 0);
+    }
+
+    /**
+     * Method to perform a write of the given buffer, and then a read into the given buffer
+     *
+     * @param writeSize   the number of bytes to write
+     * @param writeOffset the offset of the array to write
+     * @param writeBuffer the buffer to write respecting the given length and offset
+     * @param readSize    the number of bytes to read
+     * @param readOffset  the offset in the read buffer at which to insert the read bytes
+     * @param readBuffer  the buffer into which to read the bytes
+     *
+     * @return the number of bytes read
+     */
+    default int writeRead(byte[] writeBuffer, int writeSize, int writeOffset, byte[] readBuffer, int readSize,
+        int readOffset) {
+        return execute(() -> {
+            int written = write(writeBuffer, writeOffset, writeSize);
+            if (written != writeOffset)
+                throw new IllegalStateException(
+                    "Expected to write " + writeOffset + " bytes but only wrote " + written + " bytes");
+            return read(readBuffer, readOffset, readSize);
+        });
+    }
+
     /**
      * Get an encapsulated interface for reading and writing to a specific I2C device register
      *
      * @param address a int.
+     *
      * @return a {@link com.pi4j.io.i2c.I2CRegister} object.
      */
     I2CRegister getRegister(int address);

     /**
-     * I2C Device Register
-     * Get an encapsulated interface for reading and writing to a specific I2C device register
+     * I2C Device Register Get an encapsulated interface for reading and writing to a specific I2C device register
      *
      * @param address the (16-bit) device register address
+     *
      * @return an instance of I2CRegister for the provided register address
      */
-    default I2CRegister register(int address){
+    default I2CRegister register(int address) {
         return getRegister(address);
     }

     /**
      * Executes the given runnable on the I2C bus, locking the bus for the duration of the given task
      *
-     * @param action the action to perform
+     * @param action the action to perform, returning a value
      */
-    void execute(Action action);
+    <T> T execute(Callable<T> action);
 }
diff --git a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBase.java b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBase.java
index eb977f6f..b480c4cd 100644
--- a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBase.java
+++ b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBase.java
@@ -30,34 +30,43 @@ import com.pi4j.exception.ShutdownException;
 import com.pi4j.io.IOBase;
 import com.pi4j.io.i2c.impl.DefaultI2CRegister;

+import java.util.concurrent.Callable;
+
 /**
  * <p>Abstract I2CBase class.</p>
  *
  * @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
  * @version $Id: $Id
  */
-public abstract class I2CBase extends IOBase<I2C, I2CConfig, I2CProvider> implements I2C {
+public abstract class I2CBase<T extends I2CBus> extends IOBase<I2C, I2CConfig, I2CProvider> implements I2C {

-    protected boolean isOpen = false;
+    protected boolean isOpen;
+    protected final T i2CBus;

     /**
      * <p>Constructor for I2CBase.</p>
      *
-     * @param provider a {@link com.pi4j.io.i2c.I2CProvider} object.
-     * @param config a {@link com.pi4j.io.i2c.I2CConfig} object.
+     * @param provider a {@link I2CProvider} object.
+     * @param config   a {@link I2CConfig} object.
+     * @param i2CBus   a {@link I2CBus} object.
      */
-    public I2CBase(I2CProvider provider, I2CConfig config) {
+    public I2CBase(I2CProvider provider, I2CConfig config, T i2CBus) {
         super(provider, config);
         this.isOpen = true;
+        this.i2CBus = i2CBus;
     }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public boolean isOpen() {
         return this.isOpen;
     }

-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void close() {
         this.isOpen = false;
@@ -65,24 +74,33 @@ public abstract class I2CBase extends IOBase<I2C, I2CConfig, I2CProvider> implem

     /**
      * {@inheritDoc}
-     *
+     * <p>
      * Get an encapsulated interface for reading and writing to a specific I2C device register
      */
-    public I2CRegister getRegister(int address){
+    public I2CRegister getRegister(int address) {
         return new DefaultI2CRegister(this, address);
     }

-    /** {@inheritDoc} */
+    @Override
+    public <V> V execute(Callable<V> action) {
+        if (action == null)
+            throw new NullPointerException("Parameter 'action' is mandatory!");
+        return this.i2CBus.execute(this, action);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public I2C shutdown(Context context) throws ShutdownException {
         // if this I2C device is still open, then we need to close it since we are shutting down
-        if(this.isOpen()) {
+        if (this.isOpen()) {
             try {
-                this.close();
+                close();
             } catch (Exception e) {
                 throw new ShutdownException(e);
             }
         }
-        return (I2C)this;
+        return this;
     }
 }
diff --git a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBus.java b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBus.java
new file mode 100644
index 00000000..1052c0ad
--- /dev/null
+++ b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBus.java
@@ -0,0 +1,22 @@
+package com.pi4j.io.i2c;
+
+import java.util.concurrent.Callable;
+
+/**
+ * This interface defines method to be performed on an I2C bus. Most importantly the {@link #execute(I2C, Callable)}
+ * allows to perform bulk operations on the bus in a thread safe manner.
+ */
+public interface I2CBus {
+
+    /**
+     * Executes the given action, which typically performs multiple I2C reads and/or writes on the I2C bus in a thread
+     * safe manner, i.e. the bus is blocked till the action is completed.
+     *
+     * @param i2c    the device for which to perform the action
+     * @param action the action to perform
+     * @param <R>    the result type of the action, if any
+     *
+     * @return the result of the action
+     */
+    <R> R execute(I2C i2c, Callable<R> action);
+}
diff --git a/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBusBase.java b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBusBase.java
new file mode 100644
index 00000000..bd1dbef4
--- /dev/null
+++ b/pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBusBase.java
@@ -0,0 +1,60 @@
+package com.pi4j.io.i2c;
+
+import com.pi4j.exception.Pi4JException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static java.text.MessageFormat.format;
+
+public abstract class I2CBusBase implements I2CBus {
+
+    private static final Logger logger = LoggerFactory.getLogger(I2CBusBase.class);
+
+    public static final long DEFAULT_LOCK_ACQUIRE_TIMEOUT = 1000;
+    public static final TimeUnit DEFAULT_LOCK_ACQUIRE_TIMEOUT_UNITS = TimeUnit.MILLISECONDS;
+
+    protected final int bus;
+
+    protected final long lockAquireTimeout;
+    protected final TimeUnit lockAquireTimeoutUnit;
+    private final ReentrantLock lock = new ReentrantLock(true);
+
+    public I2CBusBase(I2CConfig config) {
+        if (config.bus() == null)
+            throw new IllegalArgumentException("I2C bus must be specified");
+
+        this.bus = config.getBus();
+
+        this.lockAquireTimeout = DEFAULT_LOCK_ACQUIRE_TIMEOUT;
+        this.lockAquireTimeoutUnit = DEFAULT_LOCK_ACQUIRE_TIMEOUT_UNITS;
+    }
+
+    protected <R> R _execute(I2C i2c, Callable<R> action) {
+        if (i2c == null)
+            throw new NullPointerException("Parameter 'i2c' is mandatory!");
+        if (action == null)
+            throw new NullPointerException("Parameter 'action' is mandatory!");
+        try {
+            if (this.lock.tryLock() || this.lock.tryLock(this.lockAquireTimeout, this.lockAquireTimeoutUnit)) {
+                try {
+                    return action.call();
+                } finally {
+                    this.lock.unlock();
+                }
+            } else {
+                throw new Pi4JException(
+                    format("Failed to get I2C lock on bus {0} after {1} {2}", this.bus, this.lockAquireTimeout,
+                        this.lockAquireTimeoutUnit));
+            }
+        } catch (InterruptedException e) {
+            logger.error("Failed locking {}-{}", getClass().getSimpleName(), this.bus, e);
+            throw new RuntimeException("Could not obtain an access-lock!", e);
+        } catch (Exception e) {
+            throw new Pi4JException("Failed to execute action for device " + i2c.device() + " on bus " + this.bus, e);
+        }
+    }
+}
diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2C.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2C.java
index b85a5e83..54b4b6e9 100644
--- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2C.java
+++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2C.java
@@ -40,7 +40,7 @@ import java.util.Objects;
  * @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
  * @version $Id: $Id
  */
-public class LinuxFsI2C extends I2CBase implements I2C {
+public class LinuxFsI2C extends I2CBase<LinuxFsI2CBus> implements I2C {

     private final LinuxFsI2CBus i2CBus;

@@ -53,7 +53,7 @@ public class LinuxFsI2C extends I2CBase implements I2C {
      *     a {@link I2CConfig} object.
      */
     public LinuxFsI2C(LinuxFsI2CBus i2CBus, I2CProvider provider, I2CConfig config) {
-        super(provider, config);
+        super(provider, config, i2CBus);
         this.i2CBus = i2CBus;
     }

@@ -332,19 +332,4 @@ public class LinuxFsI2C extends I2CBase implements I2C {
         return word;

     }
-
-    @Override
-    public void execute(Action action) {
-        this.i2CBus.execute(this, file -> {
-            action.execute();
-            return null;
-        });
-    }
-
-    @Override
-    public void close() {
-        if (this.i2CBus != null)
-            this.i2CBus.close();
-        super.close();
-    }
 }
diff --git a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CBus.java b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CBus.java
index b29b04a6..48124cef 100644
--- a/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CBus.java
+++ b/plugins/pi4j-plugin-linuxfs/src/main/java/com/pi4j/plugin/linuxfs/provider/i2c/LinuxFsI2CBus.java
@@ -1,24 +1,21 @@
 package com.pi4j.plugin.linuxfs.provider.i2c;

-import java.io.File;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-
+import com.pi4j.common.CheckedFunction;
 import com.pi4j.exception.Pi4JException;
 import com.pi4j.io.i2c.I2C;
+import com.pi4j.io.i2c.I2CBusBase;
 import com.pi4j.io.i2c.I2CConfig;
 import com.pi4j.library.linuxfs.LinuxFile;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

-public class LinuxFsI2CBus {
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.concurrent.Callable;

-    public static final long DEFAULT_LOCK_ACQUIRE_TIMEOUT = 1000;
-    public static final TimeUnit DEFAULT_LOCK_ACQUIRE_TIMEOUT_UNITS = TimeUnit.MILLISECONDS;
-    private final Integer bus;
+public class LinuxFsI2CBus extends I2CBusBase {

     protected Logger logger = LoggerFactory.getLogger(this.getClass());

@@ -28,13 +25,9 @@ public class LinuxFsI2CBus {
     protected LinuxFile file;
     private int lastAddress;

-    protected long lockAquireTimeout;
-    protected TimeUnit lockAquireTimeoutUnit;
-    private final ReentrantLock lock = new ReentrantLock(true);
-
     public LinuxFsI2CBus(I2CConfig config) {
+        super(config);

-        this.bus = config.getBus();
         final File sysfs = new File("/sys/bus/i2c/devices/i2c-" + this.bus);
         if (!sysfs.exists() || !sysfs.isDirectory())
             throw new Pi4JException("I2C bus " + this.bus + " does not exist.");
@@ -49,16 +42,63 @@ public class LinuxFsI2CBus {
         } catch (IOException e) {
             throw new Pi4JException(e);
         }
+    }

-        this.lockAquireTimeout = DEFAULT_LOCK_ACQUIRE_TIMEOUT;
-        this.lockAquireTimeoutUnit = DEFAULT_LOCK_ACQUIRE_TIMEOUT_UNITS;
+    @Override
+    public <R> R execute(I2C i2c, Callable<R> action) {
+        return _execute(i2c, () -> {
+            try {
+                selectBusSlave(i2c);
+                return action.call();
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new Pi4JException("Failed to execute action for device " + i2c.device() + " on bus " + this.bus,
+                    e);
+            }
+        });
+    }
+
+    public <R> R execute(final I2C i2c, final CheckedFunction<LinuxFile, R> action) {
+        return _execute(i2c, () -> {
+            try {
+                selectBusSlave(i2c);
+                return action.apply(this.file);
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new Pi4JException("Failed to execute action for device " + i2c.device() + " on bus " + this.bus,
+                    e);
+            }
+        });
+    }
+
+    /**
+     * @param i2c     the device to select before performing the ioctl command
+     * @param command From I2CConstants
+     * @param data    values in bytes for all structures, with 4 or 8 byte alignment enforced by filling holes before
+     *                pointers
+     * @param offsets ByteBuffer: offsets of pointer/ byte offset of pointedToData
+     */
+    public void executeIOCTL(final I2C i2c, long command, ByteBuffer data, IntBuffer offsets) {
+        _execute(i2c, () -> {
+            try {
+                selectBusSlave(i2c);
+                this.file.ioctl(command, data, offsets);
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new Pi4JException("Failed to execute ioctl for device " + i2c.device() + " on bus " + this.bus,
+                    e);
+            }
+            return null;
+        });
     }

     /**
      * Selects the slave device if not already selected on this bus. Runs the required ioctl's via JNI.
      *
-     * @param i2c
-     *     Device to select
+     * @param i2c Device to select
      */
     protected void selectBusSlave(I2C i2c) throws IOException {
         if (this.lastAddress == i2c.device())
@@ -68,81 +108,12 @@ public class LinuxFsI2CBus {
         this.file.ioctl(I2CConstants.I2C_SLAVE, i2c.device() & 0xFF);
     }

-    /**
-     *
-     * @param i2c
-     * @param command   From I2CConstants
-     * @param data  values in bytes for all structures, with 4 or 8 byte alignment enforced by filling holes before pointers
-     * @param offsets   ByteBuffer: offsets of pointer/ byte offset of pointedToData
-     *
-     * @return    0 if success, else -1
-     */
-    public int executeIOCTL(final I2C i2c, long command, ByteBuffer data, IntBuffer offsets){
-        int rc = -1;
-        try {
-            if (this.lock.tryLock() || this.lock.tryLock(this.lockAquireTimeout, this.lockAquireTimeoutUnit)) {
-
-                try {
-                    selectBusSlave(i2c);
-                    this.file.ioctl( command, data, offsets);
-                    rc = 0; //had there been any failure an exception would bypass this statement
-                    } finally {
-                    while (this.lock.isHeldByCurrentThread())
-                        this.lock.unlock();
-                }
-
-            } else {
-                throw new Pi4JException(
-                    "Failed to get I2C lock on bus " + this.bus + " after " + this.lockAquireTimeout + " "
-                        + this.lockAquireTimeoutUnit);
-            }
-        } catch (InterruptedException e) {
-            logger.error("Failed locking " + getClass().getSimpleName() + "-" + this.bus, e);
-            throw new RuntimeException("Could not obtain an access-lock!", e);
-        } catch (Exception e) {
-            throw new Pi4JException("Failed to execute action for device " + i2c.device() + " on bus " + this.bus, e);
-        }
-
-        return rc;
-    }
-
-    public <R> R execute(final I2C i2c, final CheckedFunction<LinuxFile, R> action) {
-        if (i2c == null)
-            throw new NullPointerException("Parameter 'i2c' is mandatory!");
-        if (action == null)
-            throw new NullPointer…
  • Loading branch information
eitch committed Apr 19, 2024
1 parent d4b1850 commit a7ba4f0
Show file tree
Hide file tree
Showing 16 changed files with 373 additions and 194 deletions.
6 changes: 0 additions & 6 deletions pi4j-core/src/main/java/com/pi4j/common/Action.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.pi4j.plugin.linuxfs.provider.i2c;
package com.pi4j.common;

@FunctionalInterface
public interface CheckedFunction<T, R> {
Expand Down
74 changes: 54 additions & 20 deletions pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,24 @@
* #L%
*/

import com.pi4j.common.Action;
import com.pi4j.context.Context;
import com.pi4j.io.IO;
import com.pi4j.io.IODataReader;
import com.pi4j.io.IODataWriter;

import java.util.concurrent.Callable;

/**
* I2C I/O Interface for Pi4J I2C Bus/Device Communications
*
* @author Robert Savage
*
* Based on previous contributions from:
* Daniel Sendula,
* <a href="http://raspelikan.blogspot.co.at">RasPelikan</a>
* <p>
* Based on previous contributions from: Daniel Sendula,
* <a href="http://raspelikan.blogspot.co.at">RasPelikan</a>
* @version $Id: $Id
*/
public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
IODataWriter,
IODataReader,
I2CRegisterDataReaderWriter,
AutoCloseable {
public interface I2C
extends IO<I2C, I2CConfig, I2CProvider>, IODataWriter, IODataReader, I2CRegisterDataReaderWriter, AutoCloseable {

/**
* <p>close.</p>
Expand All @@ -58,9 +55,10 @@ public interface I2C extends IO<I2C, I2CConfig, I2CProvider>,
* <p>newConfigBuilder.</p>
*
* @param context {@link Context}
*
* @return a {@link com.pi4j.io.i2c.I2CConfigBuilder} object.
*/
static I2CConfigBuilder newConfigBuilder(Context context){
static I2CConfigBuilder newConfigBuilder(Context context) {
return I2CConfigBuilder.newInstance(context);
}

Expand All @@ -69,7 +67,7 @@ static I2CConfigBuilder newConfigBuilder(Context context){
*
* @return The I2C device address for which this instance is constructed for.
*/
default int device(){
default int device() {
return config().device();
}

Expand All @@ -78,7 +76,7 @@ default int device(){
*
* @return The I2C bus address for which this instance is constructed for.
*/
default int bus(){
default int bus() {
return config().bus();
}

Expand All @@ -94,7 +92,7 @@ default int bus(){
*
* @return The I2C bus address for which this instance is constructed for.
*/
default int getBus(){
default int getBus() {
return bus();
}

Expand All @@ -103,33 +101,69 @@ default int getBus(){
*
* @return The I2C device address for which this instance is constructed for.
*/
default int getDevice(){
default int getDevice() {
return device();
}

/**
* Method to perform a write of the given buffer, and then a read into the given buffer
*
* @param writeBuffer the buffer to write
* @param readBuffer the buffer to read into
*
* @return the number of bytes read
*/
default int writeRead(byte[] writeBuffer, byte[] readBuffer) {
return writeRead(writeBuffer, writeBuffer.length, 0, readBuffer, readBuffer.length, 0);
}

/**
* Method to perform a write of the given buffer, and then a read into the given buffer
*
* @param writeSize the number of bytes to write
* @param writeOffset the offset of the array to write
* @param writeBuffer the buffer to write respecting the given length and offset
* @param readSize the number of bytes to read
* @param readOffset the offset in the read buffer at which to insert the read bytes
* @param readBuffer the buffer into which to read the bytes
*
* @return the number of bytes read
*/
default int writeRead(byte[] writeBuffer, int writeSize, int writeOffset, byte[] readBuffer, int readSize,
int readOffset) {
return execute(() -> {
int written = write(writeBuffer, writeOffset, writeSize);
if (written != writeOffset)
throw new IllegalStateException(
"Expected to write " + writeOffset + " bytes but only wrote " + written + " bytes");
return read(readBuffer, readOffset, readSize);
});
}

/**
* Get an encapsulated interface for reading and writing to a specific I2C device register
*
* @param address a int.
*
* @return a {@link com.pi4j.io.i2c.I2CRegister} object.
*/
I2CRegister getRegister(int address);

/**
* I2C Device Register
* Get an encapsulated interface for reading and writing to a specific I2C device register
* I2C Device Register Get an encapsulated interface for reading and writing to a specific I2C device register
*
* @param address the (16-bit) device register address
*
* @return an instance of I2CRegister for the provided register address
*/
default I2CRegister register(int address){
default I2CRegister register(int address) {
return getRegister(address);
}

/**
* Executes the given runnable on the I2C bus, locking the bus for the duration of the given task
*
* @param action the action to perform
* @param action the action to perform, returning a value
*/
void execute(Action action);
<T> T execute(Callable<T> action);
}
44 changes: 31 additions & 13 deletions pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,59 +30,77 @@
import com.pi4j.io.IOBase;
import com.pi4j.io.i2c.impl.DefaultI2CRegister;

import java.util.concurrent.Callable;

/**
* <p>Abstract I2CBase class.</p>
*
* @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
* @version $Id: $Id
*/
public abstract class I2CBase extends IOBase<I2C, I2CConfig, I2CProvider> implements I2C {
public abstract class I2CBase<T extends I2CBus> extends IOBase<I2C, I2CConfig, I2CProvider> implements I2C {

protected boolean isOpen = false;
protected boolean isOpen;
protected final T i2CBus;

/**
* <p>Constructor for I2CBase.</p>
*
* @param provider a {@link com.pi4j.io.i2c.I2CProvider} object.
* @param config a {@link com.pi4j.io.i2c.I2CConfig} object.
* @param provider a {@link I2CProvider} object.
* @param config a {@link I2CConfig} object.
* @param i2CBus a {@link I2CBus} object.
*/
public I2CBase(I2CProvider provider, I2CConfig config) {
public I2CBase(I2CProvider provider, I2CConfig config, T i2CBus) {
super(provider, config);
this.isOpen = true;
this.i2CBus = i2CBus;
}

/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public boolean isOpen() {
return this.isOpen;
}

/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public void close() {
this.isOpen = false;
}

/**
* {@inheritDoc}
*
* <p>
* Get an encapsulated interface for reading and writing to a specific I2C device register
*/
public I2CRegister getRegister(int address){
public I2CRegister getRegister(int address) {
return new DefaultI2CRegister(this, address);
}

/** {@inheritDoc} */
@Override
public <V> V execute(Callable<V> action) {
if (action == null)
throw new NullPointerException("Parameter 'action' is mandatory!");
return this.i2CBus.execute(this, action);
}

/**
* {@inheritDoc}
*/
@Override
public I2C shutdown(Context context) throws ShutdownException {
// if this I2C device is still open, then we need to close it since we are shutting down
if(this.isOpen()) {
if (this.isOpen()) {
try {
this.close();
close();
} catch (Exception e) {
throw new ShutdownException(e);
}
}
return (I2C)this;
return this;
}
}
22 changes: 22 additions & 0 deletions pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.pi4j.io.i2c;

import java.util.concurrent.Callable;

/**
* This interface defines method to be performed on an I2C bus. Most importantly the {@link #execute(I2C, Callable)}
* allows to perform bulk operations on the bus in a thread safe manner.
*/
public interface I2CBus {

/**
* Executes the given action, which typically performs multiple I2C reads and/or writes on the I2C bus in a thread
* safe manner, i.e. the bus is blocked till the action is completed.
*
* @param i2c the device for which to perform the action
* @param action the action to perform
* @param <R> the result type of the action, if any
*
* @return the result of the action
*/
<R> R execute(I2C i2c, Callable<R> action);
}
60 changes: 60 additions & 0 deletions pi4j-core/src/main/java/com/pi4j/io/i2c/I2CBusBase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.pi4j.io.i2c;

import com.pi4j.exception.Pi4JException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import static java.text.MessageFormat.format;

public abstract class I2CBusBase implements I2CBus {

private static final Logger logger = LoggerFactory.getLogger(I2CBusBase.class);

public static final long DEFAULT_LOCK_ACQUIRE_TIMEOUT = 1000;
public static final TimeUnit DEFAULT_LOCK_ACQUIRE_TIMEOUT_UNITS = TimeUnit.MILLISECONDS;

protected final int bus;

protected final long lockAquireTimeout;
protected final TimeUnit lockAquireTimeoutUnit;
private final ReentrantLock lock = new ReentrantLock(true);

public I2CBusBase(I2CConfig config) {
if (config.bus() == null)
throw new IllegalArgumentException("I2C bus must be specified");

this.bus = config.getBus();

this.lockAquireTimeout = DEFAULT_LOCK_ACQUIRE_TIMEOUT;
this.lockAquireTimeoutUnit = DEFAULT_LOCK_ACQUIRE_TIMEOUT_UNITS;
}

protected <R> R _execute(I2C i2c, Callable<R> action) {
if (i2c == null)
throw new NullPointerException("Parameter 'i2c' is mandatory!");
if (action == null)
throw new NullPointerException("Parameter 'action' is mandatory!");
try {
if (this.lock.tryLock() || this.lock.tryLock(this.lockAquireTimeout, this.lockAquireTimeoutUnit)) {
try {
return action.call();
} finally {
this.lock.unlock();
}
} else {
throw new Pi4JException(
format("Failed to get I2C lock on bus {0} after {1} {2}", this.bus, this.lockAquireTimeout,
this.lockAquireTimeoutUnit));
}
} catch (InterruptedException e) {
logger.error("Failed locking {}-{}", getClass().getSimpleName(), this.bus, e);
throw new RuntimeException("Could not obtain an access-lock!", e);
} catch (Exception e) {
throw new Pi4JException("Failed to execute action for device " + i2c.device() + " on bus " + this.bus, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
* @author Robert Savage (<a href="http://www.savagehomeautomation.com">http://www.savagehomeautomation.com</a>)
* @version $Id: $Id
*/
public class LinuxFsI2C extends I2CBase implements I2C {
public class LinuxFsI2C extends I2CBase<LinuxFsI2CBus> implements I2C {

private final LinuxFsI2CBus i2CBus;

Expand All @@ -53,7 +53,7 @@ public class LinuxFsI2C extends I2CBase implements I2C {
* a {@link I2CConfig} object.
*/
public LinuxFsI2C(LinuxFsI2CBus i2CBus, I2CProvider provider, I2CConfig config) {
super(provider, config);
super(provider, config, i2CBus);
this.i2CBus = i2CBus;
}

Expand Down Expand Up @@ -332,19 +332,4 @@ public int writeReadRegisterWord(int register, int word) {
return word;

}

@Override
public void execute(Action action) {
this.i2CBus.execute(this, file -> {
action.execute();
return null;
});
}

@Override
public void close() {
if (this.i2CBus != null)
this.i2CBus.close();
super.close();
}
}
Loading

0 comments on commit a7ba4f0

Please sign in to comment.