2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.plclogo.internal.handler;
15 import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
17 import java.util.List;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.atomic.AtomicReference;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.plclogo.internal.PLCLogoClient;
26 import org.openhab.binding.plclogo.internal.config.PLCPulseConfiguration;
27 import org.openhab.core.config.core.Configuration;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.OpenClosedType;
31 import org.openhab.core.thing.Bridge;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingTypeUID;
37 import org.openhab.core.thing.binding.builder.ChannelBuilder;
38 import org.openhab.core.thing.binding.builder.ThingBuilder;
39 import org.openhab.core.thing.type.ChannelTypeUID;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.openhab.core.types.State;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
47 import Moka7.S7Client;
50 * The {@link PLCPulseHandler} is responsible for handling commands, which are
51 * sent to one of the channels.
53 * @author Alexander Falkenstern - Initial contribution
56 public class PLCPulseHandler extends PLCCommonHandler {
58 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_PULSE);
60 private final Logger logger = LoggerFactory.getLogger(PLCPulseHandler.class);
61 private AtomicReference<PLCPulseConfiguration> config = new AtomicReference<>();
62 private AtomicReference<@Nullable Boolean> received = new AtomicReference<>();
67 public PLCPulseHandler(Thing thing) {
72 public void handleCommand(ChannelUID channelUID, Command command) {
73 if (!isThingOnline()) {
77 Channel channel = getThing().getChannel(channelUID.getId());
78 String name = getBlockFromChannel(channel);
79 if (!isValid(name) || (channel == null)) {
80 logger.debug("Can not update channel {}, block {}.", channelUID, name);
84 int bit = getBit(name);
85 int address = getAddress(name);
86 PLCLogoClient client = getLogoClient();
87 if ((address != INVALID) && (bit != INVALID) && (client != null)) {
88 byte[] buffer = new byte[1];
89 if (command instanceof RefreshType) {
90 int result = client.readDBArea(1, address, buffer.length, S7Client.S7WLByte, buffer);
92 updateChannel(channel, S7.GetBitAt(buffer, 0, bit));
94 logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
96 } else if ((command instanceof OpenClosedType) || (command instanceof OnOffType)) {
97 String type = channel.getAcceptedItemType();
98 if (DIGITAL_INPUT_ITEM.equalsIgnoreCase(type)) {
99 boolean flag = ((OpenClosedType) command == OpenClosedType.CLOSED);
100 S7.SetBitAt(buffer, 0, 0, flag);
102 } else if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type)) {
103 boolean flag = ((OnOffType) command == OnOffType.ON);
104 S7.SetBitAt(buffer, 0, 0, flag);
107 logger.debug("Channel {} will not accept {} items.", channelUID, type);
109 int result = client.writeDBArea(1, 8 * address + bit, buffer.length, S7Client.S7WLBit, buffer);
111 logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
114 logger.debug("Channel {} received not supported command {}.", channelUID, command);
117 logger.info("Invalid channel {} or client {} found.", channelUID, client);
122 public void setData(final byte[] data) {
123 if (!isThingOnline()) {
127 if (data.length != getBufferLength()) {
128 logger.info("Received and configured data sizes does not match.");
132 List<Channel> channels = thing.getChannels();
133 if (channels.size() != getNumberOfChannels()) {
134 logger.info("Received and configured channel sizes does not match.");
138 PLCLogoClient client = getLogoClient();
139 for (Channel channel : channels) {
140 ChannelUID channelUID = channel.getUID();
141 String name = getBlockFromChannel(channel);
143 int bit = getBit(name);
144 int address = getAddress(name);
145 if ((address != INVALID) && (bit != INVALID) && (client != null)) {
146 DecimalType state = (DecimalType) getOldValue(channelUID.getId());
147 if (STATE_CHANNEL.equalsIgnoreCase(channelUID.getId())) {
148 boolean value = S7.GetBitAt(data, address - getBase(name), bit);
149 if ((state == null) || ((value ? 1 : 0) != state.intValue())) {
150 updateChannel(channel, value);
152 if (logger.isTraceEnabled()) {
153 int buffer = (data[address - getBase(name)] & 0xFF) + 0x100;
154 logger.trace("Channel {} received [{}].", channelUID,
155 Integer.toBinaryString(buffer).substring(1));
157 } else if (OBSERVE_CHANNEL.equalsIgnoreCase(channelUID.getId())) {
158 handleCommand(channelUID, RefreshType.REFRESH);
159 DecimalType current = (DecimalType) getOldValue(channelUID.getId());
160 if ((state != null) && (current.intValue() != state.intValue())) {
161 Integer pulse = config.get().getPulseLength();
162 scheduler.schedule(new Runnable() {
165 Boolean value = received.getAndSet(null);
167 byte[] buffer = new byte[1];
168 S7.SetBitAt(buffer, 0, 0, !value.booleanValue());
169 String block = config.get().getBlockName();
170 int bit = 8 * getAddress(block) + getBit(block);
171 int result = client.writeDBArea(1, bit, buffer.length, S7Client.S7WLBit, buffer);
173 logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
176 logger.debug("Invalid received value on channel {}.", channelUID);
179 }, pulse.longValue(), TimeUnit.MILLISECONDS);
182 logger.info("Invalid channel {} found.", channelUID);
185 logger.info("Invalid channel {} or client {} found.", channelUID, client);
191 protected void updateState(ChannelUID channelUID, State state) {
192 super.updateState(channelUID, state);
193 DecimalType value = state.as(DecimalType.class);
194 if (state instanceof OpenClosedType openClosedState) {
195 value = new DecimalType(openClosedState == OpenClosedType.CLOSED ? 1 : 0);
198 setOldValue(channelUID.getId(), value);
202 protected void updateConfiguration(Configuration configuration) {
203 super.updateConfiguration(configuration);
204 config.set(getConfigAs(PLCPulseConfiguration.class));
208 protected boolean isValid(final String name) {
209 if (2 <= name.length() && (name.length() <= 7)) {
210 String kind = config.get().getObservedBlockKind();
211 if (Character.isDigit(name.charAt(1))) {
212 boolean valid = I_DIGITAL.equalsIgnoreCase(kind) || Q_DIGITAL.equalsIgnoreCase(kind);
213 return name.startsWith(kind) && (valid || M_DIGITAL.equalsIgnoreCase(kind));
214 } else if (Character.isDigit(name.charAt(2))) {
215 String bKind = getBlockKind();
216 boolean valid = NI_DIGITAL.equalsIgnoreCase(kind) || NQ_DIGITAL.equalsIgnoreCase(kind);
217 valid = name.startsWith(kind) && (valid || MEMORY_BYTE.equalsIgnoreCase(kind));
218 return (name.startsWith(bKind) && MEMORY_BYTE.equalsIgnoreCase(bKind)) || valid;
225 protected String getBlockKind() {
226 return config.get().getBlockKind();
230 protected int getNumberOfChannels() {
235 protected int getAddress(final String name) {
236 int address = super.getAddress(name);
237 if (address != INVALID) {
238 int base = getBase(name);
240 address = base + (address - 1) / 8;
243 logger.info("Wrong configurated LOGO! block {} found.", name);
249 protected void doInitialization() {
250 Thing thing = getThing();
251 logger.debug("Initialize LOGO! pulse handler.");
253 config.set(getConfigAs(PLCPulseConfiguration.class));
255 super.doInitialization();
256 if (ThingStatus.OFFLINE != thing.getStatus()) {
257 ThingBuilder tBuilder = editThing();
259 String label = thing.getLabel();
261 Bridge bridge = getBridge();
262 label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
263 label += (": digital pulse in/output");
265 tBuilder.withLabel(label);
267 String bName = config.get().getBlockName();
268 String bType = config.get().getChannelType();
269 ChannelUID uid = new ChannelUID(thing.getUID(), STATE_CHANNEL);
270 ChannelBuilder cBuilder = ChannelBuilder.create(uid, bType);
271 cBuilder.withType(new ChannelTypeUID(BINDING_ID, bType.toLowerCase()));
272 cBuilder.withLabel(bName);
273 cBuilder.withDescription("Control block " + bName);
274 cBuilder.withProperties(Map.of(BLOCK_PROPERTY, bName));
275 tBuilder.withChannel(cBuilder.build());
276 setOldValue(STATE_CHANNEL, null);
278 String oName = config.get().getObservedBlock();
279 String oType = config.get().getObservedChannelType();
280 cBuilder = ChannelBuilder.create(new ChannelUID(thing.getUID(), OBSERVE_CHANNEL), oType);
281 cBuilder.withType(new ChannelTypeUID(BINDING_ID, oType.toLowerCase()));
282 cBuilder.withLabel(oName);
283 cBuilder.withDescription("Observed block " + oName);
284 cBuilder.withProperties(Map.of(BLOCK_PROPERTY, oName));
285 tBuilder.withChannel(cBuilder.build());
286 setOldValue(OBSERVE_CHANNEL, null);
288 updateThing(tBuilder.build());
289 updateStatus(ThingStatus.ONLINE);
294 * Calculate bit within address for block with given name.
296 * @param name Name of the LOGO! block
297 * @return Calculated bit
299 private int getBit(final String name) {
302 logger.debug("Get bit of {} LOGO! for block {} .", getLogoFamily(), name);
304 if (isValid(name) && (getAddress(name) != INVALID)) {
305 String[] parts = name.trim().split("\\.");
306 if (parts.length > 1) {
307 bit = Integer.parseInt(parts[1]);
308 } else if (parts.length == 1) {
309 if (Character.isDigit(parts[0].charAt(1))) {
310 bit = Integer.parseInt(parts[0].substring(1));
311 } else if (Character.isDigit(parts[0].charAt(2))) {
312 bit = Integer.parseInt(parts[0].substring(2));
313 } else if (Character.isDigit(parts[0].charAt(3))) {
314 bit = Integer.parseInt(parts[0].substring(3));
319 logger.info("Wrong configurated LOGO! block {} found.", name);
325 private void updateChannel(final Channel channel, boolean value) {
326 ChannelUID channelUID = channel.getUID();
327 String type = channel.getAcceptedItemType();
328 if (DIGITAL_INPUT_ITEM.equalsIgnoreCase(type)) {
329 updateState(channelUID, value ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
330 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
331 } else if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type)) {
332 updateState(channelUID, value ? OnOffType.ON : OnOffType.OFF);
333 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
335 logger.debug("Channel {} will not accept {} items.", channelUID, type);