[New] Added TCA9534OutputConnection from PCF8574OutputConnection

This commit is contained in:
Robert von Burg 2021-02-15 23:22:48 +01:00
parent aa9c8197b3
commit f62cac696b
3 changed files with 343 additions and 166 deletions

View File

@ -0,0 +1,206 @@
package li.strolch.plc.core.hw.i2c;
import static java.util.stream.Collectors.joining;
import static li.strolch.plc.model.PlcConstants.PARAM_SIMULATED;
import static li.strolch.utils.collections.CollectionsHelper.byteStream;
import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessageWithCauses;
import static li.strolch.utils.helper.StringHelper.toHexString;
import java.io.IOException;
import java.util.*;
import com.pi4j.io.i2c.I2CBus;
import com.pi4j.io.i2c.I2CDevice;
import com.pi4j.io.i2c.I2CFactory;
import li.strolch.plc.core.hw.Plc;
import li.strolch.plc.core.hw.connections.SimplePlcConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class Multi8BitI2cOutputConnection extends SimplePlcConnection {
protected static final Logger logger = LoggerFactory.getLogger(Multi8BitI2cOutputConnection.class);
protected boolean verbose;
protected boolean resetOnConnect;
protected int i2cBusNr;
protected int nrOfBits;
protected boolean inverted;
protected boolean reversed;
protected byte[] addresses;
protected I2CDevice[] outputDevices;
protected byte[] states;
protected Map<String, int[]> positionsByAddress;
public Multi8BitI2cOutputConnection(Plc plc, String id) {
super(plc, id);
}
public abstract String getName();
public String getDescription() {
return "I2C Output " + getName() + " @ " + byteStream(this.addresses).map(b -> "0x" + toHexString(b))
.collect(joining(", "));
}
public String getDescription(byte address) {
return "I2C Output " + getName() + " @ 0x" + toHexString(address);
}
@Override
public void initialize(Map<String, Object> parameters) {
this.simulated = parameters.containsKey(PARAM_SIMULATED) && (boolean) parameters.get(PARAM_SIMULATED);
if (!parameters.containsKey("i2cBus"))
throw new IllegalArgumentException("Missing param i2cBus");
if (!parameters.containsKey("addresses"))
throw new IllegalArgumentException("Missing param addresses");
this.verbose = parameters.containsKey("verbose") && (Boolean) parameters.get("verbose");
this.resetOnConnect = parameters.containsKey("resetOnConnect") && (Boolean) parameters.get("resetOnConnect");
this.nrOfBits = parameters.containsKey("nrOfBits") ? ((Integer) parameters.get("nrOfBits")) : 8;
this.i2cBusNr = (int) parameters.get("i2cBus");
this.inverted = parameters.containsKey("inverted") && (boolean) parameters.get("inverted");
this.reversed = parameters.containsKey("reversed") && (boolean) parameters.get("reversed");
logger.info("inverted: " + this.inverted);
logger.info("reversed: " + this.reversed);
logger.info("nrOfBits: " + this.nrOfBits);
@SuppressWarnings("unchecked")
List<Integer> addressList = (List<Integer>) parameters.get("addresses");
this.addresses = new byte[addressList.size()];
for (int i = 0; i < addressList.size(); i++) {
this.addresses[i] = addressList.get(i).byteValue();
}
Map<String, int[]> positionsByAddress = new HashMap<>();
for (int i = 0; i < this.addresses.length; i++) {
for (int j = 0; j < this.nrOfBits; j++)
positionsByAddress.put(this.id + "." + i + "." + j, new int[] { i, j });
}
this.positionsByAddress = Collections.unmodifiableMap(positionsByAddress);
logger.info("Configured " + getDescription());
}
@Override
public boolean connect() {
if (this.simulated) {
logger.warn(getName() + ": " + this.id + ": Running SIMULATED, NOT CONNECTING!");
return super.connect();
}
if (isConnected()) {
logger.warn(getName() + ": " + this.id + ": Already connected");
return true;
}
logger.info(getName() + ": " + this.id + ": Connecting...");
// initialize
try {
I2CBus i2cBus = I2CFactory.getInstance(this.i2cBusNr);
this.outputDevices = new I2CDevice[this.addresses.length];
this.states = new byte[this.addresses.length];
byte[] bytes = this.addresses;
for (int i = 0; i < bytes.length; i++) {
byte address = bytes[i];
I2CDevice i2cDev = i2cBus.getDevice(address);
this.outputDevices[i] = i2cDev;
}
if (setup()) {
logger.info("Successfully connected " + this.outputDevices.length + " devices as " + getDescription());
return super.connect();
}
return false;
} catch (Throwable e) {
handleBrokenConnection("Failed to connect to " + getDescription() + ": " + getExceptionMessageWithCauses(e),
e);
return false;
}
}
protected boolean setup() throws IOException {
boolean ok = true;
I2CDevice[] devices = this.outputDevices;
for (int index = 0; index < devices.length; index++) {
byte address = this.addresses[index];
I2CDevice outputDevice = devices[index];
ok &= setup(address, index, outputDevice);
logger.info("Connected " + getDescription(address));
}
if (ok)
return ok;
return false;
}
protected abstract boolean setup(byte address, int index, I2CDevice outputDevice) throws IOException;
@Override
public void disconnect() {
if (this.simulated) {
logger.warn(this.id + ": Running SIMULATED, NOT CONNECTING!");
super.disconnect();
return;
}
this.outputDevices = null;
this.states = null;
super.disconnect();
}
@Override
public Set<String> getAddresses() {
return new TreeSet<>(this.positionsByAddress.keySet());
}
@Override
public void send(String address, Object value) {
if (this.simulated) {
logger.warn(this.id + ": Running SIMULATED, NOT CONNECTING!");
return;
}
assertConnected();
int[] pos = this.positionsByAddress.get(address);
if (pos == null)
throw new IllegalStateException("Address is illegal " + address);
int device = pos[0];
int pin = pos[1];
I2CDevice outputDevice = this.outputDevices[device];
boolean high = (boolean) value;
// see if we need to invert
if (this.inverted)
high = !high;
try {
synchronized (this) {
setPin(device, pin, outputDevice, high);
}
} catch (Exception e) {
handleBrokenConnection("Failed to write to I2C address: " + address + " at " + getDescription(
(byte) outputDevice.getAddress()) + ": " + getExceptionMessageWithCauses(e), e);
throw new IllegalStateException("Failed to write to I2C address " + address + " at " + getDescription(
(byte) outputDevice.getAddress()), e);
}
}
protected abstract void setPin(int device, int pin, I2CDevice outputDevice, boolean high) throws IOException;
}

View File

@ -1,198 +1,73 @@
package li.strolch.plc.core.hw.i2c;
import static li.strolch.plc.model.PlcConstants.PARAM_SIMULATED;
import static li.strolch.utils.helper.ByteHelper.*;
import static li.strolch.utils.helper.ExceptionHelper.getExceptionMessageWithCauses;
import static li.strolch.utils.helper.StringHelper.toHexString;
import static li.strolch.utils.helper.StringHelper.toPrettyHexString;
import java.util.*;
import java.io.IOException;
import com.pi4j.io.i2c.I2CBus;
import com.pi4j.io.i2c.I2CDevice;
import com.pi4j.io.i2c.I2CFactory;
import li.strolch.plc.core.hw.Plc;
import li.strolch.plc.core.hw.connections.SimplePlcConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PCF8574OutputConnection extends SimplePlcConnection {
private static final Logger logger = LoggerFactory.getLogger(PCF8574OutputConnection.class);
private boolean verbose;
private boolean resetOnConnect;
private int i2cBusNr;
private boolean inverted;
private byte[] addresses;
private I2CDevice[] outputDevices;
private byte[] states;
private Map<String, int[]> positionsByAddress;
public class PCF8574OutputConnection extends Multi8BitI2cOutputConnection {
public PCF8574OutputConnection(Plc plc, String id) {
super(plc, id);
}
@Override
public void initialize(Map<String, Object> parameters) {
this.simulated = parameters.containsKey(PARAM_SIMULATED) && (boolean) parameters.get(PARAM_SIMULATED);
if (!parameters.containsKey("i2cBus"))
throw new IllegalArgumentException("Missing param i2cBus");
if (!parameters.containsKey("addresses"))
throw new IllegalArgumentException("Missing param addresses");
this.verbose = parameters.containsKey("verbose") && (Boolean) parameters.get("verbose");
this.resetOnConnect = parameters.containsKey("resetOnConnect") && (Boolean) parameters.get("resetOnConnect");
this.i2cBusNr = (int) parameters.get("i2cBus");
this.inverted = parameters.containsKey("inverted") && (boolean) parameters.get("inverted");
@SuppressWarnings("unchecked")
List<Integer> addressList = (List<Integer>) parameters.get("addresses");
this.addresses = new byte[addressList.size()];
for (int i = 0; i < addressList.size(); i++) {
this.addresses[i] = addressList.get(i).byteValue();
}
Map<String, int[]> positionsByAddress = new HashMap<>();
for (int i = 0; i < this.addresses.length; i++) {
for (int j = 0; j < 8; j++)
positionsByAddress.put(this.id + "." + i + "." + j, new int[] { i, j });
}
this.positionsByAddress = Collections.unmodifiableMap(positionsByAddress);
logger.info("Configured PCF8574 Output on I2C addresses 0x " + toPrettyHexString(this.addresses));
public String getName() {
return "PCF8574";
}
@Override
public boolean connect() {
if (this.simulated) {
logger.warn(this.id + ": Running SIMULATED, NOT CONNECTING!");
return super.connect();
}
protected boolean setup() throws IOException {
boolean ok = super.setup();
if (isConnected()) {
logger.warn(this.id + ": Already connected");
if (ok)
return true;
}
logger.info(this.id + ": Connecting...");
handleBrokenConnection("Failed to set initial values to " + asBinary((byte) 0xff) + " for " + getDescription(),
null);
// initialize
try {
I2CBus i2cBus = I2CFactory.getInstance(i2cBusNr);
return false;
}
this.outputDevices = new I2CDevice[this.addresses.length];
this.states = new byte[this.addresses.length];
byte[] bytes = this.addresses;
boolean ok = true;
for (int i = 0; i < bytes.length; i++) {
byte address = bytes[i];
@Override
protected boolean setup(byte address, int index, I2CDevice i2cDev) throws IOException {
boolean ok = true;
this.outputDevices[i] = i2cBus.getDevice(address);
if (this.resetOnConnect) {
// default is all outputs off, i.e. 1
this.states[i] = (byte) 0xff;
try {
this.outputDevices[i].write(this.states[i]);
logger.info("Set initial value to " + asBinary((byte) 0xff) + " for address 0x" + toHexString(
address));
} catch (Exception e) {
ok = false;
logger.error("Failed to set initial value to " + asBinary((byte) 0xff) + " on I2C Bus "
+ this.i2cBusNr + " and address 0x" + toHexString(address), e);
}
} else {
this.states[i] = (byte) this.outputDevices[i].read();
logger.info(
"Initial value is " + asBinary(this.states[i]) + " for address 0x" + toHexString(address));
}
logger.info("Connected to I2C Device at address 0x" + toHexString(address) + " on I2C Bus "
+ this.i2cBusNr);
if (this.resetOnConnect) {
// default is all outputs off, i.e. 1
this.states[index] = (byte) 0xff;
try {
i2cDev.write(this.states[index]);
logger.info("Set initial value to " + asBinary((byte) 0xff) + " for " + getDescription(address));
} catch (Exception e) {
ok = false;
logger.error(
"Failed to set initial value to " + asBinary((byte) 0xff) + " for " + getDescription(address),
e);
}
if (ok)
return super.connect();
handleBrokenConnection(
"Failed to set initial values to " + asBinary((byte) 0xff) + " on I2C Bus " + this.i2cBusNr
+ " and addresses 0x " + toPrettyHexString(this.addresses), null);
return false;
} catch (Throwable e) {
handleBrokenConnection(
"Failed to connect to I2C Bus " + this.i2cBusNr + " and addresses 0x " + toPrettyHexString(
this.addresses) + ": " + getExceptionMessageWithCauses(e), e);
return false;
} else {
this.states[index] = (byte) i2cDev.read();
logger.info("Initial value is " + asBinary(this.states[index]) + " for " + getDescription(address));
}
return ok;
}
@Override
public void disconnect() {
if (this.simulated) {
logger.warn(this.id + ": Running SIMULATED, NOT CONNECTING!");
super.disconnect();
return;
}
protected void setPin(int device, int pin, I2CDevice outputDevice, boolean high) throws IOException {
byte newState;
if (high)
newState = clearBit(this.states[device], pin);
else
newState = setBit(this.states[device], pin);
this.outputDevices = null;
this.states = null;
if (this.verbose)
logger.info("Setting " + getDescription((byte) outputDevice.getAddress()) + " to new state " + asBinary(
newState));
super.disconnect();
}
@Override
public Set<String> getAddresses() {
return new TreeSet<>(this.positionsByAddress.keySet());
}
@Override
public void send(String address, Object value) {
if (this.simulated) {
logger.warn(this.id + ": Running SIMULATED, NOT CONNECTING!");
return;
}
assertConnected();
int[] pos = this.positionsByAddress.get(address);
if (pos == null)
throw new IllegalStateException("Address is illegal " + address);
int device = pos[0];
int pin = pos[1];
boolean high = (boolean) value;
// see if we need to invert
if (this.inverted)
high = !high;
try {
synchronized (this) {
byte newState;
if (high)
newState = clearBit(this.states[device], pin);
else
newState = setBit(this.states[device], pin);
if (this.verbose)
logger.info("Setting 0x" + toHexString((byte) device) + " to new state " + asBinary(newState));
this.outputDevices[device].write(newState);
this.states[device] = newState;
}
} catch (Exception e) {
handleBrokenConnection(
"Failed to write to I2C address: " + address + ": " + getExceptionMessageWithCauses(e), e);
throw new IllegalStateException("Failed to write to I2C address " + address, e);
}
outputDevice.write(newState);
this.states[device] = newState;
}
}

View File

@ -0,0 +1,96 @@
package li.strolch.plc.core.hw.i2c;
import static li.strolch.utils.helper.ByteHelper.*;
import static li.strolch.utils.helper.StringHelper.toHexString;
import java.io.IOException;
import com.pi4j.io.i2c.I2CDevice;
import li.strolch.plc.core.hw.Plc;
public class TCA9534OutputConnection extends Multi8BitI2cOutputConnection {
private static final byte TCA9534_REG_ADDR_OUT_PORT = 0x01;
private static final byte TCA9534_REG_ADDR_CFG = 0x03;
public TCA9534OutputConnection(Plc plc, String id) {
super(plc, id);
}
@Override
public String getName() {
return "TCA9534";
}
@Override
protected boolean setup() throws IOException {
boolean ok = super.setup();
if (ok)
return true;
handleBrokenConnection("Failed to configure " + getDescription(), null);
return false;
}
protected boolean setup(byte address, int index, I2CDevice i2cDev) throws IOException {
boolean ok = true;
// first read configuration
int config = i2cDev.read(TCA9534_REG_ADDR_CFG);
if (config < 0)
throw new IllegalStateException(
"Failed to read configuration from address 0x" + toHexString(TCA9534_REG_ADDR_CFG));
if (config != 0x00) {
logger.warn(getDescription(address) + " is not configured as OUTPUT, setting register 0x" + toHexString(
TCA9534_REG_ADDR_CFG) + " to 0x00");
i2cDev.write(TCA9534_REG_ADDR_OUT_PORT, (byte) 0x00);
i2cDev.write(TCA9534_REG_ADDR_CFG, (byte) 0x00);
}
if (this.resetOnConnect) {
// default is all outputs off, i.e. 0
this.states[index] = (byte) 0x00;
try {
i2cDev.write(TCA9534_REG_ADDR_OUT_PORT, this.states[index]);
logger.info("Set initial value to " + asBinary((byte) 0x00) + " for " + getDescription(address));
} catch (Exception e) {
ok = false;
logger.error(
"Failed to set initial value to " + asBinary((byte) 0x00) + " for " + getDescription(address),
e);
}
} else {
byte currentState = (byte) i2cDev.read(TCA9534_REG_ADDR_OUT_PORT);
if (this.reversed)
currentState = reverse(currentState);
this.states[index] = currentState;
logger.info("Initial value is " + asBinary(this.states[index]) + " for " + getDescription(address));
}
return ok;
}
@Override
protected void setPin(int device, int pin, I2CDevice outputDevice, boolean high) throws IOException {
byte newState;
if (high)
newState = setBit(this.states[device], pin);
else
newState = clearBit(this.states[device], pin);
byte writeState = this.reversed ? reverse(newState) : newState;
if (this.verbose)
logger.info("Setting " + getDescription((byte) outputDevice.getAddress()) + " to new state " + asBinary(
writeState));
outputDevice.write(TCA9534_REG_ADDR_OUT_PORT, writeState);
this.states[device] = newState;
}
}