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