2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.plclogo.internal.handler;
15 import static org.openhab.binding.plclogo.internal.PLCLogoBindingConstants.*;
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;
25 import java.util.Objects;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.TimeUnit;
29 import java.util.concurrent.atomic.AtomicReference;
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;
53 import Moka7.S7Client;
56 * The {@link PLCBridgeHandler} is responsible for handling commands, which are
57 * sent to one of the channels.
59 * @author Alexander Falkenstern - Initial contribution
62 public class PLCBridgeHandler extends BaseBridgeHandler {
64 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DEVICE);
66 private final Logger logger = LoggerFactory.getLogger(PLCBridgeHandler.class);
68 private Map<ChannelUID, String> oldValues = new HashMap<>();
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<>();
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();
83 for (Channel channel : channels) {
84 handleCommand(channel.getUID(), RefreshType.REFRESH);
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];
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)) {
102 int result = localClient.readDBArea(1, 0, layout.length, S7Client.S7WLByte, buffer);
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));
111 logger.debug("Invalid handler {} found.", handler.getClass().getSimpleName());
116 logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
118 } catch (Exception exception) {
119 logger.error("Reader thread got exception: {}.", exception.getMessage());
122 logger.debug("Either memory block {} or LOGO! client {} is invalid.", memory, localClient);
130 public PLCBridgeHandler(Bridge bridge) {
135 public void handleCommand(ChannelUID channelUID, Command command) {
136 logger.debug("Handle command {} on channel {}", command, channelUID);
138 Thing thing = getThing();
139 if (ThingStatus.ONLINE != thing.getStatus()) {
143 if (!(command instanceof RefreshType)) {
144 logger.debug("Not supported command {} received.", command);
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);
157 if (RTC_CHANNEL.equals(channelId)) {
158 ZonedDateTime clock = ZonedDateTime.now();
159 if (!LOGO_0BA7.equalsIgnoreCase(getLogoFamily())) {
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());
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);
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);
197 logger.info("Invalid channel {} or client {} found.", channelUID, client);
200 if (logger.isTraceEnabled()) {
201 String raw = Arrays.toString(buffer);
202 String type = channel.getAcceptedItemType();
203 logger.trace("Channel {} accepting {} received {}.", channelUID, type, raw);
206 logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
209 logger.info("Invalid channel {} or client {} found.", channelUID, client);
214 public void initialize() {
215 logger.debug("Initialize LOGO! bridge handler.");
217 synchronized (oldValues) {
220 config.set(getConfigAs(PLCLogoBridgeConfiguration.class));
222 boolean configured = (config.get().getLocalTSAP() != null);
223 configured = configured && (config.get().getRemoteTSAP() != null);
226 if (client == null) {
227 client = new PLCLogoClient();
229 configured = connect();
231 String message = "Can not initialize LOGO!. Please, check ip address / TSAP settings.";
232 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message);
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);
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);
247 updateStatus(ThingStatus.ONLINE);
249 String message = "Can not initialize LOGO!. Please, check network connection.";
250 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
256 public void dispose() {
257 logger.debug("Dispose LOGO! bridge handler.");
260 if (rtcJob != null) {
261 rtcJob.cancel(false);
263 logger.info("Destroy RTC job for {}.", config.get().getAddress());
266 if (readerJob != null) {
267 readerJob.cancel(false);
269 logger.info("Destroy reader job for {}.", config.get().getAddress());
276 synchronized (oldValues) {
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);
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);
304 super.childHandlerDisposed(childHandler, childThing);
308 * Returns Siemens LOGO! communication client
310 * @return Configured Siemens LOGO! client
312 public @Nullable PLCLogoClient getLogoClient() {
317 * Returns configured Siemens LOGO! family: 0BA7 or 0BA8
319 * @return Configured Siemens LOGO! family
321 public String getLogoFamily() {
322 return config.get().getFamily();
326 * Returns RTC was fetched last from Siemens LOGO!
328 * @return Siemens LOGO! RTC
330 public ZonedDateTime getLogoRTC() {
335 protected void updateConfiguration(Configuration configuration) {
336 super.updateConfiguration(configuration);
337 config.set(getConfigAs(PLCLogoBridgeConfiguration.class));
341 * Read connection parameter and connect to Siemens LOGO!
343 * @return True, if connected and false otherwise
345 private boolean connect() {
346 boolean result = false;
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());
355 result = localClient.isConnected();
361 * Disconnect from Siemens LOGO!
363 * @return True, if disconnected and false otherwise
365 private boolean disconnect() {
366 boolean result = false;
368 PLCLogoClient localClient = client;
369 if (localClient != null) {
370 if (localClient.isConnected()) {
371 localClient.Disconnect();
373 result = !localClient.isConnected();