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.lcn.internal;
15 import java.util.Collection;
16 import java.util.Optional;
18 import java.util.function.Consumer;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.lcn.internal.common.LcnAddr;
23 import org.openhab.binding.lcn.internal.common.LcnAddrMod;
24 import org.openhab.binding.lcn.internal.common.LcnDefs;
25 import org.openhab.binding.lcn.internal.common.LcnDefs.OutputPortDimMode;
26 import org.openhab.binding.lcn.internal.common.LcnException;
27 import org.openhab.binding.lcn.internal.connection.Connection;
28 import org.openhab.binding.lcn.internal.connection.ConnectionCallback;
29 import org.openhab.binding.lcn.internal.connection.ConnectionSettings;
30 import org.openhab.binding.lcn.internal.connection.ModInfo;
31 import org.openhab.core.thing.Bridge;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.binding.BaseBridgeHandler;
37 import org.openhab.core.thing.binding.ThingHandler;
38 import org.openhab.core.thing.binding.ThingHandlerService;
39 import org.openhab.core.types.Command;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 * The {@link PckGatewayHandler} is responsible for the communication via a PCK gateway.
46 * @author Fabian Wolter - Initial contribution
49 public class PckGatewayHandler extends BaseBridgeHandler {
50 private final Logger logger = LoggerFactory.getLogger(PckGatewayHandler.class);
51 private @Nullable Connection connection;
52 private Optional<Consumer<String>> pckListener = Optional.empty();
53 private @Nullable PckGatewayConfiguration config;
55 public PckGatewayHandler(Bridge bridge) {
60 public void handleCommand(ChannelUID channelUID, Command command) {
65 public synchronized void initialize() {
66 PckGatewayConfiguration localConfig = config = getConfigAs(PckGatewayConfiguration.class);
68 String errorMessage = "Could not connect to LCN-PCHK/VISU: " + localConfig.getHostname() + ": ";
71 OutputPortDimMode dimMode;
72 String mode = localConfig.getMode();
73 if (LcnDefs.OutputPortDimMode.NATIVE50.name().equalsIgnoreCase(mode)) {
74 dimMode = LcnDefs.OutputPortDimMode.NATIVE50;
75 } else if (LcnDefs.OutputPortDimMode.NATIVE200.name().equalsIgnoreCase(mode)) {
76 dimMode = LcnDefs.OutputPortDimMode.NATIVE200;
78 throw new LcnException("DimMode " + mode + " is not supported");
81 ConnectionSettings settings = new ConnectionSettings("0", localConfig.getHostname(), localConfig.getPort(),
82 localConfig.getUsername(), localConfig.getPassword(), dimMode, LcnDefs.OutputPortStatusMode.PERCENT,
83 localConfig.getTimeoutMs());
85 connection = new Connection(settings, scheduler, new ConnectionCallback() {
87 public void onOnline() {
88 updateStatus(ThingStatus.ONLINE);
92 public void onOffline(@Nullable String errorMessage) {
93 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage + ".");
97 public void onPckMessageReceived(String message) {
98 pckListener.ifPresent(l -> l.accept(message));
99 getThing().getThings().stream().filter(t -> t.getStatus() == ThingStatus.ONLINE).map(t -> {
100 LcnModuleHandler handler = (LcnModuleHandler) t.getHandler();
101 if (handler == null) {
102 logger.warn("Failed to process PCK message: Handler not set");
105 }).filter(h -> h != null).forEach(h -> h.handleStatusMessage(message));
109 updateStatus(ThingStatus.UNKNOWN);
110 } catch (LcnException e) {
111 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMessage + e.getMessage());
116 public Collection<Class<? extends ThingHandlerService>> getServices() {
117 return Set.of(LcnModuleDiscoveryService.class);
121 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
122 if (childThing.getThingTypeUID().equals(LcnBindingConstants.THING_TYPE_MODULE)
123 || childThing.getThingTypeUID().equals(LcnBindingConstants.THING_TYPE_GROUP)) {
125 LcnAddr addr = getLcnAddrFromThing(childThing);
126 Connection localConnection = connection;
127 if (localConnection != null) {
128 localConnection.removeLcnModule(addr);
130 } catch (LcnException e) {
131 logger.warn("Failed to read configuration: {}", e.getMessage());
136 private LcnAddr getLcnAddrFromThing(Thing childThing) throws LcnException {
137 LcnModuleHandler lcnModuleHandler = (LcnModuleHandler) childThing.getHandler();
138 if (lcnModuleHandler != null) {
139 return lcnModuleHandler.getCommandAddress();
141 throw new LcnException("Could not get module handler");
146 * Enqueues a PCK (String) command to be sent to an LCN module.
148 * @param addr the modules address
149 * @param wantsAck true, if the module shall send an ACK upon successful processing
150 * @param pck the command to send
152 public void queue(LcnAddr addr, boolean wantsAck, String pck) {
153 Connection localConnection = connection;
154 if (localConnection != null) {
155 localConnection.queue(addr, wantsAck, pck);
157 logger.warn("Dropped PCK command: {}", pck);
162 * Enqueues a PCK (ByteBuffer) command to be sent to an LCN module.
164 * @param addr the modules address
165 * @param wantsAck true, if the module shall send an ACK upon successful processing
166 * @param pck the command to send
168 public void queue(LcnAddr addr, boolean wantsAck, byte[] pck) {
169 Connection localConnection = connection;
170 if (localConnection != null) {
171 localConnection.queue(addr, wantsAck, pck);
173 logger.warn("Dropped PCK command of length: {}", pck.length);
178 * Sends a broadcast message to all LCN modules: All LCN modules are requested to answer with an Ack.
180 void sendModuleDiscoveryCommand() {
181 Connection localConnection = connection;
182 if (localConnection != null) {
183 localConnection.sendModuleDiscoveryCommand();
188 * Send a request to an LCN module to respond with its serial number and firmware version.
190 * @param addr the module's address
192 void sendSerialNumberRequest(LcnAddrMod addr) {
193 Connection localConnection = connection;
194 if (localConnection != null) {
195 localConnection.sendSerialNumberRequest(addr);
200 * Send a request to an LCN module to respond with its configured name.
202 * @param addr the module's address
204 void sendModuleNameRequest(LcnAddrMod addr) {
205 Connection localConnection = connection;
206 if (localConnection != null) {
207 localConnection.sendModuleNameRequest(addr);
212 * Returns the ModInfo to a given module. Will be created if it doesn't exist,yet.
214 * @param addr the module's address
215 * @return the ModInfo
216 * @throws LcnException when this handler is not initialized, yet
218 ModInfo getModInfo(LcnAddrMod addr) throws LcnException {
219 Connection localConnection = connection;
220 if (localConnection != null) {
221 return localConnection.updateModuleData(addr);
223 throw new LcnException("Connection is null");
228 * Registers a listener to receive all PCK messages from this PCK gateway.
230 * @param listener the listener to add
232 void registerPckListener(Consumer<String> listener) {
233 this.pckListener = Optional.of(listener);
237 * Removes all listeners for PCK messages from this PCK gateway.
239 void removeAllPckListeners() {
240 this.pckListener = Optional.empty();
244 * Gets the Connection for this handler.
246 * @return the Connection
249 public Connection getConnection() {
254 * Gets the local segment ID. When no segments are used, the value is 0.
256 * @return the local segment ID
258 public int getLocalSegmentId() {
259 Connection localConnection = connection;
260 if (localConnection != null) {
261 return localConnection.getLocalSegId();
268 * Translates the given physical segment ID (0 or 4 if local segment) to the logical segment ID (local segment ID).
270 * @param physicalSegmentId the segment ID to convert
271 * @return the converted segment ID
273 public int toLogicalSegmentId(int physicalSegmentId) {
274 int localSegmentId = getLocalSegmentId();
275 if ((physicalSegmentId == 0 || physicalSegmentId == 4) && localSegmentId != -1) {
276 // PCK message came from local segment
277 // physicalSegmentId == 0 => Module is programmed to send status messages to local segment only
278 // physicalSegmentId == 4 => Module is programmed to send status messages globally (to all segments)
279 // or segment coupler scan did not finish, yet (-1). Assume local segment, then.
280 return localSegmentId;
282 return physicalSegmentId;
287 public void dispose() {
288 Connection localConnection = connection;
289 if (localConnection != null) {
290 localConnection.shutdown();
295 * Gets the configured connection timeout for the PCK gateway.
297 * @return the timeout in ms
299 public long getTimeoutMs() {
300 PckGatewayConfiguration localConfig = config;
301 return localConfig != null ? localConfig.getTimeoutMs() : 3500;