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.Collections;
18 import java.util.HashMap;
19 import java.util.List;
22 import java.util.concurrent.atomic.AtomicReference;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.openhab.binding.plclogo.internal.PLCLogoClient;
26 import org.openhab.binding.plclogo.internal.config.PLCAnalogConfiguration;
27 import org.openhab.core.config.core.Configuration;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.Channel;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.binding.builder.ChannelBuilder;
36 import org.openhab.core.thing.binding.builder.ThingBuilder;
37 import org.openhab.core.thing.type.ChannelTypeUID;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.types.State;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 import Moka7.S7Client;
48 * The {@link PLCAnalogHandler} is responsible for handling commands, which are
49 * sent to one of the channels.
51 * @author Alexander Falkenstern - Initial contribution
54 public class PLCAnalogHandler extends PLCCommonHandler {
56 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_ANALOG);
58 private final Logger logger = LoggerFactory.getLogger(PLCAnalogHandler.class);
59 private AtomicReference<PLCAnalogConfiguration> config = new AtomicReference<>();
61 private static final Map<String, Integer> LOGO_BLOCKS_0BA7;
63 Map<String, Integer> buffer = new HashMap<>();
64 buffer.put(I_ANALOG, 8); // 8 analog inputs
65 buffer.put(Q_ANALOG, 2); // 2 analog outputs
66 buffer.put(M_ANALOG, 16); // 16 analog markers
67 LOGO_BLOCKS_0BA7 = Collections.unmodifiableMap(buffer);
70 private static final Map<String, Integer> LOGO_BLOCKS_0BA8;
72 Map<String, Integer> buffer = new HashMap<>();
73 buffer.put(I_ANALOG, 8); // 8 analog inputs
74 buffer.put(Q_ANALOG, 8); // 8 analog outputs
75 buffer.put(M_ANALOG, 64); // 64 analog markers
76 buffer.put(NI_ANALOG, 32); // 32 network analog inputs
77 buffer.put(NQ_ANALOG, 16); // 16 network analog outputs
78 LOGO_BLOCKS_0BA8 = Collections.unmodifiableMap(buffer);
81 private static final Map<String, Map<String, Integer>> LOGO_BLOCK_NUMBER;
83 Map<String, Map<String, Integer>> buffer = new HashMap<>();
84 buffer.put(LOGO_0BA7, LOGO_BLOCKS_0BA7);
85 buffer.put(LOGO_0BA8, LOGO_BLOCKS_0BA8);
86 LOGO_BLOCK_NUMBER = Collections.unmodifiableMap(buffer);
92 public PLCAnalogHandler(Thing thing) {
97 public void handleCommand(ChannelUID channelUID, Command command) {
98 if (!isThingOnline()) {
102 Channel channel = getThing().getChannel(channelUID.getId());
103 String name = getBlockFromChannel(channel);
104 if (!isValid(name) || (channel == null)) {
105 logger.debug("Can not update channel {}, block {}.", channelUID, name);
109 int address = getAddress(name);
110 PLCLogoClient client = getLogoClient();
111 if ((address != INVALID) && (client != null)) {
112 if (command instanceof RefreshType) {
113 int base = getBase(name);
114 byte[] buffer = new byte[getBufferLength()];
115 int result = client.readDBArea(1, base, buffer.length, S7Client.S7WLByte, buffer);
117 updateChannel(channel, S7.GetShortAt(buffer, address - base));
119 logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
121 } else if (command instanceof DecimalType) {
122 byte[] buffer = new byte[2];
123 String type = channel.getAcceptedItemType();
124 if (ANALOG_ITEM.equalsIgnoreCase(type)) {
125 S7.SetShortAt(buffer, 0, ((DecimalType) command).intValue());
127 logger.debug("Channel {} will not accept {} items.", channelUID, type);
129 int result = client.writeDBArea(1, address, buffer.length, S7Client.S7WLByte, buffer);
131 logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
134 logger.debug("Channel {} received not supported command {}.", channelUID, command);
137 logger.info("Invalid channel {} or client {} found.", channelUID, client);
142 public void setData(final byte[] data) {
143 if (!isThingOnline()) {
147 if (data.length != getBufferLength()) {
148 logger.info("Received and configured data sizes does not match.");
152 List<Channel> channels = thing.getChannels();
153 if (channels.size() != getNumberOfChannels()) {
154 logger.info("Received and configured channel sizes does not match.");
158 Boolean force = config.get().isUpdateForced();
159 Integer threshold = config.get().getThreshold();
160 for (Channel channel : channels) {
161 ChannelUID channelUID = channel.getUID();
162 String name = getBlockFromChannel(channel);
164 int address = getAddress(name);
165 if (address != INVALID) {
166 DecimalType state = (DecimalType) getOldValue(name);
167 int value = S7.GetShortAt(data, address - getBase(name));
168 if ((state == null) || (Math.abs(value - state.intValue()) > threshold) || force) {
169 updateChannel(channel, value);
171 if (logger.isTraceEnabled()) {
172 int index = address - getBase(name);
173 logger.trace("Channel {} received [{}, {}].", channelUID, data[index], data[index + 1]);
176 logger.info("Invalid channel {} found.", channelUID);
182 protected void updateState(ChannelUID channelUID, State state) {
183 super.updateState(channelUID, state);
185 Channel channel = thing.getChannel(channelUID.getId());
186 setOldValue(getBlockFromChannel(channel), state);
190 protected void updateConfiguration(Configuration configuration) {
191 super.updateConfiguration(configuration);
192 config.set(getConfigAs(PLCAnalogConfiguration.class));
196 protected boolean isValid(final String name) {
197 if (3 <= name.length() && (name.length() <= 5)) {
198 String kind = getBlockKind();
199 if (Character.isDigit(name.charAt(2)) || Character.isDigit(name.charAt(3))) {
200 boolean valid = I_ANALOG.equalsIgnoreCase(kind) || NI_ANALOG.equalsIgnoreCase(kind);
201 valid = valid || Q_ANALOG.equalsIgnoreCase(kind) || NQ_ANALOG.equalsIgnoreCase(kind);
202 return name.startsWith(kind) && (valid || M_ANALOG.equalsIgnoreCase(kind));
209 protected String getBlockKind() {
210 return config.get().getBlockKind();
214 protected int getNumberOfChannels() {
215 String kind = getBlockKind();
216 String family = getLogoFamily();
217 logger.debug("Get block number of {} LOGO! for {} blocks.", family, kind);
219 Map<?, Integer> blocks = LOGO_BLOCK_NUMBER.get(family);
220 Integer number = (blocks != null) ? blocks.get(kind) : null;
221 return (number != null) ? number.intValue() : 0;
225 protected int getAddress(final String name) {
226 int address = super.getAddress(name);
227 if (address != INVALID) {
228 address = getBase(name) + (address - 1) * 2;
230 logger.info("Wrong configurated LOGO! block {} found.", name);
236 protected void doInitialization() {
237 Thing thing = getThing();
238 logger.debug("Initialize LOGO! analog input blocks handler.");
240 config.set(getConfigAs(PLCAnalogConfiguration.class));
242 super.doInitialization();
243 if (ThingStatus.OFFLINE != thing.getStatus()) {
244 String kind = getBlockKind();
245 String text = I_ANALOG.equalsIgnoreCase(kind) || NI_ANALOG.equalsIgnoreCase(kind) ? "input" : "output";
247 ThingBuilder tBuilder = editThing();
249 String label = thing.getLabel();
251 Bridge bridge = getBridge();
252 label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
253 label += (": analog " + text + "s");
255 tBuilder.withLabel(label);
257 String type = config.get().getChannelType();
258 for (int i = 0; i < getNumberOfChannels(); i++) {
259 String name = kind + String.valueOf(i + 1);
260 ChannelUID uid = new ChannelUID(thing.getUID(), name);
261 ChannelBuilder cBuilder = ChannelBuilder.create(uid, type);
262 cBuilder.withType(new ChannelTypeUID(BINDING_ID, type.toLowerCase()));
263 cBuilder.withLabel(name);
264 cBuilder.withDescription("Analog " + text + " block " + name);
265 cBuilder.withProperties(Collections.singletonMap(BLOCK_PROPERTY, name));
266 tBuilder.withChannel(cBuilder.build());
267 setOldValue(name, null);
270 updateThing(tBuilder.build());
271 updateStatus(ThingStatus.ONLINE);
275 private void updateChannel(final Channel channel, int value) {
276 ChannelUID channelUID = channel.getUID();
277 String type = channel.getAcceptedItemType();
278 if (ANALOG_ITEM.equalsIgnoreCase(type)) {
279 updateState(channelUID, new DecimalType(value));
280 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
282 logger.debug("Channel {} will not accept {} items.", channelUID, type);