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.ZonedDateTime;
18 import java.util.List;
21 import java.util.concurrent.atomic.AtomicReference;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.plclogo.internal.PLCLogoClient;
25 import org.openhab.binding.plclogo.internal.config.PLCDateTimeConfiguration;
26 import org.openhab.core.config.core.Configuration;
27 import org.openhab.core.library.types.DateTimeType;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.Channel;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.binding.builder.ChannelBuilder;
36 import org.openhab.core.thing.binding.builder.ThingBuilder;
37 import org.openhab.core.thing.type.ChannelTypeUID;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.types.State;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 import Moka7.S7Client;
48 * The {@link PLCDateTimeHandler} is responsible for handling commands, which are
49 * sent to one of the channels.
51 * @author Alexander Falkenstern - Initial contribution
54 public class PLCDateTimeHandler extends PLCCommonHandler {
56 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_DATETIME);
58 private final Logger logger = LoggerFactory.getLogger(PLCDateTimeHandler.class);
59 private AtomicReference<PLCDateTimeConfiguration> config = new AtomicReference<>();
64 public PLCDateTimeHandler(Thing thing) {
69 public void handleCommand(ChannelUID channelUID, Command command) {
70 if (!isThingOnline()) {
74 Channel channel = getThing().getChannel(channelUID.getId());
75 String name = config.get().getBlockName();
76 if (!isValid(name) || (channel == null)) {
77 logger.debug("Can not update channel {}, block {}.", channelUID, name);
81 int address = getAddress(name);
82 PLCLogoClient client = getLogoClient();
83 if ((address != INVALID) && (client != null)) {
84 if (command instanceof RefreshType) {
85 byte[] buffer = new byte[getBufferLength()];
86 int result = client.readDBArea(1, 0, buffer.length, S7Client.S7WLByte, buffer);
88 updateChannel(channel, S7.GetShortAt(buffer, address));
90 logger.debug("Can not read data from LOGO!: {}.", S7Client.ErrorText(result));
92 } else if (command instanceof DateTimeType dateTimeCommand) {
93 byte[] buffer = new byte[2];
94 String type = channel.getAcceptedItemType();
95 if (DATE_TIME_ITEM.equalsIgnoreCase(type)) {
96 ZonedDateTime datetime = dateTimeCommand.getZonedDateTime();
97 if ("Time".equalsIgnoreCase(channelUID.getId())) {
98 buffer[0] = S7.ByteToBCD(datetime.getHour());
99 buffer[1] = S7.ByteToBCD(datetime.getMinute());
100 } else if ("Date".equalsIgnoreCase(channelUID.getId())) {
101 buffer[0] = S7.ByteToBCD(datetime.getMonthValue());
102 buffer[1] = S7.ByteToBCD(datetime.getDayOfMonth());
105 logger.debug("Channel {} will not accept {} items.", channelUID, type);
107 int result = client.writeDBArea(1, address, buffer.length, S7Client.S7WLByte, buffer);
109 logger.debug("Can not write data to LOGO!: {}.", S7Client.ErrorText(result));
112 logger.debug("Channel {} received not supported command {}.", channelUID, command);
115 logger.info("Invalid channel {} or client {} found.", channelUID, client);
120 public void setData(final byte[] data) {
121 if (!isThingOnline()) {
125 if (data.length != getBufferLength()) {
126 logger.info("Received and configured data sizes does not match.");
130 List<Channel> channels = getThing().getChannels();
131 if (channels.size() != getNumberOfChannels()) {
132 logger.info("Received and configured channel sizes does not match.");
136 String name = config.get().getBlockName();
137 Boolean force = config.get().isUpdateForced();
138 for (Channel channel : channels) {
139 int address = getAddress(name);
140 if (address != INVALID) {
141 DecimalType state = (DecimalType) getOldValue(name);
142 int value = S7.GetShortAt(data, address);
143 if ((state == null) || (value != state.intValue()) || force) {
144 updateChannel(channel, value);
146 if (logger.isTraceEnabled()) {
147 logger.trace("Channel {} received [{}, {}].", channel.getUID(), data[address], data[address + 1]);
150 logger.info("Invalid channel {} found.", channel.getUID());
156 protected void updateState(ChannelUID channelUID, State state) {
157 super.updateState(channelUID, state);
158 if (state instanceof DecimalType) {
159 setOldValue(config.get().getBlockName(), state);
164 protected void updateConfiguration(Configuration configuration) {
165 super.updateConfiguration(configuration);
166 config.set(getConfigAs(PLCDateTimeConfiguration.class));
170 protected boolean isValid(final String name) {
171 if (3 <= name.length() && (name.length() <= 5)) {
172 String kind = getBlockKind();
173 if (Character.isDigit(name.charAt(2))) {
174 return name.startsWith(kind) && MEMORY_WORD.equalsIgnoreCase(kind);
181 protected String getBlockKind() {
182 return config.get().getBlockKind();
186 protected int getNumberOfChannels() {
191 protected void doInitialization() {
192 Thing thing = getThing();
193 logger.debug("Initialize LOGO! date/time handler.");
195 config.set(getConfigAs(PLCDateTimeConfiguration.class));
197 super.doInitialization();
198 if (ThingStatus.OFFLINE != thing.getStatus()) {
199 String block = config.get().getBlockType();
200 String text = "Time".equalsIgnoreCase(block) ? "Time" : "Date";
202 ThingBuilder tBuilder = editThing();
204 String label = thing.getLabel();
206 Bridge bridge = getBridge();
207 label = (bridge == null) || (bridge.getLabel() == null) ? "Siemens Logo!" : bridge.getLabel();
208 label += (": " + text.toLowerCase() + " in/output");
210 tBuilder.withLabel(label);
212 String name = config.get().getBlockName();
213 String type = config.get().getChannelType();
214 ChannelUID uid = new ChannelUID(thing.getUID(), "Time".equalsIgnoreCase(block) ? "time" : "date");
215 ChannelBuilder cBuilder = ChannelBuilder.create(uid, type);
216 cBuilder.withType(new ChannelTypeUID(BINDING_ID, type.toLowerCase()));
217 cBuilder.withLabel(name);
218 cBuilder.withDescription(text + " block parameter " + name);
219 cBuilder.withProperties(Map.of(BLOCK_PROPERTY, name));
220 tBuilder.withChannel(cBuilder.build());
222 cBuilder = ChannelBuilder.create(new ChannelUID(thing.getUID(), VALUE_CHANNEL), ANALOG_ITEM);
223 cBuilder.withType(new ChannelTypeUID(BINDING_ID, ANALOG_ITEM.toLowerCase()));
224 cBuilder.withLabel(name);
225 cBuilder.withDescription(text + " block parameter " + name);
226 cBuilder.withProperties(Map.of(BLOCK_PROPERTY, name));
227 tBuilder.withChannel(cBuilder.build());
228 setOldValue(name, null);
230 updateThing(tBuilder.build());
231 updateStatus(ThingStatus.ONLINE);
235 private void updateChannel(final Channel channel, int value) {
236 ChannelUID channelUID = channel.getUID();
237 PLCBridgeHandler handler = getBridgeHandler();
238 if (handler == null) {
239 String name = config.get().getBlockName();
240 logger.debug("Can not update channel {}, block {}.", channelUID, name);
244 String type = channel.getAcceptedItemType();
245 if (DATE_TIME_ITEM.equalsIgnoreCase(type)) {
246 String channelId = channelUID.getId();
247 ZonedDateTime datetime = ZonedDateTime.from(handler.getLogoRTC());
249 byte[] data = new byte[2];
250 S7.SetShortAt(data, 0, value);
251 if ("Time".equalsIgnoreCase(channelId)) {
252 if ((value < 0x0) || (value > 0x2359)) {
253 logger.debug("Channel {} got garbage time {}.", channelUID, Long.toHexString(value));
255 datetime = datetime.withHour(S7.BCDtoByte(data[0]));
256 datetime = datetime.withMinute(S7.BCDtoByte(data[1]));
257 } else if ("Date".equalsIgnoreCase(channelId)) {
258 if ((value < 0x0101) || (value > 0x1231)) {
259 logger.debug("Channel {} got garbage date {}.", channelUID, Long.toHexString(value));
261 datetime = datetime.withMonth(S7.BCDtoByte(data[0]));
262 datetime = datetime.withDayOfMonth(S7.BCDtoByte(data[1]));
264 logger.debug("Channel {} has wrong id {}.", channelUID, channelId);
266 updateState(channelUID, new DateTimeType(datetime));
267 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, datetime);
268 } else if (ANALOG_ITEM.equalsIgnoreCase(type)) {
269 updateState(channelUID, new DecimalType(value));
270 logger.debug("Channel {} accepting {} was set to {}.", channelUID, type, value);
272 logger.debug("Channel {} will not accept {} items.", channelUID, type);