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.HashMap;
21 import java.util.HashSet;
22 import java.util.List;
24 import java.util.Objects;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28 import java.util.concurrent.atomic.AtomicReference;
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;
52 import Moka7.S7Client;
55 * The {@link PLCBridgeHandler} is responsible for handling commands, which are
56 * sent to one of the channels.
58 * @author Alexander Falkenstern - Initial contribution
61 public class PLCBridgeHandler extends BaseBridgeHandler {
63 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DEVICE);
65 private final Logger logger = LoggerFactory.getLogger(PLCBridgeHandler.class);
67 private Map<ChannelUID, String> oldValues = new HashMap<>();
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<>();
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();
82 for (Channel channel : channels) {
83 handleCommand(channel.getUID(), RefreshType.REFRESH);
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];
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)) {
101 int result = localClient.readDBArea(1, 0, layout.length, S7Client.S7WLByte, buffer);
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));
110 logger.debug("Invalid handler {} found.", handler.getClass().getSimpleName());
115 logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
117 } catch (Exception exception) {
118 logger.error("Reader thread got exception: {}.", exception.getMessage());
121 logger.debug("Either memory block {} or LOGO! client {} is invalid.", memory, localClient);
129 public PLCBridgeHandler(Bridge bridge) {
134 public void handleCommand(ChannelUID channelUID, Command command) {
135 logger.debug("Handle command {} on channel {}", command, channelUID);
137 Thing thing = getThing();
138 if (ThingStatus.ONLINE != thing.getStatus()) {
142 if (!(command instanceof RefreshType)) {
143 logger.debug("Not supported command {} received.", command);
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);
156 if (RTC_CHANNEL.equals(channelId)) {
157 ZonedDateTime clock = ZonedDateTime.now();
158 if (!LOGO_0BA7.equalsIgnoreCase(getLogoFamily())) {
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());
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);
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);
196 logger.info("Invalid channel {} or client {} found.", channelUID, client);
199 if (logger.isTraceEnabled()) {
200 String raw = Arrays.toString(buffer);
201 String type = channel.getAcceptedItemType();
202 logger.trace("Channel {} accepting {} received {}.", channelUID, type, raw);
205 logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
208 logger.info("Invalid channel {} or client {} found.", channelUID, client);
213 public void initialize() {
214 logger.debug("Initialize LOGO! bridge handler.");
216 synchronized (oldValues) {
219 config.set(getConfigAs(PLCLogoBridgeConfiguration.class));
221 boolean configured = (config.get().getLocalTSAP() != null);
222 configured = configured && (config.get().getRemoteTSAP() != null);
225 if (client == null) {
226 client = new PLCLogoClient();
228 configured = connect();
230 String message = "Can not initialize LOGO!. Please, check ip address / TSAP settings.";
231 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message);
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);
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);
246 updateStatus(ThingStatus.ONLINE);
248 String message = "Can not initialize LOGO!. Please, check network connection.";
249 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
255 public void dispose() {
256 logger.debug("Dispose LOGO! bridge handler.");
259 if (rtcJob != null) {
260 rtcJob.cancel(false);
262 logger.info("Destroy RTC job for {}.", config.get().getAddress());
265 if (readerJob != null) {
266 readerJob.cancel(false);
268 logger.info("Destroy reader job for {}.", config.get().getAddress());
275 synchronized (oldValues) {
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);
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);
301 super.childHandlerDisposed(childHandler, childThing);
305 * Returns Siemens LOGO! communication client
307 * @return Configured Siemens LOGO! client
309 public @Nullable PLCLogoClient getLogoClient() {
314 * Returns configured Siemens LOGO! family: 0BA7 or 0BA8
316 * @return Configured Siemens LOGO! family
318 public String getLogoFamily() {
319 return config.get().getFamily();
323 * Returns RTC was fetched last from Siemens LOGO!
325 * @return Siemens LOGO! RTC
327 public ZonedDateTime getLogoRTC() {
332 protected void updateConfiguration(Configuration configuration) {
333 super.updateConfiguration(configuration);
334 config.set(getConfigAs(PLCLogoBridgeConfiguration.class));
338 * Read connection parameter and connect to Siemens LOGO!
340 * @return True, if connected and false otherwise
342 private boolean connect() {
343 boolean result = false;
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());
352 result = localClient.isConnected();
358 * Disconnect from Siemens LOGO!
360 * @return True, if disconnected and false otherwise
362 private boolean disconnect() {
363 boolean result = false;
365 PLCLogoClient localClient = client;
366 if (localClient != null) {
367 if (localClient.isConnected()) {
368 localClient.Disconnect();
370 result = !localClient.isConnected();