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