]> git.basschouten.com Git - openhab-addons.git/blob
8b5a2a7af525e3c7d05c2e8807db84a40fdb2897
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.lcn.internal;
14
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.NoSuchElementException;
22 import java.util.Optional;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.lcn.internal.common.DimmerOutputCommand;
27 import org.openhab.binding.lcn.internal.common.LcnAddr;
28 import org.openhab.binding.lcn.internal.common.LcnAddrMod;
29 import org.openhab.binding.lcn.internal.common.LcnChannelGroup;
30 import org.openhab.binding.lcn.internal.common.LcnException;
31 import org.openhab.binding.lcn.internal.connection.Connection;
32 import org.openhab.binding.lcn.internal.connection.ModInfo;
33 import org.openhab.binding.lcn.internal.converter.Converter;
34 import org.openhab.binding.lcn.internal.converter.Converters;
35 import org.openhab.binding.lcn.internal.converter.InversionConverter;
36 import org.openhab.binding.lcn.internal.converter.S0Converter;
37 import org.openhab.binding.lcn.internal.subhandler.AbstractLcnModuleSubHandler;
38 import org.openhab.binding.lcn.internal.subhandler.LcnModuleMetaAckSubHandler;
39 import org.openhab.binding.lcn.internal.subhandler.LcnModuleMetaFirmwareSubHandler;
40 import org.openhab.core.library.types.DecimalType;
41 import org.openhab.core.library.types.HSBType;
42 import org.openhab.core.library.types.OnOffType;
43 import org.openhab.core.library.types.PercentType;
44 import org.openhab.core.library.types.QuantityType;
45 import org.openhab.core.library.types.StopMoveType;
46 import org.openhab.core.library.types.StringType;
47 import org.openhab.core.library.types.UpDownType;
48 import org.openhab.core.thing.Bridge;
49 import org.openhab.core.thing.Channel;
50 import org.openhab.core.thing.ChannelUID;
51 import org.openhab.core.thing.Thing;
52 import org.openhab.core.thing.ThingStatus;
53 import org.openhab.core.thing.ThingStatusDetail;
54 import org.openhab.core.thing.binding.BaseThingHandler;
55 import org.openhab.core.thing.binding.ThingHandlerService;
56 import org.openhab.core.types.Command;
57 import org.openhab.core.types.RefreshType;
58 import org.openhab.core.types.State;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 /**
63  * The {@link LcnModuleHandler} is responsible for handling commands, which are
64  * sent to or received from one of the channels.
65  *
66  * @author Fabian Wolter - Initial contribution
67  */
68 @NonNullByDefault
69 public class LcnModuleHandler extends BaseThingHandler {
70     private final Logger logger = LoggerFactory.getLogger(LcnModuleHandler.class);
71     private static final Map<String, Converter> VALUE_CONVERTERS = new HashMap<>();
72     private static final InversionConverter INVERSION_CONVERTER = new InversionConverter();
73     private @Nullable LcnAddrMod moduleAddress;
74     private final Map<LcnChannelGroup, AbstractLcnModuleSubHandler> subHandlers = new HashMap<>();
75     private final List<AbstractLcnModuleSubHandler> metadataSubHandlers = new ArrayList<>();
76     private final Map<ChannelUID, Converter> converters = new HashMap<>();
77
78     static {
79         VALUE_CONVERTERS.put("temperature", Converters.TEMPERATURE);
80         VALUE_CONVERTERS.put("light", Converters.LIGHT);
81         VALUE_CONVERTERS.put("co2", Converters.CO2);
82         VALUE_CONVERTERS.put("current", Converters.CURRENT);
83         VALUE_CONVERTERS.put("voltage", Converters.VOLTAGE);
84         VALUE_CONVERTERS.put("angle", Converters.ANGLE);
85         VALUE_CONVERTERS.put("windspeed", Converters.WINDSPEED);
86     }
87
88     public LcnModuleHandler(Thing thing) {
89         super(thing);
90     }
91
92     @Override
93     public void initialize() {
94         LcnModuleConfiguration localConfig = getConfigAs(LcnModuleConfiguration.class);
95         LcnAddrMod localModuleAddress = moduleAddress = new LcnAddrMod(localConfig.segmentId, localConfig.moduleId);
96
97         try {
98             // Determine serial number of manually added modules
99             requestFirmwareVersionAndSerialNumberIfNotSet();
100
101             // create sub handlers
102             ModInfo info = getPckGatewayHandler().getModInfo(localModuleAddress);
103             for (LcnChannelGroup type : LcnChannelGroup.values()) {
104                 subHandlers.put(type, type.createSubHandler(this, info));
105             }
106
107             // meta sub handlers, which are not assigned to a channel group
108             metadataSubHandlers.add(new LcnModuleMetaAckSubHandler(this, info));
109             metadataSubHandlers.add(new LcnModuleMetaFirmwareSubHandler(this, info));
110
111             // initialize converters
112             for (Channel channel : thing.getChannels()) {
113                 Object unitObject = channel.getConfiguration().get("unit");
114                 Object parameterObject = channel.getConfiguration().get("parameter");
115                 Object invertState = channel.getConfiguration().get("invertState");
116                 Object invertUpDown = channel.getConfiguration().get("invertUpDown");
117
118                 // Initialize value converters
119                 if (unitObject instanceof String) {
120                     switch ((String) unitObject) {
121                         case "power":
122                         case "energy":
123                             converters.put(channel.getUID(), new S0Converter(parameterObject));
124                             break;
125                         default:
126                             Converter converter = VALUE_CONVERTERS.get(unitObject);
127                             if (converter != null) {
128                                 converters.put(channel.getUID(), converter);
129                             }
130                             break;
131                     }
132                 }
133
134                 // Initialize inversion converter
135                 if (Boolean.TRUE.equals(invertState) || Boolean.TRUE.equals(invertUpDown)) {
136                     converters.put(channel.getUID(), INVERSION_CONVERTER);
137                 }
138
139             }
140
141             // module is assumed as online, when the corresponding Bridge (PckGatewayHandler) is online.
142             updateStatus(ThingStatus.ONLINE);
143
144             // trigger REFRESH commands for all linked Channels to start polling
145             getThing().getChannels().forEach(channel -> {
146                 if (isLinked(channel.getUID())) {
147                     channelLinked(channel.getUID());
148                 }
149             });
150         } catch (LcnException e) {
151             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
152         }
153     }
154
155     /**
156      * Triggers requesting the firmware version of the LCN module. The message also contains the serial number.
157      *
158      * @throws LcnException when the handler is not initialized
159      */
160     @SuppressWarnings("null")
161     protected void requestFirmwareVersionAndSerialNumberIfNotSet() throws LcnException {
162         String serialNumber = getThing().getProperties().get(Thing.PROPERTY_SERIAL_NUMBER);
163         if (serialNumber == null || serialNumber.isEmpty()) {
164             LcnAddrMod localModuleAddress = moduleAddress;
165             if (localModuleAddress != null) {
166                 getPckGatewayHandler().getModInfo(localModuleAddress).requestFirmwareVersion();
167             }
168         }
169     }
170
171     @Override
172     public void handleCommand(ChannelUID channelUid, Command command) {
173         try {
174             String groupId = channelUid.getGroupId();
175
176             if (!channelUid.isInGroup()) {
177                 return;
178             }
179
180             if (groupId == null) {
181                 throw new LcnException("Group ID is null");
182             }
183
184             LcnChannelGroup channelGroup = LcnChannelGroup.valueOf(groupId.toUpperCase());
185             AbstractLcnModuleSubHandler subHandler = subHandlers.get(channelGroup);
186
187             if (subHandler == null) {
188                 throw new LcnException("Sub Handler not found for: " + channelGroup);
189             }
190
191             Optional<Integer> number = channelUidToChannelNumber(channelUid, channelGroup);
192
193             if (command instanceof RefreshType) {
194                 number.ifPresent(n -> subHandler.handleRefresh(channelGroup, n));
195                 subHandler.handleRefresh(channelUid.getIdWithoutGroup());
196             } else if (command instanceof OnOffType) {
197                 subHandler.handleCommandOnOff((OnOffType) command, channelGroup, number.get());
198             } else if (command instanceof DimmerOutputCommand) {
199                 subHandler.handleCommandDimmerOutput((DimmerOutputCommand) command, number.get());
200             } else if (command instanceof PercentType && number.isPresent()) {
201                 subHandler.handleCommandPercent((PercentType) command, channelGroup, number.get());
202             } else if (command instanceof HSBType) {
203                 subHandler.handleCommandHsb((HSBType) command, channelUid.getIdWithoutGroup());
204             } else if (command instanceof PercentType) {
205                 subHandler.handleCommandPercent((PercentType) command, channelGroup, channelUid.getIdWithoutGroup());
206             } else if (command instanceof StringType) {
207                 subHandler.handleCommandString((StringType) command, number.get());
208             } else if (command instanceof DecimalType) {
209                 DecimalType decimalType = (DecimalType) command;
210                 DecimalType nativeValue = getConverter(channelUid).onCommandFromItem(decimalType.doubleValue());
211                 subHandler.handleCommandDecimal(nativeValue, channelGroup, number.get());
212             } else if (command instanceof QuantityType) {
213                 QuantityType<?> quantityType = (QuantityType<?>) command;
214                 DecimalType nativeValue = getConverter(channelUid).onCommandFromItem(quantityType);
215                 subHandler.handleCommandDecimal(nativeValue, channelGroup, number.get());
216             } else if (command instanceof UpDownType) {
217                 Channel channel = thing.getChannel(channelUid);
218                 if (channel != null) {
219                     Object invertConfig = channel.getConfiguration().get("invertUpDown");
220                     boolean invertUpDown = invertConfig instanceof Boolean && (boolean) invertConfig;
221                     subHandler.handleCommandUpDown((UpDownType) command, channelGroup, number.get(), invertUpDown);
222                 }
223             } else if (command instanceof StopMoveType) {
224                 subHandler.handleCommandStopMove((StopMoveType) command, channelGroup, number.get());
225             } else {
226                 throw new LcnException("Unsupported command type");
227             }
228         } catch (IllegalArgumentException | NoSuchElementException | LcnException e) {
229             logger.warn("{}: Failed to handle command {}: {}", channelUid, command.getClass().getSimpleName(),
230                     e.getMessage());
231         }
232     }
233
234     @NonNullByDefault({}) // getOrDefault()
235     private Converter getConverter(ChannelUID channelUid) {
236         return converters.getOrDefault(channelUid, Converters.IDENTITY);
237     }
238
239     /**
240      * Invoked when a PCK messages arrives from the PCK gateway
241      *
242      * @param pck the message without line termination
243      */
244     @SuppressWarnings("null")
245     public void handleStatusMessage(String pck) {
246         for (AbstractLcnModuleSubHandler handler : subHandlers.values()) {
247             if (handler.tryParse(pck)) {
248                 break;
249             }
250         }
251
252         metadataSubHandlers.forEach(h -> h.tryParse(pck));
253     }
254
255     private Optional<Integer> channelUidToChannelNumber(ChannelUID channelUid, LcnChannelGroup channelGroup)
256             throws LcnException {
257         try {
258             int number = Integer.parseInt(channelUid.getIdWithoutGroup()) - 1;
259
260             if (!channelGroup.isValidId(number)) {
261                 throw new LcnException("Out of range: " + number);
262             }
263             return Optional.of(number);
264         } catch (NumberFormatException e) {
265             return Optional.empty();
266         }
267     }
268
269     private PckGatewayHandler getPckGatewayHandler() throws LcnException {
270         Bridge bridge = getBridge();
271         if (bridge == null) {
272             throw new LcnException("No LCN-PCK gateway configured for this module");
273         }
274
275         PckGatewayHandler handler = (PckGatewayHandler) bridge.getHandler();
276         if (handler == null) {
277             throw new LcnException("Could not get PckGatewayHandler");
278         }
279         return handler;
280     }
281
282     /**
283      * Queues a PCK string for sending.
284      *
285      * @param command without the address part
286      * @throws LcnException when the module address is unknown
287      */
288     public void sendPck(String command) throws LcnException {
289         getPckGatewayHandler().queue(getCommandAddress(), true, command);
290     }
291
292     /**
293      * Queues a PCK byte buffer for sending.
294      *
295      * @param command without the address part
296      * @throws LcnException when the module address is unknown
297      */
298     public void sendPck(byte[] command) throws LcnException {
299         getPckGatewayHandler().queue(getCommandAddress(), true, command);
300     }
301
302     /**
303      * Gets the address, which shall be used when sending commands into the LCN bus. This can also be a group address.
304      *
305      * @return the address to send to
306      * @throws LcnException when the address is unknown
307      */
308     protected LcnAddr getCommandAddress() throws LcnException, LcnException {
309         LcnAddr localAddress = moduleAddress;
310         if (localAddress == null) {
311             throw new LcnException("Module address not set");
312         }
313         return localAddress;
314     }
315
316     /**
317      * Invoked when an update for this LCN module should be fired to openHAB.
318      *
319      * @param channelGroup the Channel to update
320      * @param channelId the ID within the Channel to update
321      * @param state the new state
322      */
323     public void updateChannel(LcnChannelGroup channelGroup, String channelId, State state) {
324         ChannelUID channelUid = createChannelUid(channelGroup, channelId);
325         Converter converter = converters.get(channelUid);
326
327         State convertedState = state;
328         if (converter != null) {
329             convertedState = converter.onStateUpdateFromHandler(state);
330         }
331
332         updateState(channelUid, convertedState);
333     }
334
335     /**
336      * Updates the LCN module's serial number property.
337      *
338      * @param serialNumber the new serial number
339      */
340     public void updateSerialNumberProperty(String serialNumber) {
341         updateProperty(Thing.PROPERTY_SERIAL_NUMBER, serialNumber);
342     }
343
344     /**
345      * Invoked when an trigger for this LCN module should be fired to openHAB.
346      *
347      * @param channelGroup the Channel to update
348      * @param channelId the ID within the Channel to update
349      * @param event the event used to trigger
350      */
351     public void triggerChannel(LcnChannelGroup channelGroup, String channelId, String event) {
352         triggerChannel(createChannelUid(channelGroup, channelId), event);
353     }
354
355     private ChannelUID createChannelUid(LcnChannelGroup channelGroup, String channelId) {
356         return new ChannelUID(thing.getUID(), channelGroup.name().toLowerCase() + "#" + channelId);
357     }
358
359     /**
360      * Checks the LCN module address against the own.
361      *
362      * @param physicalSegmentId which is 0 if it is the local segment
363      * @param moduleId
364      * @return true, if the given address matches the own address
365      */
366     public boolean isMyAddress(String physicalSegmentId, String moduleId) {
367         try {
368             return new LcnAddrMod(getPckGatewayHandler().toLogicalSegmentId(Integer.parseInt(physicalSegmentId)),
369                     Integer.parseInt(moduleId)).equals(getStatusMessageAddress());
370         } catch (LcnException e) {
371             return false;
372         }
373     }
374
375     @Override
376     public Collection<Class<? extends ThingHandlerService>> getServices() {
377         return Collections.singleton(LcnModuleActions.class);
378     }
379
380     /**
381      * Invoked when an Ack from this module has been received.
382      */
383     public void onAckRceived() {
384         try {
385             Connection connection = getPckGatewayHandler().getConnection();
386             LcnAddrMod localModuleAddress = moduleAddress;
387             if (connection != null && localModuleAddress != null) {
388                 getPckGatewayHandler().getModInfo(localModuleAddress).onAck(LcnBindingConstants.CODE_ACK, connection,
389                         getPckGatewayHandler().getTimeoutMs(), System.nanoTime());
390             }
391         } catch (LcnException e) {
392             logger.warn("Connection or module address not set");
393         }
394     }
395
396     /**
397      * Gets the address the handler shall react to, when a status message from this address is processed.
398      *
399      * @return the address for status messages
400      */
401     public LcnAddrMod getStatusMessageAddress() {
402         LcnAddrMod localmoduleAddress = moduleAddress;
403         if (localmoduleAddress != null) {
404             return localmoduleAddress;
405         } else {
406             return new LcnAddrMod(0, 0);
407         }
408     }
409
410     @Override
411     public void dispose() {
412         metadataSubHandlers.clear();
413         subHandlers.clear();
414         converters.clear();
415     }
416 }