[flicbutton] Initial contribution FlicButton Binding (#9234)
* [flicbutton] Initial contribution FlicButton Binding Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Add config parameter address for FlicButton thing Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Run spotless Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Code cleanup & docs improvement Signed-off-by: Patrick Fink <mail@pfink.de> * Apply suggestions from code review Co-authored-by: Fabian Wolter <github@fabian-wolter.de> * [flicbutton] Update LICENSE Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Apply suggestions from code review (2) & update to 3.1-SNAPSHOT Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Apply suggestions from code review (3) & fix offline status Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix 3rd party source for proper IDE integration Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Simplify config parsing Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Move everything to internal package Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Remove hyphens from port parameter docs example Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Change maintainer to openHAB project Signed-off-by: Patrick Fink <mail@pfink.de> * Apply docs suggestions + update to 3.2.0-SNAPSHOT Signed-off-by: Patrick Fink <mail@pfink.de> Co-authored-by: Matthew Skinner <matt@pcmus.com> * [flicbutton] Fix bridge offline & reconnect handling Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Close open socket on dispose Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Improve exception error message in ThingStatus Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix README title Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Improve exception error message in ThingStatus Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Style fixes Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Use trace log level for button clicks & status changes Signed-off-by: Patrick Fink <mail@pfink.de> * Apply doc improvements from code review Signed-off-by: Patrick Fink <mail@pfink.de> Co-authored-by: Matthew Skinner <matt@pcmus.com> * [flicbutton] Add binding to bom/openhab-addons Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Cleanup / remove guava leftover Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Remove online status description Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Improve flicd hostname label Signed-off-by: Patrick Fink <mail@pfink.de> Co-authored-by: Fabian Wolter <github@fabian-wolter.de> * [flicbutton] Do not catch IllegalArgumentException anymore as its not neeed Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Use debug log level instead of info Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Update version and license Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix SAT warnings, e.g. add null handling annotations Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix SAT warnings (2) Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Concurrency refactoring & fixes Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Cancel initialization task also when already running Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Add javadoc and move FLIC_OPENHAB_EVENT_TRIGGER_MAP constant to constants class Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Use ThingStatusDetail.OFFLINE.GONE when Flic button was removed from bridge Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix FlicSimpleclientDiscoveryServiceImpl javadoc Signed-off-by: Patrick Fink <mail@pfink.de> * [flicbutton] Fix required definition of thing types Signed-off-by: Patrick Fink <mail@pfink.de> Co-authored-by: Fabian Wolter <github@fabian-wolter.de> Co-authored-by: Matthew Skinner <matt@pcmus.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.flicbutton-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-flicbutton" description="FlicButton Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.flicbutton/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.CommonTriggerEvents;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link FlicButtonBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicButtonBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "flicbutton";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "flicd-bridge");
|
||||
public static final ThingTypeUID FLICBUTTON_THING_TYPE = new ThingTypeUID(BINDING_ID, "button");
|
||||
|
||||
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Collections.singleton(BRIDGE_THING_TYPE);
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(FLICBUTTON_THING_TYPE);
|
||||
|
||||
// List of all configuration options
|
||||
public static final String CONFIG_HOST_NAME = "hostname";
|
||||
public static final String CONFIG_PORT = "port";
|
||||
public static final String CONFIG_ADDRESS = "address";
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_ID_RAWBUTTON_EVENTS = "rawbutton";
|
||||
public static final String CHANNEL_ID_BUTTON_EVENTS = "button";
|
||||
public static final String CHANNEL_ID_BATTERY_LEVEL = "battery-level";
|
||||
|
||||
// Other stuff
|
||||
public static final int BUTTON_OFFLINE_GRACE_PERIOD_SECONDS = 60;
|
||||
|
||||
public static final Map<String, String> FLIC_OPENHAB_TRIGGER_EVENT_MAP = Collections
|
||||
.unmodifiableMap(new HashMap<String, String>() {
|
||||
{
|
||||
put("ButtonSingleClick", CommonTriggerEvents.SHORT_PRESSED);
|
||||
put("ButtonDoubleClick", CommonTriggerEvents.DOUBLE_PRESSED);
|
||||
put("ButtonHold", CommonTriggerEvents.LONG_PRESSED);
|
||||
put("ButtonDown", CommonTriggerEvents.PRESSED);
|
||||
put("ButtonUp", CommonTriggerEvents.RELEASED);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.flicbutton.internal.discovery.FlicButtonDiscoveryService;
|
||||
import org.openhab.binding.flicbutton.internal.discovery.FlicSimpleclientDiscoveryServiceImpl;
|
||||
import org.openhab.binding.flicbutton.internal.handler.FlicButtonHandler;
|
||||
import org.openhab.binding.flicbutton.internal.handler.FlicDaemonBridgeHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link FlicButtonHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.flicbutton")
|
||||
@NonNullByDefault
|
||||
public class FlicButtonHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||
.concat(FlicButtonBindingConstants.BRIDGE_THING_TYPES_UIDS.stream(),
|
||||
FlicButtonBindingConstants.SUPPORTED_THING_TYPES_UIDS.stream())
|
||||
.collect(Collectors.toSet());
|
||||
private final Map<ThingUID, @Nullable ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(FlicButtonBindingConstants.FLICBUTTON_THING_TYPE)) {
|
||||
return new FlicButtonHandler(thing);
|
||||
} else if (thingTypeUID.equals(FlicButtonBindingConstants.BRIDGE_THING_TYPE)) {
|
||||
FlicButtonDiscoveryService discoveryService = new FlicSimpleclientDiscoveryServiceImpl(thing.getUID());
|
||||
FlicDaemonBridgeHandler bridgeHandler = new FlicDaemonBridgeHandler((Bridge) thing, discoveryService);
|
||||
registerDiscoveryService(discoveryService, thing.getUID());
|
||||
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof FlicDaemonBridgeHandler) {
|
||||
unregisterDiscoveryService(thingHandler.getThing().getUID());
|
||||
}
|
||||
super.removeHandler(thingHandler);
|
||||
}
|
||||
|
||||
private synchronized void registerDiscoveryService(FlicButtonDiscoveryService discoveryService,
|
||||
ThingUID bridgeUID) {
|
||||
this.discoveryServiceRegs.put(bridgeUID, getBundleContext().registerService(DiscoveryService.class.getName(),
|
||||
discoveryService, new Hashtable<String, Object>()));
|
||||
}
|
||||
|
||||
private synchronized void unregisterDiscoveryService(ThingUID bridgeUID) {
|
||||
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.get(bridgeUID);
|
||||
if (serviceReg != null) {
|
||||
FlicButtonDiscoveryService service = (FlicButtonDiscoveryService) getBundleContext()
|
||||
.getService(serviceReg.getReference());
|
||||
if (service != null) {
|
||||
service.deactivate();
|
||||
}
|
||||
serviceReg.unregister();
|
||||
discoveryServiceRegs.remove(bridgeUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.discovery;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
import io.flic.fliclib.javaclient.Bdaddr;
|
||||
import io.flic.fliclib.javaclient.FlicClient;
|
||||
|
||||
/**
|
||||
* A {@link DiscoveryService} for Flic buttons.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface FlicButtonDiscoveryService extends DiscoveryService {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param bdaddr Bluetooth address of the discovered Flic button
|
||||
* @return UID that was created by the discovery service
|
||||
*/
|
||||
public ThingUID flicButtonDiscovered(Bdaddr bdaddr);
|
||||
|
||||
public void activate(FlicClient client);
|
||||
|
||||
public void deactivate();
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.discovery;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants;
|
||||
import org.openhab.binding.flicbutton.internal.util.FlicButtonUtils;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.flic.fliclib.javaclient.Bdaddr;
|
||||
import io.flic.fliclib.javaclient.FlicClient;
|
||||
import io.flic.fliclib.javaclient.GeneralCallbacks;
|
||||
import io.flic.fliclib.javaclient.GetInfoResponseCallback;
|
||||
import io.flic.fliclib.javaclient.enums.BdAddrType;
|
||||
import io.flic.fliclib.javaclient.enums.BluetoothControllerState;
|
||||
|
||||
/**
|
||||
* For each configured flicd service, there is a {@link FlicSimpleclientDiscoveryServiceImpl} which will be initialized
|
||||
* by {@link org.openhab.binding.flicbutton.internal.FlicButtonHandlerFactory}.
|
||||
*
|
||||
* It can scan for Flic Buttons already that are already added to fliclib-linux-hci ("verified" buttons), *
|
||||
* but it does not support adding and verify new buttons on it's own.
|
||||
* New buttons have to be added (verified) e.g. via simpleclient by Shortcut Labs.
|
||||
* Background discovery listens for new buttons that are getting verified.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicSimpleclientDiscoveryServiceImpl extends AbstractDiscoveryService
|
||||
implements FlicButtonDiscoveryService {
|
||||
private final Logger logger = LoggerFactory.getLogger(FlicSimpleclientDiscoveryServiceImpl.class);
|
||||
|
||||
private boolean activated = false;
|
||||
private ThingUID bridgeUID;
|
||||
private @Nullable FlicClient flicClient;
|
||||
|
||||
public FlicSimpleclientDiscoveryServiceImpl(ThingUID bridgeUID) {
|
||||
super(FlicButtonBindingConstants.SUPPORTED_THING_TYPES_UIDS, 2, true);
|
||||
this.bridgeUID = bridgeUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate(FlicClient flicClient) {
|
||||
this.flicClient = flicClient;
|
||||
activated = true;
|
||||
super.activate(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
activated = false;
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
try {
|
||||
if (activated) {
|
||||
discoverVerifiedButtons();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error occured during button discovery", e);
|
||||
if (this.scanListener != null) {
|
||||
scanListener.onErrorOccurred(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void discoverVerifiedButtons() throws IOException {
|
||||
flicClient.getInfo(new GetInfoResponseCallback() {
|
||||
@Override
|
||||
public void onGetInfoResponse(@Nullable BluetoothControllerState bluetoothControllerState,
|
||||
@Nullable Bdaddr myBdAddr, @Nullable BdAddrType myBdAddrType, int maxPendingConnections,
|
||||
int maxConcurrentlyConnectedButtons, int currentPendingConnections,
|
||||
boolean currentlyNoSpaceForNewConnection, Bdaddr @Nullable [] verifiedButtons) throws IOException {
|
||||
for (final @Nullable Bdaddr bdaddr : verifiedButtons) {
|
||||
if (bdaddr != null) {
|
||||
flicButtonDiscovered((@NonNull Bdaddr) bdaddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
super.startBackgroundDiscovery();
|
||||
flicClient.setGeneralCallbacks(new GeneralCallbacks() {
|
||||
@Override
|
||||
public void onNewVerifiedButton(@Nullable Bdaddr bdaddr) throws IOException {
|
||||
logger.debug("A new Flic button was added by an external flicd client: {}", bdaddr);
|
||||
if (bdaddr != null) {
|
||||
flicButtonDiscovered((@NonNull Bdaddr) bdaddr);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
super.stopBackgroundDiscovery();
|
||||
if (flicClient != null) {
|
||||
flicClient.setGeneralCallbacks(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingUID flicButtonDiscovered(Bdaddr bdaddr) {
|
||||
logger.debug("Flic Button {} discovered!", bdaddr);
|
||||
ThingUID flicButtonUID = FlicButtonUtils.getThingUIDFromBdAddr(bdaddr, bridgeUID);
|
||||
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(flicButtonUID).withBridge(bridgeUID)
|
||||
.withLabel("Flic Button " + bdaddr.toString().replace(":", ""))
|
||||
.withProperty(FlicButtonBindingConstants.CONFIG_ADDRESS, bdaddr.toString())
|
||||
.withRepresentationProperty(FlicButtonBindingConstants.CONFIG_ADDRESS).build();
|
||||
this.thingDiscovered(discoveryResult);
|
||||
return flicButtonUID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
|
||||
/**
|
||||
* The {@link ChildThingHandler} class is an abstract class for handlers that are dependent from a parent
|
||||
* {@link BridgeHandler}.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
* @param <BridgeHandlerType> The bridge type this child handler depends on
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class ChildThingHandler<BridgeHandlerType extends BridgeHandler> extends BaseThingHandler {
|
||||
private static final Collection<ThingStatus> DEFAULT_TOLERATED_BRIDGE_STATUSES = Collections
|
||||
.singleton(ThingStatus.ONLINE);
|
||||
protected boolean bridgeValid = false;
|
||||
protected @Nullable BridgeHandlerType bridgeHandler;
|
||||
|
||||
public ChildThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
setStatusBasedOnBridge();
|
||||
if (getBridge() != null) {
|
||||
linkBridge();
|
||||
}
|
||||
}
|
||||
|
||||
protected void linkBridge() {
|
||||
try {
|
||||
BridgeHandler bridgeHandlerUncasted = getBridge().getHandler();
|
||||
bridgeHandler = (BridgeHandlerType) bridgeHandlerUncasted;
|
||||
} catch (ClassCastException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge Type is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
protected void setStatusBasedOnBridge() {
|
||||
setStatusBasedOnBridge(DEFAULT_TOLERATED_BRIDGE_STATUSES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
this.setStatusBasedOnBridge();
|
||||
}
|
||||
|
||||
protected void setStatusBasedOnBridge(Collection<ThingStatus> toleratedBridgeStatuses) {
|
||||
if (getBridge() != null) {
|
||||
if (toleratedBridgeStatuses.contains(getBridge().getStatus())) {
|
||||
bridgeValid = true;
|
||||
} else {
|
||||
bridgeValid = false;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||
"Bridge in unsupported status: " + getBridge().getStatus());
|
||||
}
|
||||
} else {
|
||||
bridgeValid = false;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Bridge missing.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import io.flic.fliclib.javaclient.BatteryStatusListener;
|
||||
import io.flic.fliclib.javaclient.Bdaddr;
|
||||
|
||||
/**
|
||||
* Each {@link FlicButtonBatteryLevelListener} object listens to the battery status of a specific Flic button
|
||||
* and calls updates the {@link FlicButtonHandler} accordingly.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicButtonBatteryLevelListener extends BatteryStatusListener.Callbacks {
|
||||
|
||||
private final FlicButtonHandler thingHandler;
|
||||
|
||||
FlicButtonBatteryLevelListener(FlicButtonHandler thingHandler) {
|
||||
this.thingHandler = thingHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatteryStatus(@Nullable Bdaddr bdaddr, int i, long l) throws IOException {
|
||||
thingHandler.updateBatteryChannel(i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.flic.fliclib.javaclient.ButtonConnectionChannel;
|
||||
import io.flic.fliclib.javaclient.enums.ClickType;
|
||||
import io.flic.fliclib.javaclient.enums.ConnectionStatus;
|
||||
import io.flic.fliclib.javaclient.enums.CreateConnectionChannelError;
|
||||
import io.flic.fliclib.javaclient.enums.DisconnectReason;
|
||||
import io.flic.fliclib.javaclient.enums.RemovedReason;
|
||||
|
||||
/**
|
||||
* Each {@link FlicButtonEventListener} object listens to events of a specific Flic button and calls the
|
||||
* associated {@link FlicButtonHandler} back accordingly.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicButtonEventListener extends ButtonConnectionChannel.Callbacks {
|
||||
private final Logger logger = LoggerFactory.getLogger(FlicButtonEventListener.class);
|
||||
|
||||
private final FlicButtonHandler thingHandler;
|
||||
private final Semaphore channelResponseSemaphore = new Semaphore(0);
|
||||
|
||||
FlicButtonEventListener(FlicButtonHandler thingHandler) {
|
||||
this.thingHandler = thingHandler;
|
||||
}
|
||||
|
||||
public Semaphore getChannelResponseSemaphore() {
|
||||
return channelResponseSemaphore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onCreateConnectionChannelResponse(@Nullable ButtonConnectionChannel channel,
|
||||
@Nullable CreateConnectionChannelError createConnectionChannelError,
|
||||
@Nullable ConnectionStatus connectionStatus) {
|
||||
logger.debug("Create response {}: {}, {}", channel.getBdaddr(), createConnectionChannelError, connectionStatus);
|
||||
// Handling does not differ from Status change, so redirect
|
||||
if (connectionStatus != null) {
|
||||
thingHandler.initializeStatus((@NonNull ConnectionStatus) connectionStatus);
|
||||
channelResponseSemaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(@Nullable ButtonConnectionChannel channel, @Nullable RemovedReason removedReason) {
|
||||
thingHandler.flicButtonRemoved();
|
||||
logger.debug("Button {} removed. ThingStatus updated to OFFLINE. Reason: {}", channel.getBdaddr(),
|
||||
removedReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionStatusChanged(@Nullable ButtonConnectionChannel channel,
|
||||
@Nullable ConnectionStatus connectionStatus, @Nullable DisconnectReason disconnectReason) {
|
||||
logger.trace("New status for {}: {}", channel.getBdaddr(),
|
||||
connectionStatus + (connectionStatus == ConnectionStatus.Disconnected ? ", " + disconnectReason : ""));
|
||||
if (connectionStatus != null) {
|
||||
thingHandler.connectionStatusChanged((@NonNull ConnectionStatus) connectionStatus, disconnectReason);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonUpOrDown(@Nullable ButtonConnectionChannel channel, @Nullable ClickType clickType,
|
||||
boolean wasQueued, int timeDiff) throws IOException {
|
||||
if (channel != null && clickType != null) {
|
||||
logger.trace("{} {}", channel.getBdaddr(), clickType.name());
|
||||
String commonTriggerEvent = FlicButtonBindingConstants.FLIC_OPENHAB_TRIGGER_EVENT_MAP.get(clickType.name());
|
||||
if (commonTriggerEvent != null) {
|
||||
thingHandler.fireTriggerEvent(commonTriggerEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonSingleOrDoubleClickOrHold(@Nullable ButtonConnectionChannel channel,
|
||||
@Nullable ClickType clickType, boolean wasQueued, int timeDiff) throws IOException {
|
||||
// Handling does not differ from up/down events, so redirect
|
||||
if (channel != null && clickType != null) {
|
||||
onButtonUpOrDown((@NonNull ButtonConnectionChannel) channel, (@NonNull ClickType) clickType, wasQueued,
|
||||
timeDiff);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import static org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.CommonTriggerEvents;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.flic.fliclib.javaclient.BatteryStatusListener;
|
||||
import io.flic.fliclib.javaclient.Bdaddr;
|
||||
import io.flic.fliclib.javaclient.ButtonConnectionChannel;
|
||||
import io.flic.fliclib.javaclient.enums.ConnectionStatus;
|
||||
import io.flic.fliclib.javaclient.enums.DisconnectReason;
|
||||
|
||||
/**
|
||||
* The {@link FlicButtonHandler} is responsible for initializing the online status of Flic Buttons
|
||||
* and trigger channel events when they're used.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicButtonHandler extends ChildThingHandler<FlicDaemonBridgeHandler> {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(FlicButtonHandler.class);
|
||||
private @Nullable ScheduledFuture<?> delayedDisconnectTask;
|
||||
private @Nullable Future<?> initializationTask;
|
||||
private @Nullable DisconnectReason latestDisconnectReason;
|
||||
private @Nullable ButtonConnectionChannel eventConnection;
|
||||
private @Nullable Bdaddr bdaddr;
|
||||
private @Nullable BatteryStatusListener batteryConnection;
|
||||
|
||||
public FlicButtonHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
public @Nullable Bdaddr getBdaddr() {
|
||||
return bdaddr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Pure sensor -> no commands have to be handled
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
bdaddr = new Bdaddr((String) this.getThing().getConfiguration().get(CONFIG_ADDRESS));
|
||||
if (bridgeValid) {
|
||||
initializationTask = scheduler.submit(this::initializeThing);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
super.bridgeStatusChanged(bridgeStatusInfo);
|
||||
if (getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE && bridgeValid) {
|
||||
dispose();
|
||||
initializationTask = scheduler.submit(this::initializeThing);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeThing() {
|
||||
try {
|
||||
initializeBatteryListener();
|
||||
initializeEventListener();
|
||||
// EventListener calls initializeStatus() before releasing so that ThingStatus should be set at this point
|
||||
if (this.getThing().getStatus().equals(ThingStatus.INITIALIZING)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Got no response by eventListener");
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Connection setup failed: {}" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeBatteryListener() throws IOException {
|
||||
FlicButtonBatteryLevelListener batteryListener = new FlicButtonBatteryLevelListener(this);
|
||||
BatteryStatusListener batteryConnection = new BatteryStatusListener(getBdaddr(), batteryListener);
|
||||
bridgeHandler.getFlicClient().addBatteryStatusListener(batteryConnection);
|
||||
this.batteryConnection = batteryConnection;
|
||||
}
|
||||
|
||||
public void initializeEventListener() throws IOException, InterruptedException {
|
||||
FlicButtonEventListener eventListener = new FlicButtonEventListener(this);
|
||||
ButtonConnectionChannel eventConnection = new ButtonConnectionChannel(getBdaddr(), eventListener);
|
||||
bridgeHandler.getFlicClient().addConnectionChannel(eventConnection);
|
||||
this.eventConnection = eventConnection;
|
||||
eventListener.getChannelResponseSemaphore().tryAcquire(5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
cancelDelayedDisconnectTask();
|
||||
cancelInitializationTask();
|
||||
try {
|
||||
if (eventConnection != null) {
|
||||
bridgeHandler.getFlicClient().removeConnectionChannel(eventConnection);
|
||||
}
|
||||
if (batteryConnection != null) {
|
||||
bridgeHandler.getFlicClient().removeBatteryStatusListener(this.batteryConnection);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Button channel could not be properly removed", e);
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void initializeStatus(ConnectionStatus connectionStatus) {
|
||||
if (connectionStatus == ConnectionStatus.Disconnected) {
|
||||
setOffline();
|
||||
} else {
|
||||
setOnline();
|
||||
}
|
||||
}
|
||||
|
||||
void connectionStatusChanged(ConnectionStatus connectionStatus, @Nullable DisconnectReason disconnectReason) {
|
||||
latestDisconnectReason = disconnectReason;
|
||||
if (connectionStatus == ConnectionStatus.Disconnected) {
|
||||
// Status change to offline have to be scheduled to improve stability,
|
||||
// see https://github.com/pfink/openhab2-flicbutton/issues/2
|
||||
scheduleStatusChangeToOffline();
|
||||
} else {
|
||||
setOnline();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleStatusChangeToOffline() {
|
||||
if (delayedDisconnectTask == null) {
|
||||
delayedDisconnectTask = scheduler.schedule(this::setOffline, BUTTON_OFFLINE_GRACE_PERIOD_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setOnline() {
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||
}
|
||||
|
||||
protected void setOffline() {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE,
|
||||
"Disconnect Reason: " + Objects.toString(latestDisconnectReason));
|
||||
}
|
||||
|
||||
// Cleanup delayedDisconnect on status change to online
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
|
||||
if (status == ThingStatus.ONLINE) {
|
||||
cancelDelayedDisconnectTask();
|
||||
}
|
||||
super.updateStatus(status, statusDetail, description);
|
||||
}
|
||||
|
||||
private void cancelInitializationTask() {
|
||||
if (initializationTask != null) {
|
||||
initializationTask.cancel(true);
|
||||
initializationTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelDelayedDisconnectTask() {
|
||||
if (delayedDisconnectTask != null) {
|
||||
delayedDisconnectTask.cancel(false);
|
||||
delayedDisconnectTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
void updateBatteryChannel(int percent) {
|
||||
DecimalType batteryLevel = new DecimalType(percent);
|
||||
updateState(CHANNEL_ID_BATTERY_LEVEL, batteryLevel);
|
||||
}
|
||||
|
||||
void flicButtonRemoved() {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.GONE,
|
||||
"Button was removed/detached from flicd (e.g. by simpleclient).");
|
||||
}
|
||||
|
||||
void fireTriggerEvent(String event) {
|
||||
String channelID = event.equals(CommonTriggerEvents.PRESSED) || event.equals(CommonTriggerEvents.RELEASED)
|
||||
? CHANNEL_ID_RAWBUTTON_EVENTS
|
||||
: CHANNEL_ID_BUTTON_EVENTS;
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
triggerChannel(channelID, event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The configuration of a flicd bridge handled by {@link FlicDaemonBridgeHandler}.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicDaemonBridgeConfiguration {
|
||||
|
||||
@Nullable
|
||||
private String hostname;
|
||||
private int port;
|
||||
|
||||
public @Nullable InetAddress getHost() throws UnknownHostException {
|
||||
return InetAddress.getByName(hostname);
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.flicbutton.internal.discovery.FlicButtonDiscoveryService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.flic.fliclib.javaclient.FlicClient;
|
||||
|
||||
/**
|
||||
* The {@link FlicDaemonBridgeHandler} handles a running instance of the fliclib-linux-hci server (flicd).
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicDaemonBridgeHandler extends BaseBridgeHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(FlicDaemonBridgeHandler.class);
|
||||
private static final long REINITIALIZE_DELAY_SECONDS = 10;
|
||||
// Config parameters
|
||||
private @Nullable FlicDaemonBridgeConfiguration cfg;
|
||||
// Services
|
||||
private FlicButtonDiscoveryService buttonDiscoveryService;
|
||||
private @Nullable Future<?> flicClientFuture;
|
||||
// For disposal
|
||||
private Collection<@Nullable Future<?>> startedTasks = new ArrayList<>(3);
|
||||
private @Nullable FlicClient flicClient;
|
||||
|
||||
public FlicDaemonBridgeHandler(Bridge bridge, FlicButtonDiscoveryService buttonDiscoveryService) {
|
||||
super(bridge);
|
||||
this.buttonDiscoveryService = buttonDiscoveryService;
|
||||
}
|
||||
|
||||
public @Nullable FlicClient getFlicClient() {
|
||||
return flicClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
startedTasks.add(scheduler.submit(this::initializeThing));
|
||||
}
|
||||
|
||||
public void initializeThing() {
|
||||
try {
|
||||
initConfigParameters();
|
||||
startFlicdClientAsync();
|
||||
activateButtonDiscoveryService();
|
||||
initThingStatus();
|
||||
} catch (UnknownHostException ignored) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hostname wrong or unknown!");
|
||||
} catch (IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Error connecting to flicd: " + e.getMessage());
|
||||
dispose();
|
||||
scheduleReinitialize();
|
||||
}
|
||||
}
|
||||
|
||||
private void initConfigParameters() {
|
||||
cfg = getConfigAs(FlicDaemonBridgeConfiguration.class);
|
||||
}
|
||||
|
||||
private void activateButtonDiscoveryService() {
|
||||
if (flicClient != null) {
|
||||
buttonDiscoveryService.activate((@NonNull FlicClient) flicClient);
|
||||
} else {
|
||||
throw new IllegalStateException("flicClient not properly initialized");
|
||||
}
|
||||
}
|
||||
|
||||
private void startFlicdClientAsync() throws IOException {
|
||||
flicClient = new FlicClient(cfg.getHost().getHostAddress(), cfg.getPort());
|
||||
Runnable flicClientService = () -> {
|
||||
try {
|
||||
flicClient.handleEvents();
|
||||
flicClient.close();
|
||||
logger.debug("Listening to flicd ended");
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error occured while listening to flicd", e);
|
||||
} finally {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
onClientFailure();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
flicClientFuture = scheduler.submit(flicClientService);
|
||||
startedTasks.add(flicClientFuture);
|
||||
}
|
||||
}
|
||||
|
||||
private void onClientFailure() {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"flicd client terminated, probably flicd is not reachable anymore.");
|
||||
dispose();
|
||||
scheduleReinitialize();
|
||||
}
|
||||
|
||||
private void initThingStatus() {
|
||||
if (!flicClientFuture.isDone()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"flicd client could not be started, probably flicd is not reachable.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
startedTasks.forEach(task -> task.cancel(true));
|
||||
startedTasks = new ArrayList<>(2);
|
||||
buttonDiscoveryService.deactivate();
|
||||
}
|
||||
|
||||
private void scheduleReinitialize() {
|
||||
startedTasks.add(scheduler.schedule(this::initialize, REINITIALIZE_DELAY_SECONDS, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// No commands to the fliclib-linux-hci are supported.
|
||||
// So there is nothing to handle in the bridge handler
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.flicbutton.internal.util;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
import io.flic.fliclib.javaclient.Bdaddr;
|
||||
|
||||
/**
|
||||
* The {@link FlicButtonUtils} class defines static utility methods that are used within the binding.
|
||||
*
|
||||
* @author Patrick Fink - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FlicButtonUtils {
|
||||
public static ThingUID getThingUIDFromBdAddr(Bdaddr bdaddr, ThingUID bridgeUID) {
|
||||
String thingID = bdaddr.toString().replace(":", "-");
|
||||
return new ThingUID(FlicButtonBindingConstants.FLICBUTTON_THING_TYPE, bridgeUID, thingID);
|
||||
}
|
||||
|
||||
public static Bdaddr getBdAddrFromThingUID(ThingUID thingUID) {
|
||||
String bdaddrRaw = thingUID.getId().replace("-", ":");
|
||||
return new Bdaddr(bdaddrRaw);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="flicbutton" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>FlicButton Binding</name>
|
||||
<description>This is the binding for Flic buttons by Shortcut Labs.</description>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="flicbutton"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="flicd-bridge">
|
||||
<label>FlicButton Bridge</label>
|
||||
<description>This bridge represents a running instance of the fliclib-linux-hci server (flicd).</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text" required="false">
|
||||
<context>network-address</context>
|
||||
<label>Flic Daemon (flicd) Hostname</label>
|
||||
<description>IP or Host name of the Flic daemon (flicd).</description>
|
||||
<default>localhost</default>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="false">
|
||||
<label>Flic Daemon (flicd) Port</label>
|
||||
<description>Port where flicd is running. Defaults to 5551.</description>
|
||||
<default>5551</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="button">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="flicd-bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Flic Button</label>
|
||||
<description>The thing(-type) representing a Flic Button</description>
|
||||
<channels>
|
||||
<channel id="rawbutton" typeId="system.rawbutton"/>
|
||||
<channel id="button" typeId="system.button"/>
|
||||
<channel id="battery-level" typeId="system.battery-level"/>
|
||||
</channels>
|
||||
<config-description>
|
||||
<parameter name="address" type="text" required="true">
|
||||
<label>Address</label>
|
||||
<description>Bluetooth address in XX:XX:XX:XX:XX:XX format</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user