mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[nikobus] discovery for push buttons, (#9134)
* removed state update if value didn't change so expiry binding can actually - expire. If expire time was larger than refresh interval of the binding, expiration never happened ... Signed-off-by: Boris Krivonog <boris.krivonog@inova.si>
This commit is contained in:
parent
d646215288
commit
a138748ff3
@ -33,10 +33,6 @@ The bridge enables communication with other Nikobus components:
|
||||
* `rollershutter-module` - Nikobus roller shutter module,
|
||||
* `push-button` - Nikobus physical push button.
|
||||
|
||||
## Discovery
|
||||
|
||||
The binding does not support any automatic discovery of Things.
|
||||
|
||||
## Bridge Configuration
|
||||
|
||||
The binding can connect to the PC-Link via serial interface.
|
||||
@ -174,6 +170,84 @@ Thing push-button pb1 [ address = "28092A", impactedModules = "switch-module:s1:
|
||||
|
||||
In addition to the status requests triggered by button presses, there is also a scheduled status update interval defined by the `refreshInterval` parameter and explained above.
|
||||
|
||||
## Discovery
|
||||
|
||||
Pressing a physical Nikobus push-button will generate a new inbox entry with an exception of buttons already discovered or setup.
|
||||
|
||||
Nikobus push buttons have the following format in inbox:
|
||||
|
||||
```
|
||||
Nikobus Push Button 14E7F4:3
|
||||
4BF9CA
|
||||
nikobus:push-button
|
||||
```
|
||||
|
||||
where first line contains name of the discovered button and second one contains button's bus address.
|
||||
|
||||
Each discovered button has a Nikobus address appended to its name, same as can be seen in Nikobus's PC application, `14E7F4:3` in above example.
|
||||
|
||||
* `14E7F4` - address of the Nikobus switch, as can be seen in Nikobus PC software and
|
||||
* `3` - represents a button on Nikobus switch.
|
||||
|
||||
### Button mappings
|
||||
|
||||
##### 2 buttons switch
|
||||
|
||||
![Nikobus Switch with 2 buttons](doc/s2.png)
|
||||
|
||||
```
|
||||
1 = A
|
||||
2 = B
|
||||
```
|
||||
|
||||
##### 4 buttons switch
|
||||
|
||||
![Nikobus Switch with 4 buttons](doc/s4.png)
|
||||
|
||||
maps as
|
||||
|
||||
```
|
||||
3 1
|
||||
4 2
|
||||
```
|
||||
|
||||
so
|
||||
|
||||
```
|
||||
1 = C
|
||||
2 = D
|
||||
3 = A
|
||||
4 = B
|
||||
```
|
||||
|
||||
##### 8 buttons switch
|
||||
|
||||
![Nikobus Switch with 8 buttons](doc/s8.png)
|
||||
|
||||
maps as
|
||||
|
||||
```
|
||||
7 5 3 1
|
||||
8 6 4 2
|
||||
```
|
||||
|
||||
so
|
||||
|
||||
```
|
||||
1 = 2C
|
||||
2 = 2D
|
||||
3 = 2A
|
||||
4 = 2B
|
||||
5 = 1C
|
||||
6 = 1D
|
||||
7 = 1A
|
||||
8 = 1B
|
||||
```
|
||||
|
||||
Above example `14E7F4:3` would give:
|
||||
* for 4 buttons switch - push button A,
|
||||
* for 8 buttons switch - push button 2A.
|
||||
|
||||
## Full Example
|
||||
|
||||
### nikobus.things
|
||||
|
BIN
bundles/org.openhab.binding.nikobus/doc/s2.png
Normal file
BIN
bundles/org.openhab.binding.nikobus/doc/s2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
bundles/org.openhab.binding.nikobus/doc/s4.png
Normal file
BIN
bundles/org.openhab.binding.nikobus/doc/s4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
bundles/org.openhab.binding.nikobus/doc/s8.png
Normal file
BIN
bundles/org.openhab.binding.nikobus/doc/s8.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.nikobus.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nikobus.internal.handler.NikobusPcLinkHandler;
|
||||
import org.openhab.binding.nikobus.internal.utils.Utils;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NikobusDiscoveryService} discovers push button things for Nikobus switches.
|
||||
* Buttons are not discovered via scan but only when physical button is pressed and a new
|
||||
* nikobus push button bus address is detected.
|
||||
*
|
||||
* @author Boris Krivonog - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NikobusDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NikobusDiscoveryService.class);
|
||||
private @Nullable NikobusPcLinkHandler bridgeHandler;
|
||||
|
||||
public NikobusDiscoveryService() throws IllegalArgumentException {
|
||||
super(Collections.singleton(THING_TYPE_PUSH_BUTTON), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
NikobusPcLinkHandler handler = bridgeHandler;
|
||||
if (handler != null) {
|
||||
handler.resetUnhandledCommandProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
NikobusPcLinkHandler handler = bridgeHandler;
|
||||
if (handler != null) {
|
||||
handler.setUnhandledCommandProcessor(this::process);
|
||||
}
|
||||
}
|
||||
|
||||
private void process(String command) {
|
||||
if (command.length() <= 2 || !command.startsWith("#N")) {
|
||||
logger.debug("Ignoring command() '{}'", command);
|
||||
}
|
||||
|
||||
String address = command.substring(2);
|
||||
logger.debug("Received address = '{}'", address);
|
||||
|
||||
NikobusPcLinkHandler handler = bridgeHandler;
|
||||
if (handler != null) {
|
||||
ThingUID thingUID = new ThingUID(THING_TYPE_PUSH_BUTTON, handler.getThing().getUID(), address);
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(CONFIG_ADDRESS, address);
|
||||
|
||||
String humanReadableNikobusAddress = Utils.convertToHumanReadableNikobusAddress(address).toUpperCase();
|
||||
logger.debug("Detected Nikobus Push Button: '{}'", humanReadableNikobusAddress);
|
||||
thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_PUSH_BUTTON)
|
||||
.withLabel("Nikobus Push Button " + humanReadableNikobusAddress).withProperties(properties)
|
||||
.withRepresentationProperty(CONFIG_ADDRESS).withBridge(handler.getThing().getUID()).build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
if (handler instanceof NikobusPcLinkHandler) {
|
||||
bridgeHandler = (NikobusPcLinkHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
super.activate(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
}
|
@ -176,14 +176,16 @@ abstract class NikobusModuleHandler extends NikobusBaseThingHandler {
|
||||
|
||||
logger.debug("setting channel '{}' to {}", channelId, value);
|
||||
|
||||
Integer previousValue;
|
||||
synchronized (cachedStates) {
|
||||
cachedStates.put(channelId, value);
|
||||
previousValue = cachedStates.put(channelId, value);
|
||||
}
|
||||
|
||||
updateState(channelId, stateFromValue(value));
|
||||
if (previousValue == null || previousValue.intValue() != value) {
|
||||
updateState(channelId, stateFromValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unused", "null" })
|
||||
private void processWrite(ChannelUID channelUID, Command command) {
|
||||
StringBuilder commandPayload = new StringBuilder();
|
||||
SwitchModuleGroup group = SwitchModuleGroup.mapFromChannel(channelUID);
|
||||
|
@ -16,6 +16,7 @@ import static org.openhab.binding.nikobus.internal.NikobusBindingConstants.CONFI
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
@ -24,12 +25,14 @@ import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
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.nikobus.internal.NikobusBindingConstants;
|
||||
import org.openhab.binding.nikobus.internal.discovery.NikobusDiscoveryService;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusCommand;
|
||||
import org.openhab.binding.nikobus.internal.protocol.NikobusConnection;
|
||||
import org.openhab.binding.nikobus.internal.utils.Utils;
|
||||
@ -41,6 +44,7 @@ import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -63,6 +67,7 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
|
||||
private @Nullable ScheduledFuture<?> scheduledRefreshFuture;
|
||||
private @Nullable ScheduledFuture<?> scheduledSendCommandWatchdogFuture;
|
||||
private @Nullable String ack;
|
||||
private @Nullable Consumer<String> unhandledCommandsProcessor;
|
||||
private int refreshThingIndex = 0;
|
||||
|
||||
public NikobusPcLinkHandler(Bridge bridge, SerialPortManager serialPortManager) {
|
||||
@ -113,7 +118,11 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
|
||||
// Noop.
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(NikobusDiscoveryService.class);
|
||||
}
|
||||
|
||||
private void processReceivedValue(byte value) {
|
||||
logger.trace("Received {}", value);
|
||||
|
||||
@ -133,6 +142,11 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
|
||||
Runnable listener = commandListeners.get(command);
|
||||
if (listener != null) {
|
||||
listener.run();
|
||||
} else {
|
||||
Consumer<String> processor = unhandledCommandsProcessor;
|
||||
if (processor != null) {
|
||||
processor.accept(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
@ -157,7 +171,6 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public void addListener(String command, Runnable listener) {
|
||||
if (commandListeners.put(command, listener) != null) {
|
||||
logger.warn("Multiple registrations for '{}'", command);
|
||||
@ -168,6 +181,17 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
|
||||
commandListeners.remove(command);
|
||||
}
|
||||
|
||||
public void setUnhandledCommandProcessor(Consumer<String> processor) {
|
||||
if (unhandledCommandsProcessor != null) {
|
||||
logger.debug("Unexpected override of unhandledCommandsProcessor");
|
||||
}
|
||||
unhandledCommandsProcessor = processor;
|
||||
}
|
||||
|
||||
public void resetUnhandledCommandProcessor() {
|
||||
unhandledCommandsProcessor = null;
|
||||
}
|
||||
|
||||
private void processResponse(String commandPayload, @Nullable String ack) {
|
||||
NikobusCommand command;
|
||||
synchronized (pendingCommands) {
|
||||
@ -229,7 +253,6 @@ public class NikobusPcLinkHandler extends BaseBridgeHandler {
|
||||
scheduler.submit(this::processCommand);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unused", "null" })
|
||||
private void processCommand() {
|
||||
NikobusCommand command;
|
||||
synchronized (pendingCommands) {
|
||||
|
@ -56,7 +56,7 @@ public class CRCUtil {
|
||||
}
|
||||
|
||||
check = check & CRC_INIT;
|
||||
String checksum = leftPadWithZeros(Integer.toHexString(check), 4);
|
||||
String checksum = Utils.leftPadWithZeros(Integer.toHexString(check), 4);
|
||||
return (input + checksum).toUpperCase();
|
||||
}
|
||||
|
||||
@ -87,14 +87,6 @@ public class CRCUtil {
|
||||
}
|
||||
}
|
||||
|
||||
return input + leftPadWithZeros(Integer.toHexString(check), 2).toUpperCase();
|
||||
}
|
||||
|
||||
private static String leftPadWithZeros(String text, int size) {
|
||||
StringBuilder builder = new StringBuilder(text);
|
||||
while (builder.length() < size) {
|
||||
builder.insert(0, '0');
|
||||
}
|
||||
return builder.toString();
|
||||
return input + Utils.leftPadWithZeros(Integer.toHexString(check), 2).toUpperCase();
|
||||
}
|
||||
}
|
||||
|
@ -29,4 +29,62 @@ public class Utils {
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bus address to push button's address as seen in Nikobus
|
||||
* PC software.
|
||||
*
|
||||
* @param addressString
|
||||
* String representing a bus Push Button's address.
|
||||
* @return Push button's address as seen in Nikobus PC software.
|
||||
*/
|
||||
public static String convertToHumanReadableNikobusAddress(String addressString) {
|
||||
try {
|
||||
int address = Integer.parseInt(addressString, 16);
|
||||
int nikobusAddress = 0;
|
||||
|
||||
for (int i = 0; i < 21; ++i) {
|
||||
nikobusAddress = (nikobusAddress << 1) | ((address >> i) & 1);
|
||||
}
|
||||
|
||||
nikobusAddress = (nikobusAddress << 1);
|
||||
int button = (address >> 21) & 0x07;
|
||||
|
||||
return leftPadWithZeros(Integer.toHexString(nikobusAddress), 6) + ":" + mapButton(button);
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
return "[" + addressString + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private static String mapButton(int buttonIndex) {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
return "1";
|
||||
case 1:
|
||||
return "5";
|
||||
case 2:
|
||||
return "2";
|
||||
case 3:
|
||||
return "6";
|
||||
case 4:
|
||||
return "3";
|
||||
case 5:
|
||||
return "7";
|
||||
case 6:
|
||||
return "4";
|
||||
case 7:
|
||||
return "8";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
public static String leftPadWithZeros(String text, int size) {
|
||||
StringBuilder builder = new StringBuilder(text);
|
||||
while (builder.length() < size) {
|
||||
builder.insert(0, '0');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user