]> git.basschouten.com Git - openhab-addons.git/blob
32064f085c6e10363824153343d82d87c896581d
[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.List;
19 import java.util.Set;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.atomic.AtomicReference;
22
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;
45
46 import Moka7.S7;
47 import Moka7.S7Client;
48
49 /**
50  * The {@link PLCPulseHandler} 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 PLCPulseHandler extends PLCCommonHandler {
57
58     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_PULSE);
59
60     private final Logger logger = LoggerFactory.getLogger(PLCPulseHandler.class);
61     private AtomicReference<PLCPulseConfiguration> config = new AtomicReference<>();
62     private AtomicReference<@Nullable Boolean> received = new AtomicReference<>();
63
64     /**
65      * Constructor.
66      */
67     public PLCPulseHandler(Thing thing) {
68         super(thing);
69     }
70
71     @Override
72     public void handleCommand(ChannelUID channelUID, Command command) {
73         if (!isThingOnline()) {
74             return;
75         }
76
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);
81             return;
82         }
83
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);
91                 if (result == 0) {
92                     updateChannel(channel, S7.GetBitAt(buffer, 0, bit));
93                 } else {
94                     logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
95                 }
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);
101                     received.set(flag);
102                 } else if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type)) {
103                     boolean flag = ((OnOffType) command == OnOffType.ON);
104                     S7.SetBitAt(buffer, 0, 0, flag);
105                     received.set(flag);
106                 } else {
107                     logger.debug("Channel {} will not accept {} items.", channelUID, type);
108                 }
109                 int result = client.writeDBArea(1, 8 * address + bit, buffer.length, S7Client.S7WLBit, buffer);
110                 if (result != 0) {
111                     logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
112                 }
113             } else {
114                 logger.debug("Channel {} received not supported command {}.", channelUID, command);
115             }
116         } else {
117             logger.info("Invalid channel {} or client {} found.", channelUID, client);
118         }
119     }
120
121     @Override
122     public void setData(final byte[] data) {
123         if (!isThingOnline()) {
124             return;
125         }
126
127         if (data.length != getBufferLength()) {
128             logger.info("Received and configured data sizes does not match.");
129             return;
130         }
131
132         List<Channel> channels = thing.getChannels();
133         if (channels.size() != getNumberOfChannels()) {
134             logger.info("Received and configured channel sizes does not match.");
135             return;
136         }
137
138         PLCLogoClient client = getLogoClient();
139         for (Channel channel : channels) {
140             ChannelUID channelUID = channel.getUID();
141             String name = getBlockFromChannel(channel);
142
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);
151                     }
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));
156                     }
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() {
163                             @Override
164                             public void run() {
165                                 Boolean value = received.getAndSet(null);
166                                 if (value != 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);
172                                     if (result != 0) {
173                                         logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
174                                     }
175                                 } else {
176                                     logger.debug("Invalid received value on channel {}.", channelUID);
177                                 }
178                             }
179                         }, pulse.longValue(), TimeUnit.MILLISECONDS);
180                     }
181                 } else {
182                     logger.info("Invalid channel {} found.", channelUID);
183                 }
184             } else {
185                 logger.info("Invalid channel {} or client {} found.", channelUID, client);
186             }
187         }
188     }
189
190     @Override
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) {
195             OpenClosedType type = (OpenClosedType) state;
196             value = new DecimalType(type == OpenClosedType.CLOSED ? 1 : 0);
197         }
198
199         setOldValue(channelUID.getId(), value);
200     }
201
202     @Override
203     protected void updateConfiguration(Configuration configuration) {
204         super.updateConfiguration(configuration);
205         config.set(getConfigAs(PLCPulseConfiguration.class));
206     }
207
208     @Override
209     protected boolean isValid(final String name) {
210         if (2 <= name.length() && (name.length() <= 7)) {
211             String kind = config.get().getObservedBlockKind();
212             if (Character.isDigit(name.charAt(1))) {
213                 boolean valid = I_DIGITAL.equalsIgnoreCase(kind) || Q_DIGITAL.equalsIgnoreCase(kind);
214                 return name.startsWith(kind) && (valid || M_DIGITAL.equalsIgnoreCase(kind));
215             } else if (Character.isDigit(name.charAt(2))) {
216                 String bKind = getBlockKind();
217                 boolean valid = NI_DIGITAL.equalsIgnoreCase(kind) || NQ_DIGITAL.equalsIgnoreCase(kind);
218                 valid = name.startsWith(kind) && (valid || MEMORY_BYTE.equalsIgnoreCase(kind));
219                 return (name.startsWith(bKind) && MEMORY_BYTE.equalsIgnoreCase(bKind)) || valid;
220             }
221         }
222         return false;
223     }
224
225     @Override
226     protected String getBlockKind() {
227         return config.get().getBlockKind();
228     }
229
230     @Override
231     protected int getNumberOfChannels() {
232         return 2;
233     }
234
235     @Override
236     protected int getAddress(final String name) {
237         int address = super.getAddress(name);
238         if (address != INVALID) {
239             int base = getBase(name);
240             if (base != 0) {
241                 address = base + (address - 1) / 8;
242             }
243         } else {
244             logger.info("Wrong configurated LOGO! block {} found.", name);
245         }
246         return address;
247     }
248
249     @Override
250     protected void doInitialization() {
251         Thing thing = getThing();
252         logger.debug("Initialize LOGO! pulse handler.");
253
254         config.set(getConfigAs(PLCPulseConfiguration.class));
255
256         super.doInitialization();
257         if (ThingStatus.OFFLINE != thing.getStatus()) {
258             ThingBuilder tBuilder = editThing();
259
260             String label = thing.getLabel();
261             if (label == null) {
262                 Bridge bridge = getBridge();
263                 label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
264                 label += (": digital pulse in/output");
265             }
266             tBuilder.withLabel(label);
267
268             String bName = config.get().getBlockName();
269             String bType = config.get().getChannelType();
270             ChannelUID uid = new ChannelUID(thing.getUID(), STATE_CHANNEL);
271             ChannelBuilder cBuilder = ChannelBuilder.create(uid, bType);
272             cBuilder.withType(new ChannelTypeUID(BINDING_ID, bType.toLowerCase()));
273             cBuilder.withLabel(bName);
274             cBuilder.withDescription("Control block " + bName);
275             cBuilder.withProperties(Collections.singletonMap(BLOCK_PROPERTY, bName));
276             tBuilder.withChannel(cBuilder.build());
277             setOldValue(STATE_CHANNEL, null);
278
279             String oName = config.get().getObservedBlock();
280             String oType = config.get().getObservedChannelType();
281             cBuilder = ChannelBuilder.create(new ChannelUID(thing.getUID(), OBSERVE_CHANNEL), oType);
282             cBuilder.withType(new ChannelTypeUID(BINDING_ID, oType.toLowerCase()));
283             cBuilder.withLabel(oName);
284             cBuilder.withDescription("Observed block " + oName);
285             cBuilder.withProperties(Collections.singletonMap(BLOCK_PROPERTY, oName));
286             tBuilder.withChannel(cBuilder.build());
287             setOldValue(OBSERVE_CHANNEL, null);
288
289             updateThing(tBuilder.build());
290             updateStatus(ThingStatus.ONLINE);
291         }
292     }
293
294     /**
295      * Calculate bit within address for block with given name.
296      *
297      * @param name Name of the LOGO! block
298      * @return Calculated bit
299      */
300     private int getBit(final String name) {
301         int bit = INVALID;
302
303         logger.debug("Get bit of {} LOGO! for block {} .", getLogoFamily(), name);
304
305         if (isValid(name) && (getAddress(name) != INVALID)) {
306             String[] parts = name.trim().split("\\.");
307             if (parts.length > 1) {
308                 bit = Integer.parseInt(parts[1]);
309             } else if (parts.length == 1) {
310                 if (Character.isDigit(parts[0].charAt(1))) {
311                     bit = Integer.parseInt(parts[0].substring(1));
312                 } else if (Character.isDigit(parts[0].charAt(2))) {
313                     bit = Integer.parseInt(parts[0].substring(2));
314                 } else if (Character.isDigit(parts[0].charAt(3))) {
315                     bit = Integer.parseInt(parts[0].substring(3));
316                 }
317                 bit = (bit - 1) % 8;
318             }
319         } else {
320             logger.info("Wrong configurated LOGO! block {} found.", name);
321         }
322
323         return bit;
324     }
325
326     private void updateChannel(final Channel channel, boolean value) {
327         ChannelUID channelUID = channel.getUID();
328         String type = channel.getAcceptedItemType();
329         if (DIGITAL_INPUT_ITEM.equalsIgnoreCase(type)) {
330             updateState(channelUID, value ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
331             logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
332         } else if (DIGITAL_OUTPUT_ITEM.equalsIgnoreCase(type)) {
333             updateState(channelUID, value ? OnOffType.ON : OnOffType.OFF);
334             logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
335         } else {
336             logger.debug("Channel {} will not accept {} items.", channelUID, type);
337         }
338     }
339 }