]> git.basschouten.com Git - openhab-addons.git/blob
206f5aeeeb26338102c7e2e24f86f38096598cf9
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.plclogo.internal.handler;
14
15 import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
16
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.concurrent.atomic.AtomicReference;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.openhab.binding.plclogo.internal.PLCLogoClient;
26 import org.openhab.binding.plclogo.internal.config.PLCDigitalConfiguration;
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;
45
46 import Moka7.S7;
47 import Moka7.S7Client;
48
49 /**
50  * The {@link PLCDigitalHandler} is responsible for handling commands, which are
51  * sent to one of the channels.
52  *
53  * @author Alexander Falkenstern - Initial contribution
54  */
55 @NonNullByDefault
56 public class PLCDigitalHandler extends PLCCommonHandler {
57
58     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DIGITAL);
59
60     private final Logger logger = LoggerFactory.getLogger(PLCDigitalHandler.class);
61     private AtomicReference<PLCDigitalConfiguration> config = new AtomicReference<>();
62
63     private static final Map<String, Integer> LOGO_BLOCKS_0BA7;
64     static {
65         Map<String, Integer> buffer = new HashMap<>();
66         buffer.put(I_DIGITAL, 24); // 24 digital inputs
67         buffer.put(Q_DIGITAL, 16); // 16 digital outputs
68         buffer.put(M_DIGITAL, 27); // 27 digital markers
69         LOGO_BLOCKS_0BA7 = Collections.unmodifiableMap(buffer);
70     }
71
72     private static final Map<String, Integer> LOGO_BLOCKS_0BA8;
73     static {
74         Map<String, Integer> buffer = new HashMap<>();
75         buffer.put(I_DIGITAL, 24); // 24 digital inputs
76         buffer.put(Q_DIGITAL, 20); // 20 digital outputs
77         buffer.put(M_DIGITAL, 64); // 64 digital markers
78         buffer.put(NI_DIGITAL, 64); // 64 network inputs
79         buffer.put(NQ_DIGITAL, 64); // 64 network outputs
80         LOGO_BLOCKS_0BA8 = Collections.unmodifiableMap(buffer);
81     }
82
83     private static final Map<String, Map<String, Integer>> LOGO_BLOCK_NUMBER;
84     static {
85         Map<String, Map<String, Integer>> buffer = new HashMap<>();
86         buffer.put(LOGO_0BA7, LOGO_BLOCKS_0BA7);
87         buffer.put(LOGO_0BA8, LOGO_BLOCKS_0BA8);
88         LOGO_BLOCK_NUMBER = Collections.unmodifiableMap(buffer);
89     }
90
91     /**
92      * Constructor.
93      */
94     public PLCDigitalHandler(Thing thing) {
95         super(thing);
96     }
97
98     @Override
99     public void handleCommand(ChannelUID channelUID, Command command) {
100         if (!isThingOnline()) {
101             return;
102         }
103
104         Channel channel = getThing().getChannel(channelUID.getId());
105         String name = getBlockFromChannel(channel);
106         if (!isValid(name) || (channel == null)) {
107             logger.debug("Can not update channel {}, block {}.", channelUID, name);
108             return;
109         }
110
111         int bit = getBit(name);
112         int address = getAddress(name);
113         PLCLogoClient client = getLogoClient();
114         if ((address != INVALID) && (bit != INVALID) && (client != null)) {
115             if (command instanceof RefreshType) {
116                 int base = getBase(name);
117                 byte[] buffer = new byte[getBufferLength()];
118                 int result = client.readDBArea(1, base, buffer.length, S7Client.S7WLByte, buffer);
119                 if (result == 0) {
120                     updateChannel(channel, S7.GetBitAt(buffer, address - base, bit));
121                 } else {
122                     logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
123                 }
124             } else if ((command instanceof OpenClosedType) || (command instanceof OnOffType)) {
125                 byte[] buffer = new byte[1];
126                 String type = channel.getAcceptedItemType();
127                 if (DIGITAL_INPUT_ITEM.equalsIgnoreCase(type)) {
128                     S7.SetBitAt(buffer, 0, 0, ((OpenClosedType) command) == OpenClosedType.CLOSED);
129                 } else if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type)) {
130                     S7.SetBitAt(buffer, 0, 0, ((OnOffType) command) == OnOffType.ON);
131                 } else {
132                     logger.debug("Channel {} will not accept {} items.", channelUID, type);
133                 }
134                 int result = client.writeDBArea(1, 8 * address + bit, buffer.length, S7Client.S7WLBit, buffer);
135                 if (result != 0) {
136                     logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
137                 }
138             } else {
139                 logger.debug("Channel {} received not supported command {}.", channelUID, command);
140             }
141         } else {
142             logger.info("Invalid channel {} or client {} found.", channelUID, client);
143         }
144     }
145
146     @Override
147     public void setData(final byte[] data) {
148         if (!isThingOnline()) {
149             return;
150         }
151
152         if (data.length != getBufferLength()) {
153             logger.info("Received and configured data sizes does not match.");
154             return;
155         }
156
157         List<Channel> channels = thing.getChannels();
158         if (channels.size() != getNumberOfChannels()) {
159             logger.info("Received and configured channel sizes does not match.");
160             return;
161         }
162
163         Boolean force = config.get().isUpdateForced();
164         for (Channel channel : channels) {
165             ChannelUID channelUID = channel.getUID();
166             String name = getBlockFromChannel(channel);
167
168             int bit = getBit(name);
169             int address = getAddress(name);
170             if ((address != INVALID) && (bit != INVALID)) {
171                 DecimalType state = (DecimalType) getOldValue(name);
172                 boolean value = S7.GetBitAt(data, address - getBase(name), bit);
173                 if ((state == null) || ((value ? 1 : 0) != state.intValue()) || force) {
174                     updateChannel(channel, value);
175                 }
176                 if (logger.isTraceEnabled()) {
177                     int buffer = (data[address - getBase(name)] & 0xFF) + 0x100;
178                     logger.trace("Channel {} received [{}].", channelUID, Integer.toBinaryString(buffer).substring(1));
179                 }
180             } else {
181                 logger.info("Invalid channel {} found.", channelUID);
182             }
183         }
184     }
185
186     @Override
187     protected void updateState(ChannelUID channelUID, State state) {
188         super.updateState(channelUID, state);
189         DecimalType value = state.as(DecimalType.class);
190         if (state instanceof OpenClosedType openClosedState) {
191             value = new DecimalType(openClosedState == OpenClosedType.CLOSED ? 1 : 0);
192         }
193
194         Channel channel = thing.getChannel(channelUID.getId());
195         setOldValue(getBlockFromChannel(channel), value);
196     }
197
198     @Override
199     protected void updateConfiguration(Configuration configuration) {
200         super.updateConfiguration(configuration);
201         config.set(getConfigAs(PLCDigitalConfiguration.class));
202     }
203
204     @Override
205     protected boolean isValid(final String name) {
206         if (2 <= name.length() && (name.length() <= 4)) {
207             String kind = getBlockKind();
208             if (Character.isDigit(name.charAt(1)) || Character.isDigit(name.charAt(2))) {
209                 boolean valid = I_DIGITAL.equalsIgnoreCase(kind) || NI_DIGITAL.equalsIgnoreCase(kind);
210                 valid = valid || Q_DIGITAL.equalsIgnoreCase(kind) || NQ_DIGITAL.equalsIgnoreCase(kind);
211                 return name.startsWith(kind) && (valid || M_DIGITAL.equalsIgnoreCase(kind));
212             }
213         }
214         return false;
215     }
216
217     @Override
218     protected String getBlockKind() {
219         return config.get().getBlockKind();
220     }
221
222     @Override
223     protected int getNumberOfChannels() {
224         String kind = getBlockKind();
225         String family = getLogoFamily();
226         logger.debug("Get block number of {} LOGO! for {} blocks.", family, kind);
227
228         Map<?, Integer> blocks = LOGO_BLOCK_NUMBER.get(family);
229         Integer number = (blocks != null) ? blocks.get(kind) : null;
230         return (number != null) ? number.intValue() : 0;
231     }
232
233     @Override
234     protected int getAddress(final String name) {
235         int address = super.getAddress(name);
236         if (address != INVALID) {
237             address = getBase(name) + (address - 1) / 8;
238         } else {
239             logger.info("Wrong configurated LOGO! block {} found.", name);
240         }
241         return address;
242     }
243
244     @Override
245     protected void doInitialization() {
246         Thing thing = getThing();
247         logger.debug("Initialize LOGO! digital input blocks handler.");
248
249         config.set(getConfigAs(PLCDigitalConfiguration.class));
250
251         super.doInitialization();
252         if (ThingStatus.OFFLINE != thing.getStatus()) {
253             String kind = getBlockKind();
254             String type = config.get().getChannelType();
255             String text = DIGITAL_INPUT_ITEM.equalsIgnoreCase(type) ? "input" : "output";
256
257             ThingBuilder tBuilder = editThing();
258
259             String label = thing.getLabel();
260             if (label == null) {
261                 Bridge bridge = getBridge();
262                 label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
263                 label += (": digital " + text + "s");
264             }
265             tBuilder.withLabel(label);
266
267             for (int i = 0; i < getNumberOfChannels(); i++) {
268                 String name = kind + String.valueOf(i + 1);
269                 ChannelUID uid = new ChannelUID(thing.getUID(), name);
270                 ChannelBuilder cBuilder = ChannelBuilder.create(uid, type);
271                 cBuilder.withType(new ChannelTypeUID(BINDING_ID, type.toLowerCase()));
272                 cBuilder.withLabel(name);
273                 cBuilder.withDescription("Digital " + text + " block " + name);
274                 cBuilder.withProperties(Map.of(BLOCK_PROPERTY, name));
275                 tBuilder.withChannel(cBuilder.build());
276                 setOldValue(name, null);
277             }
278
279             updateThing(tBuilder.build());
280             updateStatus(ThingStatus.ONLINE);
281         }
282     }
283
284     /**
285      * Calculate bit within address for block with given name.
286      *
287      * @param name Name of the LOGO! block
288      * @return Calculated bit
289      */
290     private int getBit(final String name) {
291         int bit = INVALID;
292
293         logger.debug("Get bit of {} LOGO! for block {} .", getLogoFamily(), name);
294
295         if (isValid(name) && (getAddress(name) != INVALID)) {
296             if (Character.isDigit(name.charAt(1))) {
297                 bit = Integer.parseInt(name.substring(1));
298             } else if (Character.isDigit(name.charAt(2))) {
299                 bit = Integer.parseInt(name.substring(2));
300             }
301             bit = (bit - 1) % 8;
302         } else {
303             logger.info("Wrong configurated LOGO! block {} found.", name);
304         }
305
306         return bit;
307     }
308
309     private void updateChannel(final Channel channel, boolean value) {
310         ChannelUID channelUID = channel.getUID();
311         String type = channel.getAcceptedItemType();
312         if (DIGITAL_INPUT_ITEM.equalsIgnoreCase(type)) {
313             updateState(channelUID, value ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
314             logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
315         } else if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type)) {
316             updateState(channelUID, OnOffType.from(value));
317             logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
318         } else {
319             logger.debug("Channel {} will not accept {} items.", channelUID, type);
320         }
321     }
322 }