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