diff --git a/org.openhab.binding.simatic/pom.xml b/org.openhab.binding.simatic/pom.xml index 3decfd5..7090091 100644 --- a/org.openhab.binding.simatic/pom.xml +++ b/org.openhab.binding.simatic/pom.xml @@ -7,7 +7,7 @@ org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 3.1.0-SNAPSHOT + 3.2.0-SNAPSHOT org.openhab.binding.simatic diff --git a/org.openhab.binding.simatic/src/main/history/dependencies.xml b/org.openhab.binding.simatic/src/main/history/dependencies.xml index a5b9005..27d596e 100644 --- a/org.openhab.binding.simatic/src/main/history/dependencies.xml +++ b/org.openhab.binding.simatic/src/main/history/dependencies.xml @@ -1,9 +1,9 @@ - + openhab-runtime-base wrap - mvn:org.openhab.addons.bundles/org.openhab.binding.simatic/3.1.0-SNAPSHOT + mvn:org.openhab.addons.bundles/org.openhab.binding.simatic/3.2.0-SNAPSHOT wrap:mvn:org.lastnpe.eea/eea-all/2.2.1 diff --git a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/config/simaticBridgeConfiguration.java b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/config/simaticBridgeConfiguration.java index 376f575..3593401 100644 --- a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/config/simaticBridgeConfiguration.java +++ b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/config/simaticBridgeConfiguration.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.simatic.internal.config; +import org.openhab.binding.simatic.internal.simatic.SimaticUpdateMode; + /** * The {@link SimaticBridgeConfiguration} class contains fields mapping thing configuration parameters. * @@ -53,4 +55,19 @@ public class SimaticBridgeConfiguration { * Device poll rate */ public int pollRate = 1000; + + /** + * Value update mode (OC,PL) + */ + public String updateMode = "OnChange"; + + /** + * Get Value Update Mode + * + * @return Return Update mode + */ + public SimaticUpdateMode getUpdateMode() { + return SimaticUpdateMode.valueOf(updateMode); + } + } diff --git a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/handler/simaticBridgeHandler.java b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/handler/simaticBridgeHandler.java index 700234d..20eb090 100644 --- a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/handler/simaticBridgeHandler.java +++ b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/handler/simaticBridgeHandler.java @@ -24,6 +24,7 @@ import org.openhab.binding.simatic.internal.simatic.SimaticGenericDevice; import org.openhab.binding.simatic.internal.simatic.SimaticTCP; import org.openhab.binding.simatic.internal.simatic.SimaticTCP200; +import org.openhab.binding.simatic.internal.simatic.SimaticUpdateMode; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; @@ -96,9 +97,10 @@ public void initialize() { config = getConfigAs(SimaticBridgeConfiguration.class); - logger.debug("{} - Bridge configuration: Host/IP={},Rack={},Slot={},Comm={},Is200={},Charset={},PollRate={}", + logger.debug( + "{} - Bridge configuration: Host/IP={},Rack={},Slot={},Comm={},Is200={},Charset={},PollRate={},Mode={}", getThing().getLabel(), config.address, config.rack, config.slot, config.communicationType, - config.isS7200, config.charset, config.pollRate); + config.isS7200, config.charset, config.pollRate, config.updateMode); // configuration validation boolean valid = true; @@ -106,28 +108,24 @@ public void initialize() { if (config.address == null || config.address.isBlank()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No Host/IP address"); valid = false; - return; } - if (config.rack < 0 || config.rack > 2) { + if (valid && (config.rack < 0 || config.rack > 2)) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid Rack number. Valid is 0-2."); valid = false; - return; } - if (config.slot < 0 || config.slot > 15) { + if (valid && (config.slot < 0 || config.slot > 15)) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid Slot number. Valid is 0-15."); valid = false; - return; } - if (config.communicationType == null || !(config.communicationType.equals("S7") - || config.communicationType.equals("PG") || config.communicationType.equals("OP"))) { + if (valid && (config.communicationType == null || !(config.communicationType.equals("S7") + || config.communicationType.equals("PG") || config.communicationType.equals("OP")))) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid communication type."); valid = false; - return; } if (config.pollRate <= 0) { @@ -136,6 +134,19 @@ public void initialize() { getThing().getLabel()); } + if (valid && (config.updateMode == null || !SimaticUpdateMode.validate(config.updateMode))) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid value update mode."); + valid = false; + } + + if (!valid) { + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + logger.error("{} - Bridge configuration is invalid. Host/IP={},Rack={},Slot={},Comm={},Is200={},Mode={}", + getThing().getLabel(), config.address, config.rack, config.slot, config.communicationType, + config.isS7200, config.updateMode); + return; + } + Charset charset; if (config.charset == null || config.charset.isBlank()) { charset = Charset.defaultCharset(); @@ -149,19 +160,13 @@ public void initialize() { logger.info("{} - Current charset {}", getThing().getLabel(), charset.name()); - if (!valid) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); - logger.error("{} - Bridge configuration is invalid. Host/IP={},Rack={},Slot={},Comm={},Is200={}", - getThing().getLabel(), config.address, config.rack, config.slot, config.communicationType, - config.isS7200); - } - // S7-200 PLC if (config.isS7200) { - connection = new SimaticTCP200(config.address, config.rack, config.slot, config.pollRate, charset); + connection = new SimaticTCP200(config.address, config.rack, config.slot, config.pollRate, charset, + SimaticUpdateMode.fromString(config.updateMode)); } else { connection = new SimaticTCP(config.address, config.rack, config.slot, config.communicationType, - config.pollRate, charset); + config.pollRate, charset, SimaticUpdateMode.fromString(config.updateMode)); } // react on connection changes diff --git a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/handler/simaticGenericHandler.java b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/handler/simaticGenericHandler.java index 12536fe..def292a 100644 --- a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/handler/simaticGenericHandler.java +++ b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/handler/simaticGenericHandler.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.simatic.internal.handler; +import java.nio.charset.Charset; import java.util.LinkedHashMap; import java.util.Map; @@ -19,6 +20,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.simatic.internal.simatic.SimaticChannel; import org.openhab.binding.simatic.internal.simatic.SimaticGenericDevice; +import org.openhab.binding.simatic.internal.simatic.SimaticUpdateMode; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -71,8 +73,8 @@ public void initialize() { } final SimaticChannel chConfig = channel.getConfiguration().as(SimaticChannel.class); - chConfig.channelId = channelUID; - chConfig.channelType = channelTypeUID; + chConfig.setChannelId(channelUID); + chConfig.setChannelType(channelTypeUID); if (!chConfig.init(this)) { errors++; @@ -218,6 +220,9 @@ public void setError(String message) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message); } + /** + * Clear thing error if necessary + */ public void clearError() { // no error if (getThing().getStatus() == ThingStatus.ONLINE) { @@ -229,4 +234,30 @@ public void clearError() { updateStatus(ThingStatus.ONLINE); } } + + /** + * Get configured code page + * + * @return + */ + public Charset getCharset() { + if (connection != null) { + return connection.getCharset(); + } + + return Charset.defaultCharset(); + } + + /** + * Get configured update mode + * + * @return + */ + public SimaticUpdateMode getUpdateMode() { + if (connection != null) { + return connection.getUpdateMode(); + } + + return SimaticUpdateMode.OnChange; + } } diff --git a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticChannel.java b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticChannel.java index 8de7092..cbf3cbc 100644 --- a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticChannel.java +++ b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticChannel.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.simatic.internal.simatic; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -20,6 +22,13 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.simatic.internal.SimaticBindingConstants; import org.openhab.binding.simatic.internal.handler.SimaticGenericHandler; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.State; @@ -35,9 +44,9 @@ public class SimaticChannel { private static final Logger logger = LoggerFactory.getLogger(SimaticChannel.class); /** Channel ID */ - public ChannelUID channelId; + private ChannelUID channelId; /** ChannelType ID */ - public ChannelTypeUID channelType; + private ChannelTypeUID channelType; /** State string address */ public String stateAddress; /** Command string address */ @@ -59,6 +68,7 @@ public class SimaticChannel { private boolean missingCommandReported = false; private Unit unitInstance = null; private boolean unitExists = false; + private boolean chContact, chColor, chDimmer, chNumber, chRollershutter, chString, chSwitch; final private static Pattern numberAddressPattern = Pattern.compile( "^(([IQAEM][BW])(\\d+))$|^(([IQAEM]D)(\\d+)(F?))$|^(DB(\\d+)\\.DB([BW])(\\d+))$|^(DB(\\d+)\\.DB(D)(\\d+)(F?))$|^(([IQAEM])(\\d+)\\.([0-7]))$|^(DB(\\d+)\\.DBX(\\d+)\\.([0-7]))$"); @@ -85,7 +95,7 @@ public String toString() { * Initialize channel from thing configuration * * @param handler Thing handler - * @return True if initialization is OK. When initialization not succeed, reason can be obtaion by getError() + * @return True if initialization is OK. When initialization not succeed, reason can be obtain by getError() */ public boolean init(SimaticGenericHandler handler) { missingCommandReported = false; @@ -354,19 +364,117 @@ public boolean hasUnit() { return unitExists; } + /** + * Set state from incoming data + * + * @param buffer Incoming data + * @param position Data start position in buffer + */ + public void setState(byte[] buffer, int start) { + // logger.debug("item={}", toString()); + // logger.debug("buffer={}", buffer.length); + // logger.debug("position={}", position); + // logger.debug("item len={}", getStateAddress().getDataLength()); + + int position = getStateAddress().getByteOffset() - start; + + try { + ByteBuffer bb = ByteBuffer.wrap(buffer, position, getStateAddress().getDataLength()); + bb.order(ByteOrder.BIG_ENDIAN); + + if (isString()) { + // check for '\0' char and resolve string length + int i; + for (i = position; i < getStateAddress().getDataLength(); i++) { + if (buffer[i] == 0) { + break; + } + } + String str = new String(buffer, position, i, thing.getCharset()); + setState(new StringType(str)); + } else if (isNumber()) { + if (getStateAddress().isFloat()) { + setState(hasUnit() ? new QuantityType<>(bb.getFloat(), getUnit()) : new DecimalType(bb.getFloat())); + } else { + final int intValue; + if (getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.BIT) { + intValue = (bb.get() & (int) Math.pow(2, getStateAddress().getBitOffset())) != 0 ? 1 : 0; + } else if (getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.BYTE) { + intValue = bb.get(); + } else if (getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.WORD) { + intValue = bb.getShort(); + } else if (getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.DWORD) { + intValue = bb.getInt(); + } else { + intValue = 0; + } + + setState(hasUnit() ? new QuantityType<>(intValue, getUnit()) : new DecimalType(intValue)); + } + } else if (isDimmer()) { + setState(new PercentType(bb.get())); + } else if (isContact()) { + if (getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.BIT) { + setState((bb.get() & (int) Math.pow(2, getStateAddress().getBitOffset())) != 0 ? OpenClosedType.OPEN + : OpenClosedType.CLOSED); + } else { + setState((bb.get() != 0) ? OpenClosedType.OPEN : OpenClosedType.CLOSED); + } + } else if (isSwitch()) { + if (getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.BIT) { + setState((bb.get() & (int) Math.pow(2, getStateAddress().getBitOffset())) != 0 ? OnOffType.ON + : OnOffType.OFF); + } else { + setState(bb.get() != 0 ? OnOffType.ON : OnOffType.OFF); + } + } else if (isRollershutter()) { + setState(new PercentType(bb.get())); + } else if (isColor()) { + byte b0 = bb.get(); + byte b1 = bb.get(); + byte b2 = bb.get(); + // byte b3 = bb.get(); + + setState(HSBType.fromRGB(b0 & 0xFF, b1 & 0xFF, b2 & 0xFF)); + } else { + setState(null); + logger.warn("{} - Incoming data channel {} - Unsupported channel type {}.", toString(), channelId, + channelType.getId()); + return; + } + } catch (Exception ex) { + logger.error("{} - Incoming data post error. Item:{}", toString(), channelId, ex); + } + + /* + * if (logger.isTraceEnabled()) { + * logger.trace("{} - Incoming data - item:{}/state:{}", toString(), channelId, getState()); + * } + */ + } + /** * Set last channel state * * @param state */ public void setState(State state) { - value = state; + // not thing no update if (thing == null) { return; } + // clear thing errors + clearError(); + // in OnChange mode, only changed values are forwarded + if (thing.getUpdateMode() == SimaticUpdateMode.OnChange && value != null && value.equals(state)) { + return; + } + // store value internally + value = state; + // sent value into OH core thing.updateState(channelId, state); + // mark update time setValueUpdateTime(System.currentTimeMillis()); - clearError(); } /** @@ -435,4 +543,124 @@ public boolean isMissingCommandReported() { } return true; } + + /** + * Set channel ID + * + * @param channelUID ChannelUID + */ + public void setChannelId(ChannelUID channelUID) { + this.channelId = channelUID; + } + + /** + * Set channel type + * + * @param channelTypeUID ChannelTypeUID + */ + public void setChannelType(ChannelTypeUID channelTypeUID) { + this.channelType = channelTypeUID; + + chContact = chColor = chDimmer = chNumber = chRollershutter = chString = chSwitch = false; + switch (channelType.getId()) { + case SimaticBindingConstants.CHANNEL_CONTACT: + chContact = true; + break; + case SimaticBindingConstants.CHANNEL_COLOR: + chColor = true; + break; + case SimaticBindingConstants.CHANNEL_DIMMER: + chDimmer = true; + break; + case SimaticBindingConstants.CHANNEL_NUMBER: + chNumber = true; + break; + case SimaticBindingConstants.CHANNEL_ROLLERSHUTTER: + chRollershutter = true; + break; + case SimaticBindingConstants.CHANNEL_STRING: + chString = true; + break; + case SimaticBindingConstants.CHANNEL_SWITCH: + chSwitch = true; + break; + } + } + + /** + * Get channel ID + */ + public ChannelUID getChannelId() { + return channelId; + } + + /** + * Get channel type + */ + public ChannelTypeUID getChannelType() { + return channelType; + } + + /** + * Check Contact channel type + * + * @return True if channel has that type + */ + public boolean isContact() { + return chContact; + } + + /** + * Check Color channel type + * + * @return True if channel has that type + */ + public boolean isColor() { + return chColor; + } + + /** + * Check Dimmer channel type + * + * @return True if channel has that type + */ + public boolean isDimmer() { + return chDimmer; + } + + /** + * Check Number channel type + * + * @return True if channel has that type + */ + public boolean isNumber() { + return chNumber; + } + + /** + * Check Rollershutter channel type + * + * @return True if channel has that type + */ + public boolean isRollershutter() { + return chRollershutter; + } + + /** + * Check String channel type + * + * @return True if channel has that type + */ + public boolean isString() { + return chString; + } + + /** + * Check Switch channel type + * + * @return True if channel has that type + */ + public boolean isSwitch() { + return chSwitch; + } } \ No newline at end of file diff --git a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticGenericDevice.java b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticGenericDevice.java index a4b0b75..c743baa 100644 --- a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticGenericDevice.java +++ b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticGenericDevice.java @@ -8,8 +8,6 @@ */ package org.openhab.binding.simatic.internal.simatic; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; @@ -26,16 +24,8 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.simatic.internal.SimaticBindingConstants; import org.openhab.binding.simatic.internal.simatic.SimaticPortState.PortStates; import org.openhab.core.common.ThreadPoolManager; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.HSBType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.OpenClosedType; -import org.openhab.core.library.types.PercentType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.types.StringType; import org.openhab.core.types.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,7 +38,7 @@ */ public class SimaticGenericDevice implements SimaticIDevice { private static final Logger logger = LoggerFactory.getLogger(SimaticGenericDevice.class); - private static final String THING_HANDLER_THREADPOOL_NAME = "thingHandler"; + private static final String THING_HANDLER_THREADPOOL_NAME = "Simatic"; protected final ScheduledExecutorService scheduler = ThreadPoolManager .getScheduledPool(THING_HANDLER_THREADPOOL_NAME); @@ -78,6 +68,7 @@ public class SimaticGenericDevice implements SimaticIDevice { /** PDU size **/ protected int pduSize = 0; protected final Charset charset; + protected final SimaticUpdateMode updateMode; protected boolean disposed = false; @@ -106,8 +97,9 @@ public enum ProcessDataResult { * Constructor * */ - public SimaticGenericDevice(int pollRate, Charset charset) { + public SimaticGenericDevice(int pollRate, Charset charset, SimaticUpdateMode updateMode) { this.charset = charset; + this.updateMode = updateMode; if (pollRate > 0) { periodicJob = scheduler.scheduleAtFixedRate(() -> { if (!reconnecting.get()) { @@ -155,10 +147,7 @@ protected void execute() { if (shouldReconnect()) { reconnectWithDelaying(); } - if (isConnected()) { - // check device for new data - checkNewData(); - } + checkNewData(); } @Override @@ -286,7 +275,7 @@ public void sendData(SimaticChannel item, Command command) { var area = SimaticWriteDataArea.create(command, item, pduSize, charset); sendData(area); } catch (Exception ex) { - logger.error("{} - ChannelUID={}. {}.", toString(), item.channelId, ex.getMessage(), ex); + logger.error("{} - ChannelUID={}. {}.", toString(), item.getChannelId(), ex.getMessage(), ex); } } @@ -406,18 +395,20 @@ protected void checkNewData() { return; } - if (logger.isTraceEnabled()) { - logger.trace("{} - checkNewData() is called", toString()); - } - + /* + * if (logger.isTraceEnabled()) { + * logger.trace("{} - checkNewData() is called", toString()); + * } + */ if (!readLock.tryLock()) { if (logger.isDebugEnabled()) { logger.debug("{} - Reading already in progress", toString()); } return; } - - logger.trace("{} - Locking", toString()); + /* + * logger.trace("{} - Locking", toString()); + */ try { for (SimaticReadDataArea area : readAreasList.getData()) { @@ -438,14 +429,18 @@ protected void checkNewData() { readed++; readedBytes += area.getAddressSpaceLength(); - if (logger.isDebugEnabled()) { - logger.debug("{} - Reading finished. Area={}", toString(), area.toString()); - } + /* + * if (logger.isDebugEnabled()) { + * logger.debug("{} - Reading finished. Area={}", toString(), area.toString()); + * } + */ } } catch (Exception ex) { logger.error("{} - Read data error", toString(), ex); } finally { - logger.trace("{} - Unlocking", toString()); + /* + * logger.trace("{} - Unlocking", toString()); + */ readLock.unlock(); long diff; @@ -487,7 +482,7 @@ public int compare(SimaticChannel o1, SimaticChannel o2) { return 1; } else { return 0; - } + } } }); @@ -545,104 +540,6 @@ public int compare(SimaticChannel o1, SimaticChannel o2) { readLock.unlock(); } - /** - * Method decode incoming data and on success post this into openHAB - * - * @param item - * @param buffer - * @param position - */ - @SuppressWarnings("null") - public void postValue(SimaticChannel item, byte[] buffer, int position) { - // logger.debug("item={}", item.toString()); - // logger.debug("buffer={}", buffer.length); - // logger.debug("position={}", position); - // logger.debug("item len={}", item.getStateAddress().getDataLength()); - try { - ByteBuffer bb = ByteBuffer.wrap(buffer, position, item.getStateAddress().getDataLength()); - - // no byte swap for array - // if (item.datatype == SimaticTypes.ARRAY) { - bb.order(ByteOrder.BIG_ENDIAN); - // } else { - // bb.order(ByteOrder.LITTLE_ENDIAN); - // } - - if (item.channelType.getId().equals(SimaticBindingConstants.CHANNEL_STRING)) { - // check for '\0' char and resolve string length - int i; - for (i = position; i < item.getStateAddress().getDataLength(); i++) { - if (buffer[i] == 0) { - break; - } - } - String str = new String(buffer, position, i, charset); - item.setState(new StringType(str)); - - } else if (item.channelType.getId().equals(SimaticBindingConstants.CHANNEL_NUMBER)) { - if (item.getStateAddress().isFloat()) { - item.setState(item.hasUnit() ? new QuantityType<>(bb.getFloat(), item.getUnit()) - : new DecimalType(bb.getFloat())); - } else { - final int intValue; - if (item.getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.BIT) { - intValue = (bb.get() & (int) Math.pow(2, item.getStateAddress().getBitOffset())) != 0 ? 1 : 0; - } else if (item.getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.BYTE) { - intValue = bb.get(); - } else if (item.getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.WORD) { - intValue = bb.getShort(); - } else if (item.getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.DWORD) { - intValue = bb.getInt(); - } else { - intValue = 0; - } - - item.setState( - item.hasUnit() ? new QuantityType<>(intValue, item.getUnit()) : new DecimalType(intValue)); - } - } else if (item.channelType.getId().equals(SimaticBindingConstants.CHANNEL_DIMMER)) { - item.setState(new PercentType(bb.get())); - } else if (item.channelType.getId().equals(SimaticBindingConstants.CHANNEL_CONTACT)) { - if (item.getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.BIT) { - item.setState((bb.get() & (int) Math.pow(2, item.getStateAddress().getBitOffset())) != 0 - ? OpenClosedType.OPEN - : OpenClosedType.CLOSED); - } else { - item.setState((bb.get() != 0) ? OpenClosedType.OPEN : OpenClosedType.CLOSED); - } - } else if (item.channelType.getId().equals(SimaticBindingConstants.CHANNEL_SWITCH)) { - if (item.getStateAddress().getSimaticDataType() == SimaticPLCDataTypes.BIT) { - item.setState( - (bb.get() & (int) Math.pow(2, item.getStateAddress().getBitOffset())) != 0 ? OnOffType.ON - : OnOffType.OFF); - } else { - item.setState(bb.get() != 0 ? OnOffType.ON : OnOffType.OFF); - } - } else if (item.channelType.getId().equals(SimaticBindingConstants.CHANNEL_ROLLERSHUTTER)) { - item.setState(new PercentType(bb.get())); - } else if (item.channelType.getId().equals(SimaticBindingConstants.CHANNEL_COLOR)) { - byte b0 = bb.get(); - byte b1 = bb.get(); - byte b2 = bb.get(); - // byte b3 = bb.get(); - - item.setState(HSBType.fromRGB(b0 & 0xFF, b1 & 0xFF, b2 & 0xFF)); - } else { - item.setState(null); - logger.warn("{} - Incoming data channel {} - Unsupported channel type {}.", toString(), item.channelId, - item.channelType.getId()); - return; - } - } catch (Exception ex) { - logger.error("{} - Incoming data post error. Item:{}/state:{}.", toString(), item.channelId, - item.getState(), ex); - } - - if (logger.isTraceEnabled()) { - logger.trace("{} - Incoming data - item:{}/state:{}", toString(), item.channelId, item.getState()); - } - } - public boolean shouldReconnect() { return tryReconnect.get(); } @@ -690,4 +587,12 @@ public static String arrayToString(byte[] data, int length) { return s.toString(); } + + public Charset getCharset() { + return charset; + } + + public SimaticUpdateMode getUpdateMode() { + return updateMode; + } } diff --git a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticTCP.java b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticTCP.java index a509d09..03668f8 100644 --- a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticTCP.java +++ b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticTCP.java @@ -32,18 +32,22 @@ public class SimaticTCP extends SimaticGenericDevice { private static final Logger logger = LoggerFactory.getLogger(SimaticTCP.class); /** address */ - protected String plcAddress = ""; + protected final String plcAddress; /** rack/slot */ protected final int rack, slot, communicationType; - Socket sock; - PLCinterface di; - TCPConnection dc; - OutputStream oStream = null; - InputStream iStream = null; + protected Socket sock; + protected PLCinterface di; + protected TCPConnection dc; + protected OutputStream oStream = null; + protected InputStream iStream = null; - /** server socket instance */ - // private AsynchronousServerSocketChannel listener; + /** procedure start time **/ + private long startTime; + /** read data buffer **/ + byte[] buffer = null; + /** function result **/ + int rResult, wResult; /** * Constructor @@ -53,9 +57,10 @@ public class SimaticTCP extends SimaticGenericDevice { * @param slot * @param pollRate * @param charset + * @param updateMode */ - public SimaticTCP(String address, int rack, int slot, int pollRate, Charset charset) { - super(pollRate, charset); + public SimaticTCP(String address, int rack, int slot, int pollRate, Charset charset, SimaticUpdateMode updateMode) { + super(pollRate, charset, updateMode); this.plcAddress = address; this.rack = rack; @@ -73,9 +78,11 @@ public SimaticTCP(String address, int rack, int slot, int pollRate, Charset char * @param communicationType * @param pollRate * @param charset + * @param updateMode */ - public SimaticTCP(String address, int rack, int slot, String communicationType, int pollRate, Charset charset) { - super(pollRate, charset); + public SimaticTCP(String address, int rack, int slot, String communicationType, int pollRate, Charset charset, + SimaticUpdateMode updateMode) { + super(pollRate, charset, updateMode); this.plcAddress = address; this.rack = rack; @@ -214,13 +221,13 @@ protected boolean sendDataOut(SimaticWriteDataArea data) { } try { - int result = dc.writeBytes(data.getAreaIntFormat(), data.getDBNumber(), data.getStartAddress(), + wResult = dc.writeBytes(data.getAreaIntFormat(), data.getDBNumber(), data.getStartAddress(), data.getAddressSpaceLength(), data.getData()); - if (result != 0) { + if (wResult != 0) { logger.error("{} - Write data area error (Area={}, Result=0x{}, Error={})", toString(), - data.toString(), Integer.toHexString(result), Nodave.strerror(result)); + data.toString(), Integer.toHexString(wResult), Nodave.strerror(wResult)); - if (result == Nodave.RESULT_UNEXPECTED_FUNC) { + if (wResult == Nodave.RESULT_UNEXPECTED_FUNC) { tryReconnect.set(true); } return false; @@ -282,12 +289,13 @@ protected boolean sendDataOut(SimaticWriteDataArea data) { @SuppressWarnings("null") @Override public void readDataArea(SimaticReadDataArea area) throws SimaticReadException { - long startTime = System.currentTimeMillis(); - byte[] buffer = new byte[area.getAddressSpaceLength()]; + startTime = System.currentTimeMillis(); + if (buffer == null || buffer.length < area.getAddressSpaceLength()) { + buffer = new byte[area.getAddressSpaceLength()]; + } - int result; try { - result = dc.readBytes(area.getAreaIntFormat(), area.getDBNumber(), area.getStartAddress(), + rResult = dc.readBytes(area.getAreaIntFormat(), area.getDBNumber(), area.getStartAddress(), area.getAddressSpaceLength(), buffer); } catch (IOException ex) { if (isConnected()) { @@ -297,11 +305,11 @@ public void readDataArea(SimaticReadDataArea area) throws SimaticReadException { throw new SimaticReadException(area, ex); } - if (result != 0) { + if (rResult != 0) { String message = String.format("Read data area error (Area=%s, Return code=0x%s, Error=%s})", - area.toString(), Integer.toHexString(result), Nodave.strerror(result)); - if (result == Nodave.RESULT_UNEXPECTED_FUNC || result == Nodave.RESULT_READ_DATA_BUFFER_INSUFFICIENT_SPACE - || result == Nodave.RESULT_NO_DATA_RETURNED) { + area.toString(), Integer.toHexString(rResult), Nodave.strerror(rResult)); + if (rResult == Nodave.RESULT_UNEXPECTED_FUNC || rResult == Nodave.RESULT_READ_DATA_BUFFER_INSUFFICIENT_SPACE + || rResult == Nodave.RESULT_NO_DATA_RETURNED) { if (isConnected()) { portState.setState(PortStates.RESPONSE_ERROR); tryReconnect.set(true); @@ -317,12 +325,11 @@ public void readDataArea(SimaticReadDataArea area) throws SimaticReadException { } } - int start = area.getStartAddress(); long response = System.currentTimeMillis() - startTime; // get data for all items in area for (SimaticChannel item : area.getItems()) { // send value into openHAB - postValue(item, buffer, item.getStateAddress().getByteOffset() - start); + item.setState(buffer, area.getStartAddress()); } } diff --git a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticTCP200.java b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticTCP200.java index f87c4fe..6cc505b 100644 --- a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticTCP200.java +++ b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticTCP200.java @@ -36,9 +36,12 @@ public class SimaticTCP200 extends SimaticTCP { * @param slot * @param charset * @param pollRate + * @param charset + * @param updateMode */ - public SimaticTCP200(String address, int rack, int slot, int pollRate, Charset charset) { - super(address, rack, slot, pollRate, charset); + public SimaticTCP200(String address, int rack, int slot, int pollRate, Charset charset, + SimaticUpdateMode updateMode) { + super(address, rack, slot, pollRate, charset, updateMode); } /** diff --git a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticUpdateMode.java b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticUpdateMode.java new file mode 100644 index 0000000..7db6b65 --- /dev/null +++ b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticUpdateMode.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.simatic.internal.simatic; + +/** + * + * Value Update Mode enumeration + * + * @author Vita Tucek + * @since 3.2.0 + */ +public enum SimaticUpdateMode { + OnChange, + Poll; + + public static boolean validate(String mode) { + return mode.equals("OnChange") || mode.equals("Poll"); + } + + public static SimaticUpdateMode fromString(String mode) { + if (!validate(mode)) { + return OnChange; + } + return SimaticUpdateMode.valueOf(mode); + } +} diff --git a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticWriteDataArea.java b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticWriteDataArea.java index 647b178..dd131e1 100644 --- a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticWriteDataArea.java +++ b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simatic/SimaticWriteDataArea.java @@ -11,7 +11,6 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; -import org.openhab.binding.simatic.internal.SimaticBindingConstants; import org.openhab.binding.simatic.internal.libnodave.Nodave; import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.OnOffType; @@ -49,11 +48,11 @@ public static SimaticWriteDataArea create(Command command, SimaticChannel channe byte[] data = null; - if (channel.channelType.getId().equals(SimaticBindingConstants.CHANNEL_NUMBER)) { + if (channel.isNumber()) { if (!(command instanceof Number)) { throw new Exception(String.format( "Cannot create WriteDataArea. Command for ChannelType=%s must be DecimalType. It is %s (%s)", - channel.channelType.getId(), command.getClass().getSimpleName(), + channel.getChannelType().getId(), command.getClass().getSimpleName(), command.getClass().getGenericSuperclass().getTypeName())); } Number cmd = (Number) command; @@ -75,14 +74,14 @@ public static SimaticWriteDataArea create(Command command, SimaticChannel channe } else { throw new Exception(String.format( "Cannot create WriteDataArea. Command for ChannelType=%s has unsupported datatype=%s", - channel.channelType.getId(), address.getSimaticDataType())); + channel.getChannelType().getId(), address.getSimaticDataType())); } - } else if (channel.channelType.getId().equals(SimaticBindingConstants.CHANNEL_STRING)) { + } else if (channel.isString()) { if (!(command instanceof StringType)) { throw new Exception( String.format("Cannot create WriteDataArea. Command for ChannelType=%s must be StringType", - channel.channelType.getId())); + channel.getChannelType().getId())); } StringType cmd = (StringType) command; String str = cmd.toString(); @@ -98,11 +97,11 @@ public static SimaticWriteDataArea create(Command command, SimaticChannel channe data[i] = 0x0; } } - } else if (channel.channelType.getId().equals(SimaticBindingConstants.CHANNEL_SWITCH)) { + } else if (channel.isSwitch()) { if (!(command instanceof OnOffType)) { throw new Exception( String.format("Cannot create WriteDataArea. Command for ChannelType=%s must be OnOffType", - channel.channelType.getId())); + channel.getChannelType().getId())); } OnOffType cmd = (OnOffType) command; @@ -112,11 +111,11 @@ public static SimaticWriteDataArea create(Command command, SimaticChannel channe } else { data = new byte[] { 0 }; } - } else if (channel.channelType.getId().equals(SimaticBindingConstants.CHANNEL_CONTACT)) { + } else if (channel.isContact()) { if (!(command instanceof OpenClosedType)) { throw new Exception( String.format("Cannot create WriteDataArea. Command for ChannelType=%s must be OpenClosedType", - channel.channelType.getId())); + channel.getChannelType().getId())); } OpenClosedType cmd = (OpenClosedType) command; @@ -126,7 +125,7 @@ public static SimaticWriteDataArea create(Command command, SimaticChannel channe } else { data = new byte[] { 0 }; } - } else if (channel.channelType.getId().equals(SimaticBindingConstants.CHANNEL_COLOR)) { + } else if (channel.isColor()) { if (command instanceof HSBType) { long red = Math.round((((HSBType) command).getRed().doubleValue() * 2.55)); long green = Math.round((((HSBType) command).getGreen().doubleValue() * 2.55)); @@ -146,9 +145,9 @@ public static SimaticWriteDataArea create(Command command, SimaticChannel channe } else { throw new Exception( String.format("Cannot create WriteDataArea. Command %s for ChannelType=%s not implemented", - command.getClass(), channel.channelType.getId())); + command.getClass(), channel.getChannelType().getId())); } - } else if (channel.channelType.getId().equals(SimaticBindingConstants.CHANNEL_DIMMER)) { + } else if (channel.isDimmer()) { if (command instanceof PercentType) { data = new byte[] { ((PercentType) command).byteValue() }; } else if (command instanceof OnOffType) { @@ -160,9 +159,9 @@ public static SimaticWriteDataArea create(Command command, SimaticChannel channe } else { throw new Exception( String.format("Cannot create WriteDataArea. Command %s for ChannelType=%s not implemented", - command.getClass(), channel.channelType.getId())); + command.getClass(), channel.getChannelType().getId())); } - } else if (channel.channelType.getId().equals(SimaticBindingConstants.CHANNEL_ROLLERSHUTTER)) { + } else if (channel.isRollershutter()) { if (command instanceof StopMoveType) { if (address.getSimaticDataType() == SimaticPLCDataTypes.WORD) { data = new byte[] { (byte) (((StopMoveType) command).equals(StopMoveType.MOVE) ? 0x1 : 0x2), 0 }; @@ -181,17 +180,17 @@ public static SimaticWriteDataArea create(Command command, SimaticChannel channe } else { throw new Exception(String.format( "Cannot create WriteDataArea. Command %s ChannelType=%s need for position set command address length of WORD.", - command.getClass(), channel.channelType.getId())); + command.getClass(), channel.getChannelType().getId())); } } else { throw new Exception( String.format("Cannot create WriteDataArea. Command %s for ChannelType=%s not implemented", - command.getClass(), channel.channelType.getId())); + command.getClass(), channel.getChannelType().getId())); } } else { throw new Exception( String.format("Cannot create WriteDataArea. Command for ChannelType=%s not implemented.", - channel.channelType.getId())); + channel.getChannelType().getId())); } return new SimaticWriteDataArea(address, data, pduSize); diff --git a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simaticBindingConstants.java b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simaticBindingConstants.java index bc524f7..6d01361 100644 --- a/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simaticBindingConstants.java +++ b/org.openhab.binding.simatic/src/main/java/org/openhab/binding/simatic/internal/simaticBindingConstants.java @@ -25,7 +25,7 @@ @NonNullByDefault public class SimaticBindingConstants { - public static final String VERSION = "3.1.0"; + public static final String VERSION = "3.2.0-beta.1"; private static final String BINDING_ID = "simatic"; diff --git a/org.openhab.binding.simatic/src/main/resources/OH-INF/thing/thing-types.xml b/org.openhab.binding.simatic/src/main/resources/OH-INF/thing/thing-types.xml index c738c7d..8036b7a 100644 --- a/org.openhab.binding.simatic/src/main/resources/OH-INF/thing/thing-types.xml +++ b/org.openhab.binding.simatic/src/main/resources/OH-INF/thing/thing-types.xml @@ -62,6 +62,16 @@ false true + + + Update read value can be OnChange or Poll. In OnChange mode only changed values are sent into OH core (default). In Poll mode every value is sent into OH core (more CPU intensive). + + + + + OnChange + true +