Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Major] I2C extensions, fixed and cleanup #351

Merged
merged 2 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
75 changes: 58 additions & 17 deletions pi4j-core/src/main/java/com/pi4j/io/i2c/I2C.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,19 @@
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,26 +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, returning a value
*/
<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,11 +332,4 @@ public int writeReadRegisterWord(int register, int word) {
return word;

}

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