]> git.basschouten.com Git - openhab-addons.git/blob
626cd1a49e86229ff53d7acc3c07b20c5ac62e13
[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.time.DateTimeException;
18 import java.time.ZonedDateTime;
19 import java.util.Arrays;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Objects;
26 import java.util.Set;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.TimeUnit;
29 import java.util.concurrent.atomic.AtomicReference;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.Layout;
34 import org.openhab.binding.plclogo.internal.PLCLogoClient;
35 import org.openhab.binding.plclogo.internal.config.PLCLogoBridgeConfiguration;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.library.types.DateTimeType;
38 import org.openhab.core.library.types.StringType;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.Channel;
41 import org.openhab.core.thing.ChannelUID;
42 import org.openhab.core.thing.Thing;
43 import org.openhab.core.thing.ThingStatus;
44 import org.openhab.core.thing.ThingStatusDetail;
45 import org.openhab.core.thing.ThingTypeUID;
46 import org.openhab.core.thing.binding.BaseBridgeHandler;
47 import org.openhab.core.thing.binding.ThingHandler;
48 import org.openhab.core.types.Command;
49 import org.openhab.core.types.RefreshType;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 import Moka7.S7Client;
54
55 /**
56  * The {@link PLCBridgeHandler} is responsible for handling commands, which are
57  * sent to one of the channels.
58  *
59  * @author Alexander Falkenstern - Initial contribution
60  */
61 @NonNullByDefault
62 public class PLCBridgeHandler extends BaseBridgeHandler {
63
64     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DEVICE);
65
66     private final Logger logger = LoggerFactory.getLogger(PLCBridgeHandler.class);
67
68     private Map<ChannelUID, String> oldValues = new HashMap<>();
69
70     @Nullable
71     private volatile PLCLogoClient client; // S7 client used for communication with Logo!
72     private final Set<PLCCommonHandler> handlers = new HashSet<>();
73     private AtomicReference<PLCLogoBridgeConfiguration> config = new AtomicReference<>();
74
75     @Nullable
76     private ScheduledFuture<?> rtcJob;
77     private AtomicReference<ZonedDateTime> rtc = new AtomicReference<>(ZonedDateTime.now());
78     private final Runnable rtcReader = new Runnable() {
79         private final List<Channel> channels = getThing().getChannels();
80
81         @Override
82         public void run() {
83             for (Channel channel : channels) {
84                 handleCommand(channel.getUID(), RefreshType.REFRESH);
85             }
86         }
87     };
88
89     @Nullable
90     private ScheduledFuture<?> readerJob;
91     private final Runnable dataReader = new Runnable() {
92         // Buffer for block data read operation
93         private final byte[] buffer = new byte[2048];
94
95         @Override
96         public void run() {
97             PLCLogoClient localClient = client;
98             Map<?, Layout> memory = LOGO_MEMORY_BLOCK.get(getLogoFamily());
99             Layout layout = (memory != null) ? memory.get(MEMORY_SIZE) : null;
100             if ((layout != null) && (localClient != null)) {
101                 try {
102                     int result = localClient.readDBArea(1, 0, layout.length, S7Client.S7WLByte, buffer);
103                     if (result == 0) {
104                         synchronized (handlers) {
105                             for (PLCCommonHandler handler : handlers) {
106                                 int length = handler.getBufferLength();
107                                 int address = handler.getStartAddress();
108                                 if ((length > 0) && (address != PLCCommonHandler.INVALID)) {
109                                     handler.setData(Arrays.copyOfRange(buffer, address, address + length));
110                                 } else {
111                                     logger.debug("Invalid handler {} found.", handler.getClass().getSimpleName());
112                                 }
113                             }
114                         }
115                     } else {
116                         logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
117                     }
118                 } catch (Exception exception) {
119                     logger.error("Reader thread got exception: {}.", exception.getMessage());
120                 }
121             } else {
122                 logger.debug("Either memory block {} or LOGO! client {} is invalid.", memory, localClient);
123             }
124         }
125     };
126
127     /**
128      * Constructor.
129      */
130     public PLCBridgeHandler(Bridge bridge) {
131         super(bridge);
132     }
133
134     @Override
135     public void handleCommand(ChannelUID channelUID, Command command) {
136         logger.debug("Handle command {} on channel {}", command, channelUID);
137
138         Thing thing = getThing();
139         if (ThingStatus.ONLINE != thing.getStatus()) {
140             return;
141         }
142
143         if (!(command instanceof RefreshType)) {
144             logger.debug("Not supported command {} received.", command);
145             return;
146         }
147
148         PLCLogoClient localClient = client;
149         String channelId = channelUID.getId();
150         Channel channel = thing.getChannel(channelId);
151         Layout layout = LOGO_CHANNELS.get(channelId);
152         if ((localClient != null) && (channel != null) && (layout != null)) {
153             byte[] buffer = new byte[layout.length];
154             Arrays.fill(buffer, (byte) 0);
155             int result = localClient.readDBArea(1, layout.address, buffer.length, S7Client.S7WLByte, buffer);
156             if (result == 0) {
157                 if (RTC_CHANNEL.equals(channelId)) {
158                     ZonedDateTime clock = ZonedDateTime.now();
159                     if (!LOGO_0BA7.equalsIgnoreCase(getLogoFamily())) {
160                         try {
161                             int year = clock.getYear() / 100;
162                             clock = clock.withYear(100 * year + buffer[0]);
163                             clock = clock.withMonth(buffer[1]);
164                             clock = clock.withDayOfMonth(buffer[2]);
165                             clock = clock.withHour(buffer[3]);
166                             clock = clock.withMinute(buffer[4]);
167                             clock = clock.withSecond(buffer[5]);
168                         } catch (DateTimeException exception) {
169                             clock = ZonedDateTime.now();
170                             logger.info("Return local server time: {}.", exception.getMessage());
171                         }
172                     }
173                     rtc.set(clock);
174                     updateState(channelUID, new DateTimeType(clock));
175                 } else if (DAIGNOSTICS_CHANNEL.equals(channelId)) {
176                     Map<Integer, String> states = LOGO_STATES.get(getLogoFamily());
177                     if (states != null) {
178                         for (Integer key : states.keySet()) {
179                             String message = states.get(buffer[0] & key.intValue());
180                             synchronized (oldValues) {
181                                 if (message != null && !Objects.equals(oldValues.get(channelUID), message)) {
182                                     updateState(channelUID, new StringType(message));
183                                     oldValues.put(channelUID, message);
184                                 }
185                             }
186                         }
187                     }
188                 } else if (DAY_OF_WEEK_CHANNEL.equals(channelId)) {
189                     String value = DAY_OF_WEEK.get(Integer.valueOf(buffer[0]));
190                     synchronized (oldValues) {
191                         if (value != null && !Objects.equals(oldValues.get(channelUID), value)) {
192                             updateState(channelUID, new StringType(value));
193                             oldValues.put(channelUID, value);
194                         }
195                     }
196                 } else {
197                     logger.info("Invalid channel {} or client {} found.", channelUID, client);
198                 }
199
200                 if (logger.isTraceEnabled()) {
201                     String raw = Arrays.toString(buffer);
202                     String type = channel.getAcceptedItemType();
203                     logger.trace("Channel {} accepting {} received {}.", channelUID, type, raw);
204                 }
205             } else {
206                 logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
207             }
208         } else {
209             logger.info("Invalid channel {} or client {} found.", channelUID, client);
210         }
211     }
212
213     @Override
214     public void initialize() {
215         logger.debug("Initialize LOGO! bridge handler.");
216
217         synchronized (oldValues) {
218             oldValues.clear();
219         }
220         config.set(getConfigAs(PLCLogoBridgeConfiguration.class));
221
222         boolean configured = (config.get().getLocalTSAP() != null);
223         configured = configured && (config.get().getRemoteTSAP() != null);
224
225         if (configured) {
226             if (client == null) {
227                 client = new PLCLogoClient();
228             }
229             configured = connect();
230         } else {
231             String message = "Can not initialize LOGO!. Please, check ip address / TSAP settings.";
232             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message);
233         }
234
235         if (configured) {
236             String host = config.get().getAddress();
237             if (readerJob == null) {
238                 Integer interval = config.get().getRefreshRate();
239                 logger.info("Creating new reader job for {} with interval {} ms.", host, interval);
240                 readerJob = scheduler.scheduleWithFixedDelay(dataReader, 100, interval, TimeUnit.MILLISECONDS);
241             }
242             if (rtcJob == null) {
243                 logger.info("Creating new RTC job for {} with interval 1 s.", host);
244                 rtcJob = scheduler.scheduleAtFixedRate(rtcReader, 100, 1000, TimeUnit.MILLISECONDS);
245             }
246
247             updateStatus(ThingStatus.ONLINE);
248         } else {
249             String message = "Can not initialize LOGO!. Please, check network connection.";
250             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
251             client = null;
252         }
253     }
254
255     @Override
256     public void dispose() {
257         logger.debug("Dispose LOGO! bridge handler.");
258         super.dispose();
259
260         if (rtcJob != null) {
261             rtcJob.cancel(false);
262             rtcJob = null;
263             logger.info("Destroy RTC job for {}.", config.get().getAddress());
264         }
265
266         if (readerJob != null) {
267             readerJob.cancel(false);
268             readerJob = null;
269             logger.info("Destroy reader job for {}.", config.get().getAddress());
270         }
271
272         if (disconnect()) {
273             client = null;
274         }
275
276         synchronized (oldValues) {
277             oldValues.clear();
278         }
279     }
280
281     @Override
282     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
283         super.childHandlerInitialized(childHandler, childThing);
284         if (childHandler instanceof PLCCommonHandler) {
285             PLCCommonHandler handler = (PLCCommonHandler) childHandler;
286             synchronized (handlers) {
287                 if (!handlers.contains(handler)) {
288                     handlers.add(handler);
289                 }
290             }
291         }
292     }
293
294     @Override
295     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
296         if (childHandler instanceof PLCCommonHandler) {
297             PLCCommonHandler handler = (PLCCommonHandler) childHandler;
298             synchronized (handlers) {
299                 if (handlers.contains(handler)) {
300                     handlers.remove(handler);
301                 }
302             }
303         }
304         super.childHandlerDisposed(childHandler, childThing);
305     }
306
307     /**
308      * Returns Siemens LOGO! communication client
309      *
310      * @return Configured Siemens LOGO! client
311      */
312     public @Nullable PLCLogoClient getLogoClient() {
313         return client;
314     }
315
316     /**
317      * Returns configured Siemens LOGO! family: 0BA7 or 0BA8
318      *
319      * @return Configured Siemens LOGO! family
320      */
321     public String getLogoFamily() {
322         return config.get().getFamily();
323     }
324
325     /**
326      * Returns RTC was fetched last from Siemens LOGO!
327      *
328      * @return Siemens LOGO! RTC
329      */
330     public ZonedDateTime getLogoRTC() {
331         return rtc.get();
332     }
333
334     @Override
335     protected void updateConfiguration(Configuration configuration) {
336         super.updateConfiguration(configuration);
337         config.set(getConfigAs(PLCLogoBridgeConfiguration.class));
338     }
339
340     /**
341      * Read connection parameter and connect to Siemens LOGO!
342      *
343      * @return True, if connected and false otherwise
344      */
345     private boolean connect() {
346         boolean result = false;
347
348         PLCLogoClient localClient = client;
349         if (localClient != null) {
350             Integer local = config.get().getLocalTSAP();
351             Integer remote = config.get().getRemoteTSAP();
352             if (!localClient.isConnected() && (local != null) && (remote != null)) {
353                 localClient.Connect(config.get().getAddress(), local.intValue(), remote.intValue());
354             }
355             result = localClient.isConnected();
356         }
357         return result;
358     }
359
360     /**
361      * Disconnect from Siemens LOGO!
362      *
363      * @return True, if disconnected and false otherwise
364      */
365     private boolean disconnect() {
366         boolean result = false;
367
368         PLCLogoClient localClient = client;
369         if (localClient != null) {
370             if (localClient.isConnected()) {
371                 localClient.Disconnect();
372             }
373             result = !localClient.isConnected();
374         }
375
376         return result;
377     }
378 }