diff --git a/strolch-plc-core/src/main/java/li/strolch/plc/core/hw/i2c/Multi8BitI2cOutputConnection.java b/strolch-plc-core/src/main/java/li/strolch/plc/core/hw/i2c/Multi8BitI2cOutputConnection.java new file mode 100644 index 0000000..e839a7e --- /dev/null +++ b/strolch-plc-core/src/main/java/li/strolch/plc/core/hw/i2c/Multi8BitI2cOutputConnection.java @@ -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 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 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 addressList = (List) 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 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 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; +} \ No newline at end of file diff --git a/strolch-plc-core/src/main/java/li/strolch/plc/core/hw/i2c/PCF8574OutputConnection.java b/strolch-plc-core/src/main/java/li/strolch/plc/core/hw/i2c/PCF8574OutputConnection.java index b761be3..aa106d8 100644 --- a/strolch-plc-core/src/main/java/li/strolch/plc/core/hw/i2c/PCF8574OutputConnection.java +++ b/strolch-plc-core/src/main/java/li/strolch/plc/core/hw/i2c/PCF8574OutputConnection.java @@ -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 positionsByAddress; +public class PCF8574OutputConnection extends Multi8BitI2cOutputConnection { public PCF8574OutputConnection(Plc plc, String id) { super(plc, id); } @Override - public void initialize(Map 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 addressList = (List) 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 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 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; } } \ No newline at end of file diff --git a/strolch-plc-core/src/main/java/li/strolch/plc/core/hw/i2c/TCA9534OutputConnection.java b/strolch-plc-core/src/main/java/li/strolch/plc/core/hw/i2c/TCA9534OutputConnection.java new file mode 100644 index 0000000..15a01c0 --- /dev/null +++ b/strolch-plc-core/src/main/java/li/strolch/plc/core/hw/i2c/TCA9534OutputConnection.java @@ -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; + } +} \ No newline at end of file