Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pilight] Pilight Binding initial contribution + add discovery #9744

Merged
merged 5 commits into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@
*/
package org.openhab.binding.pilight.internal;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.*;
import java.net.Socket;
import java.util.Collections;
import java.util.LinkedList;
import java.util.concurrent.*;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand All @@ -44,7 +40,7 @@
*
*/
@NonNullByDefault
public class PilightConnector implements Runnable {
public class PilightConnector implements Runnable, Closeable {

private static final int RECONNECT_DELAY_MSEC = 10 * 1000; // 10 seconds

Expand All @@ -65,7 +61,7 @@ public class PilightConnector implements Runnable {
private @Nullable PrintStream printStream;

private final ScheduledExecutorService scheduler;
private final LinkedList<Action> delayedActionQueue = new LinkedList<>();
private final ConcurrentLinkedQueue<Action> delayedActionQueue = new ConcurrentLinkedQueue<>();
private @Nullable ScheduledFuture<?> delayedActionWorkerFuture;

public PilightConnector(final PilightBridgeConfiguration config, final IPilightCallback callback,
Expand Down Expand Up @@ -227,13 +223,13 @@ private void connect() throws InterruptedException {
*
* @param action action to send
*/
public synchronized void sendAction(Action action) {
public void sendAction(Action action) {
delayedActionQueue.add(action);

if (delayedActionWorkerFuture == null || delayedActionWorkerFuture.isCancelled()) {
delayedActionWorkerFuture = scheduler.scheduleWithFixedDelay(() -> {
if (!delayedActionQueue.isEmpty()) {
doSendAction(delayedActionQueue.pop());
doSendAction(delayedActionQueue.poll());
} else {
delayedActionWorkerFuture.cancel(false);
delayedActionWorkerFuture = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand Down Expand Up @@ -78,52 +77,40 @@ protected void startScan() {
if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
DatagramSocket ssdp = new DatagramSocket(
new InetSocketAddress(inetAddress.getHostAddress(), 0));
byte[] buff = SSDP_DISCOVERY_REQUEST_MESSAGE.getBytes();
byte[] buff = SSDP_DISCOVERY_REQUEST_MESSAGE.getBytes(StandardCharsets.UTF_8);
DatagramPacket sendPack = new DatagramPacket(buff, buff.length);
sendPack.setAddress(InetAddress.getByName(SSDP_MULTICAST_ADDRESS));
sendPack.setPort(SSDP_PORT);
ssdp.send(sendPack);
ssdp.setSoTimeout(SSDP_WAIT_TIMEOUT);

boolean loop = true;
while (loop) {
final AtomicBoolean loop = new AtomicBoolean(true);
while (loop.get()) {
DatagramPacket recvPack = new DatagramPacket(new byte[1024], 1024);
ssdp.receive(recvPack);
byte[] recvData = recvPack.getData();
try (InputStreamReader recvInput = new InputStreamReader(new ByteArrayInputStream(recvData),
StandardCharsets.UTF_8)) {
StringBuilder recvOutput = new StringBuilder();
for (int value; (value = recvInput.read()) != -1;) {
recvOutput.append((char) value);
}
try (BufferedReader bufReader = new BufferedReader(
new StringReader(recvOutput.toString()))) {
Pattern pattern = Pattern.compile("Location:([0-9.]+):(.*)");
String line;
while ((line = bufReader.readLine()) != null) {
Matcher matcher = pattern.matcher(line);
if (matcher.matches()) {
String server = matcher.group(1);
Integer port = Integer.parseInt(matcher.group(2));
loop = false;
logger.debug("Found pilight daemon at {}:{}", server, port);

Map<String, Object> properties = new HashMap<>(2);
properties.put(PilightBindingConstants.PROPERTY_IP_ADDRESS, server);
properties.put(PilightBindingConstants.PROPERTY_PORT, port);

ThingUID uid = new ThingUID(PilightBindingConstants.THING_TYPE_BRIDGE,
server.replace(".", "") + "" + port);

DiscoveryResult result = DiscoveryResultBuilder.create(uid)
.withProperties(properties)
.withLabel("Pilight Bridge (" + server + ")").build();

thingDiscovered(result);
}
}
}
}

final Scanner scanner = new Scanner(new ByteArrayInputStream(recvData),
StandardCharsets.UTF_8);
scanner.findAll("Location:([0-9.]+):(.*)").forEach(matchResult -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can avoid making loop an AtomicBoolean by using Stream.peek().count() instead of foreach. You can use the returned count value to set your loop value.

Suggested change
scanner.findAll("Location:([0-9.]+):(.*)").forEach(matchResult -> {
scanner.findAll("Location:([0-9.]+):(.*)").peek(matchResult -> {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, but is it good practise to use peek for this? According to the documentation, it is mainly intended for debugging.

This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline.

(see https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#peek-java.util.function.Consumer-)

final String server = matchResult.group(1);
final Integer port = Integer.parseInt(matchResult.group(2));

logger.debug("Found pilight daemon at {}:{}", server, port);

Map<String, Object> properties = new HashMap<>();
properties.put(PilightBindingConstants.PROPERTY_IP_ADDRESS, server);
properties.put(PilightBindingConstants.PROPERTY_PORT, port);

ThingUID uid = new ThingUID(PilightBindingConstants.THING_TYPE_BRIDGE,
server.replace(".", "") + "" + port);

DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
.withLabel("Pilight Bridge (" + server + ")").build();
niklasdoerfler marked this conversation as resolved.
Show resolved Hide resolved

thingDiscovered(result);
loop.set(false);
});
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -47,8 +46,7 @@
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.pilight")
niklasdoerfler marked this conversation as resolved.
Show resolved Hide resolved
public class PilightDeviceDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
public class PilightDeviceDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = PilightHandlerFactory.SUPPORTED_THING_TYPES_UIDS;

Expand Down Expand Up @@ -76,64 +74,52 @@ protected void startScan() {
configFuture = new CompletableFuture<>();
statusFuture = new CompletableFuture<>();

CompletableFuture.allOf(configFuture, statusFuture).thenAccept(unused -> {
Config config = null;
List<Status> allStatus = null;

try {
config = configFuture.get();
allStatus = statusFuture.get();
} catch (InterruptedException | ExecutionException ignored) {
}

if (config != null && allStatus != null) {
removeOlderResults(getTimestampOfLastScan(), bridgeUID);
List<Status> finalAllStatus = allStatus;
config.getDevices().forEach((deviceId, device) -> {
if (this.pilightBridgeHandler != null) {
final Optional<Status> status = finalAllStatus.stream()
.filter(s -> deviceId.equals(s.getDevices().get(0))).findFirst();

final ThingTypeUID thingTypeUID;
final String typeString;

if (status.isPresent()) {
if (status.get().getType().equals(DeviceType.SWITCH)) {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_SWITCH.getId());
typeString = "Switch";
} else if (status.get().getType().equals(DeviceType.DIMMER)) {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_DIMMER.getId());
typeString = "Dimmer";
} else if (status.get().getType().equals(DeviceType.VALUE)) {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId());
typeString = "Generic";
} else if (status.get().getType().equals(DeviceType.CONTACT)) {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_CONTACT.getId());
typeString = "Contact";
} else {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId());
typeString = "Generic";
}
configFuture.thenAcceptBoth(statusFuture, (config, allStatus) -> {
removeOlderResults(getTimestampOfLastScan(), bridgeUID);
config.getDevices().forEach((deviceId, device) -> {
if (this.pilightBridgeHandler != null) {
final Optional<Status> status = allStatus.stream()
.filter(s -> deviceId.equals(s.getDevices().get(0))).findFirst();
niklasdoerfler marked this conversation as resolved.
Show resolved Hide resolved

final ThingTypeUID thingTypeUID;
final String typeString;

if (status.isPresent()) {
if (status.get().getType().equals(DeviceType.SWITCH)) {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_SWITCH.getId());
typeString = "Switch";
} else if (status.get().getType().equals(DeviceType.DIMMER)) {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_DIMMER.getId());
typeString = "Dimmer";
} else if (status.get().getType().equals(DeviceType.VALUE)) {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId());
typeString = "Generic";
} else if (status.get().getType().equals(DeviceType.CONTACT)) {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_CONTACT.getId());
typeString = "Contact";
} else {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId());
typeString = "Generic";
}
} else {
thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId());
typeString = "Generic";
}

final @Nullable ThingUID thingUID = new ThingUID(thingTypeUID,
this.pilightBridgeHandler.getThing().getUID(), deviceId);
final ThingUID thingUID = new ThingUID(thingTypeUID,
this.pilightBridgeHandler.getThing().getUID(), deviceId);

Map<String, Object> properties = new HashMap<>();
properties.put(PROPERTY_NAME, deviceId);
final Map<String, Object> properties = new HashMap<>();
properties.put(PROPERTY_NAME, deviceId);

DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
.withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID)
.withRepresentationProperty(deviceId)
.withLabel("Pilight " + typeString + " Device '" + deviceId + "'").build();
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
.withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID)
.withRepresentationProperty(deviceId)
niklasdoerfler marked this conversation as resolved.
Show resolved Hide resolved
.withLabel("Pilight " + typeString + " Device '" + deviceId + "'").build();

thingDiscovered(discoveryResult);
}
});
}
thingDiscovered(discoveryResult);
}
});
});

pilightBridgeHandler.refreshConfigAndStatus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
*/
@NonNullByDefault
public class PilightBaseHandler extends BaseThingHandler {
public abstract class PilightBaseHandler extends BaseThingHandler {

private final Logger logger = LoggerFactory.getLogger(PilightBaseHandler.class);

Expand Down Expand Up @@ -68,14 +68,13 @@ public void initialize() {
}

public void updateFromStatusIfMatches(Status status) {
@Nullable
String device = status.getDevices().get(0);

if (name.equals(device)) {
if (!ThingStatus.ONLINE.equals(getThing().getStatus())) {
updateStatus(ThingStatus.ONLINE);
if (status.getDevices() != null && !status.getDevices().isEmpty()) {
if (name.equals(status.getDevices().get(0))) {
if (!ThingStatus.ONLINE.equals(getThing().getStatus())) {
updateStatus(ThingStatus.ONLINE);
}
updateFromStatus(status);
}
updateFromStatus(status);
}
}

Expand All @@ -86,18 +85,11 @@ public void updateFromConfigIfMatches(Config config) {
}
}

protected void updateFromStatus(Status status) {
// handled in derived class
}
abstract void updateFromStatus(Status status);

protected void updateFromConfigDevice(Device device) {
// may be handled in derived class
}
abstract void updateFromConfigDevice(Device device);

protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) {
// handled in the derived class
return null;
}
abstract @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command);

protected String getName() {
return name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public class PilightBridgeHandler extends BaseBridgeHandler {

private @Nullable PilightDeviceDiscoveryService discoveryService = null;

private final ExecutorService connectorExecutor = Executors
.newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true));

public PilightBridgeHandler(Bridge bridge) {
super(bridge);
}
Expand Down Expand Up @@ -104,10 +107,7 @@ public void versionReceived(Version version) {

updateStatus(ThingStatus.UNKNOWN);

final ExecutorService executor = Executors
.newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true));
executor.execute(connector);

connectorExecutor.execute(connector);
this.connector = connector;
}

Expand All @@ -123,6 +123,8 @@ public void dispose() {
connector.close();
this.connector = null;
}

connectorExecutor.shutdown();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pilight.internal.dto.Action;
import org.openhab.binding.pilight.internal.dto.Device;
import org.openhab.binding.pilight.internal.dto.Status;
import org.openhab.binding.pilight.internal.types.PilightContactType;
import org.openhab.core.thing.ChannelUID;
Expand Down Expand Up @@ -48,6 +49,10 @@ protected void updateFromStatus(Status status) {
}
}

@Override
void updateFromConfigDevice(Device device) {
}

@Override
protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) {
logger.warn("A contact is a read only device");
Expand Down
Loading