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.atomic.AtomicReference;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.binding.plclogo.internal.PLCLogoClient;
24 import org.openhab.binding.plclogo.internal.config.PLCMemoryConfiguration;
25 import org.openhab.core.config.core.Configuration;
26 import org.openhab.core.library.types.DecimalType;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.Channel;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingTypeUID;
34 import org.openhab.core.thing.binding.builder.ChannelBuilder;
35 import org.openhab.core.thing.binding.builder.ThingBuilder;
36 import org.openhab.core.thing.type.ChannelTypeUID;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.RefreshType;
39 import org.openhab.core.types.State;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 import Moka7.S7Client;
47 * The {@link PLCMemoryHandler} is responsible for handling commands, which are
48 * sent to one of the channels.
50 * @author Alexander Falkenstern - Initial contribution
53 public class PLCMemoryHandler extends PLCCommonHandler {
55 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_MEMORY);
57 private final Logger logger = LoggerFactory.getLogger(PLCMemoryHandler.class);
58 private AtomicReference<PLCMemoryConfiguration> config = new AtomicReference<>();
63 public PLCMemoryHandler(Thing thing) {
68 public void handleCommand(ChannelUID channelUID, Command command) {
69 if (!isThingOnline()) {
73 Channel channel = getThing().getChannel(channelUID.getId());
74 String name = getBlockFromChannel(channel);
75 if (!isValid(name) || (channel == null)) {
76 logger.debug("Can not update channel {}, block {}.", channelUID, name);
80 int address = getAddress(name);
81 PLCLogoClient client = getLogoClient();
82 if ((address != INVALID) && (client != null)) {
83 String kind = getBlockKind();
84 String type = channel.getAcceptedItemType();
85 if (command instanceof RefreshType) {
86 byte[] buffer = new byte[getBufferLength()];
87 int result = client.readDBArea(1, 0, buffer.length, S7Client.S7WLByte, buffer);
89 if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type) && MEMORY_BYTE.equalsIgnoreCase(kind)) {
90 boolean value = S7.GetBitAt(buffer, address, getBit(name));
91 updateState(channelUID, value ? OnOffType.ON : OnOffType.OFF);
92 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
93 } else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_BYTE.equalsIgnoreCase(kind)) {
94 int value = buffer[address];
95 updateState(channelUID, new DecimalType(value));
96 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
97 } else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_WORD.equalsIgnoreCase(kind)) {
98 int value = S7.GetShortAt(buffer, address);
99 updateState(channelUID, new DecimalType(value));
100 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
101 } else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_DWORD.equalsIgnoreCase(kind)) {
102 int value = S7.GetDIntAt(buffer, address);
103 updateState(channelUID, new DecimalType(value));
104 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
106 logger.debug("Channel {} will not accept {} items.", channelUID, type);
109 logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
111 } else if (command instanceof DecimalType decimalCommand) {
112 int length = MEMORY_BYTE.equalsIgnoreCase(kind) ? 1 : 2;
113 byte[] buffer = new byte[MEMORY_DWORD.equalsIgnoreCase(kind) ? 4 : length];
114 if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_BYTE.equalsIgnoreCase(kind)) {
115 buffer[0] = decimalCommand.byteValue();
116 } else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_WORD.equalsIgnoreCase(kind)) {
117 S7.SetShortAt(buffer, 0, decimalCommand.intValue());
118 } else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_DWORD.equalsIgnoreCase(kind)) {
119 S7.SetDIntAt(buffer, 0, decimalCommand.intValue());
121 logger.debug("Channel {} will not accept {} items.", channelUID, type);
123 int result = client.writeDBArea(1, address, buffer.length, S7Client.S7WLByte, buffer);
125 logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
127 } else if (command instanceof OnOffType onOffCommand) {
128 byte[] buffer = new byte[1];
129 if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type) && MEMORY_BYTE.equalsIgnoreCase(kind)) {
130 S7.SetBitAt(buffer, 0, 0, onOffCommand == OnOffType.ON);
132 logger.debug("Channel {} will not accept {} items.", channelUID, type);
134 int result = client.writeDBArea(1, 8 * address + getBit(name), buffer.length, S7Client.S7WLBit, buffer);
136 logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
139 logger.debug("Channel {} received not supported command {}.", channelUID, command);
142 logger.info("Invalid channel {} or client {} found.", channelUID, client);
147 public void setData(final byte[] data) {
148 if (!isThingOnline()) {
152 if (data.length != getBufferLength()) {
153 logger.info("Received and configured data sizes does not match.");
157 List<Channel> channels = thing.getChannels();
158 if (channels.size() != getNumberOfChannels()) {
159 logger.info("Received and configured channel sizes does not match.");
163 for (Channel channel : channels) {
164 ChannelUID channelUID = channel.getUID();
165 String name = getBlockFromChannel(channel);
167 int address = getAddress(name);
168 if (address != INVALID) {
169 String kind = getBlockKind();
170 String type = channel.getAcceptedItemType();
171 Boolean force = config.get().isUpdateForced();
173 if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type) && kind.equalsIgnoreCase(MEMORY_BYTE)) {
174 OnOffType state = (OnOffType) getOldValue(name);
175 OnOffType value = S7.GetBitAt(data, address, getBit(name)) ? OnOffType.ON : OnOffType.OFF;
176 if ((state == null) || (value != state) || force) {
177 updateState(channelUID, value);
178 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
180 if (logger.isTraceEnabled()) {
181 int buffer = (data[address] & 0xFF) + 0x100;
182 logger.trace("Channel {} received [{}].", channelUID,
183 Integer.toBinaryString(buffer).substring(1));
185 } else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_BYTE.equalsIgnoreCase(kind)) {
186 Integer threshold = config.get().getThreshold();
187 DecimalType state = (DecimalType) getOldValue(name);
188 int value = data[address];
189 if ((state == null) || (Math.abs(value - state.intValue()) > threshold) || force) {
190 updateState(channelUID, new DecimalType(value));
191 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
193 if (logger.isTraceEnabled()) {
194 logger.trace("Channel {} received [{}].", channelUID, data[address]);
196 } else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_WORD.equalsIgnoreCase(kind)) {
197 Integer threshold = config.get().getThreshold();
198 DecimalType state = (DecimalType) getOldValue(name);
199 int value = S7.GetShortAt(data, address);
200 if ((state == null) || (Math.abs(value - state.intValue()) > threshold) || force) {
201 updateState(channelUID, new DecimalType(value));
202 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
204 if (logger.isTraceEnabled()) {
205 logger.trace("Channel {} received [{}, {}].", channelUID, data[address], data[address + 1]);
207 } else if (ANALOG_ITEM.equalsIgnoreCase(type) && MEMORY_DWORD.equalsIgnoreCase(kind)) {
208 Integer threshold = config.get().getThreshold();
209 DecimalType state = (DecimalType) getOldValue(name);
210 int value = S7.GetDIntAt(data, address);
211 if ((state == null) || (Math.abs(value - state.intValue()) > threshold) || force) {
212 updateState(channelUID, new DecimalType(value));
213 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
215 if (logger.isTraceEnabled()) {
216 logger.trace("Channel {} received [{}, {}, {}, {}].", channelUID, data[address],
217 data[address + 1], data[address + 2], data[address + 3]);
220 logger.debug("Channel {} will not accept {} items.", channelUID, type);
223 logger.info("Invalid channel {} found.", channelUID);
229 protected void updateState(ChannelUID channelUID, State state) {
230 super.updateState(channelUID, state);
232 Channel channel = thing.getChannel(channelUID.getId());
233 setOldValue(getBlockFromChannel(channel), state);
237 protected void updateConfiguration(Configuration configuration) {
238 super.updateConfiguration(configuration);
239 config.set(getConfigAs(PLCMemoryConfiguration.class));
243 protected boolean isValid(final String name) {
244 if (3 <= name.length() && (name.length() <= 7)) {
245 String kind = getBlockKind();
246 if (Character.isDigit(name.charAt(2))) {
247 boolean valid = MEMORY_BYTE.equalsIgnoreCase(kind) || MEMORY_WORD.equalsIgnoreCase(kind);
248 return name.startsWith(kind) && (valid || MEMORY_DWORD.equalsIgnoreCase(kind));
255 protected String getBlockKind() {
256 return config.get().getBlockKind();
260 protected int getNumberOfChannels() {
265 protected void doInitialization() {
266 Thing thing = getThing();
267 logger.debug("Initialize LOGO! memory handler.");
269 config.set(getConfigAs(PLCMemoryConfiguration.class));
271 super.doInitialization();
272 if (ThingStatus.OFFLINE != thing.getStatus()) {
273 String kind = getBlockKind();
274 String name = config.get().getBlockName();
275 boolean isDigital = MEMORY_BYTE.equalsIgnoreCase(kind) && (getBit(name) != INVALID);
276 String text = isDigital ? "Digital" : "Analog";
278 ThingBuilder tBuilder = editThing();
280 String label = thing.getLabel();
282 Bridge bridge = getBridge();
283 label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
284 label += (": " + text.toLowerCase() + " in/output");
286 tBuilder.withLabel(label);
288 String type = config.get().getChannelType();
289 ChannelUID uid = new ChannelUID(thing.getUID(), isDigital ? STATE_CHANNEL : VALUE_CHANNEL);
290 ChannelBuilder cBuilder = ChannelBuilder.create(uid, type);
291 cBuilder.withType(new ChannelTypeUID(BINDING_ID, type.toLowerCase()));
292 cBuilder.withLabel(name);
293 cBuilder.withDescription(text + " in/output block " + name);
294 cBuilder.withProperties(Map.of(BLOCK_PROPERTY, name));
295 tBuilder.withChannel(cBuilder.build());
296 setOldValue(name, null);
298 updateThing(tBuilder.build());
299 updateStatus(ThingStatus.ONLINE);
304 * Calculate bit within address for block with given name.
306 * @param name Name of the LOGO! block
307 * @return Calculated bit
309 private int getBit(final String name) {
312 logger.debug("Get bit of {} LOGO! for block {} .", getLogoFamily(), name);
314 if (isValid(name) && (getAddress(name) != INVALID)) {
315 String[] parts = name.trim().split("\\.");
316 if (parts.length > 1) {
317 bit = Integer.parseInt(parts[1]);
320 logger.info("Wrong configurated LOGO! block {} found.", name);