added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.openwebnet-${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-openwebnet" description="OpenWebNet (BTicino/Legrand) Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-serial</feature>
<feature>openhab-transport-upnp</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.openwebnet/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,112 @@
/**
* 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.openwebnet;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link OpenWebNetBindingConstants} class defines common constants, which are used across the whole binding.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetBindingConstants {
public static final String BINDING_ID = "openwebnet";
public static final int THING_STATE_REQ_TIMEOUT_SEC = 5;
// #LIST OF Thing Type UIDs
// generic device (used for not identified devices)
public static final ThingTypeUID THING_TYPE_GENERIC_DEVICE = new ThingTypeUID(BINDING_ID, "generic_device");
public static final String THING_LABEL_GENERIC_DEVICE = "GENERIC Device";
// bridges
public static final ThingTypeUID THING_TYPE_ZB_GATEWAY = new ThingTypeUID(BINDING_ID, "zb_gateway");
public static final String THING_LABEL_ZB_GATEWAY = "ZigBee USB Gateway";
public static final ThingTypeUID THING_TYPE_BUS_GATEWAY = new ThingTypeUID(BINDING_ID, "bus_gateway");
public static final String THING_LABEL_BUS_GATEWAY = "BUS Gateway";
// other thing types
// BUS
public static final ThingTypeUID THING_TYPE_BUS_ON_OFF_SWITCH = new ThingTypeUID(BINDING_ID, "bus_on_off_switch");
public static final String THING_LABEL_BUS_ON_OFF_SWITCH = "Switch";
public static final ThingTypeUID THING_TYPE_BUS_DIMMER = new ThingTypeUID(BINDING_ID, "bus_dimmer");
public static final String THING_LABEL_BUS_DIMMER = "Dimmer";
public static final ThingTypeUID THING_TYPE_BUS_AUTOMATION = new ThingTypeUID(BINDING_ID, "bus_automation");
public static final String THING_LABEL_BUS_AUTOMATION = "Automation";
// ZIGBEE
public static final ThingTypeUID THING_TYPE_ZB_ON_OFF_SWITCH = new ThingTypeUID(BINDING_ID, "zb_on_off_switch");
public static final String THING_LABEL_ZB_ON_OFF_SWITCH = "ZigBee Switch";
public static final ThingTypeUID THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS = new ThingTypeUID(BINDING_ID,
"zb_on_off_switch2u");
public static final String THING_LABEL_ZB_ON_OFF_SWITCH_2UNITS = "ZigBee 2-units Switch";
public static final ThingTypeUID THING_TYPE_ZB_DIMMER = new ThingTypeUID(BINDING_ID, "zb_dimmer");
public static final String THING_LABEL_ZB_DIMMER = "ZigBee Dimmer";
public static final ThingTypeUID THING_TYPE_ZB_AUTOMATION = new ThingTypeUID(BINDING_ID, "zb_automation");
public static final String THING_LABEL_ZB_AUTOMATION = "ZigBee Automation";
// #SUPPORTED THINGS SETS
// ## Generic
public static final Set<ThingTypeUID> GENERIC_SUPPORTED_THING_TYPES = new HashSet<>(
Arrays.asList(THING_TYPE_GENERIC_DEVICE));
// ## Lighting
public static final Set<ThingTypeUID> LIGHTING_SUPPORTED_THING_TYPES = new HashSet<>(
Arrays.asList(THING_TYPE_ZB_ON_OFF_SWITCH, THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS, THING_TYPE_ZB_DIMMER,
THING_TYPE_BUS_ON_OFF_SWITCH, THING_TYPE_BUS_DIMMER));
// ## Automation
public static final Set<ThingTypeUID> AUTOMATION_SUPPORTED_THING_TYPES = new HashSet<>(
Arrays.asList(THING_TYPE_ZB_AUTOMATION, THING_TYPE_BUS_AUTOMATION));
// ## Groups
public static final Set<ThingTypeUID> DEVICE_SUPPORTED_THING_TYPES = Stream
.of(LIGHTING_SUPPORTED_THING_TYPES, AUTOMATION_SUPPORTED_THING_TYPES, GENERIC_SUPPORTED_THING_TYPES)
.flatMap(Collection::stream).collect(Collectors.toCollection(HashSet::new));
public static final Set<ThingTypeUID> BRIDGE_SUPPORTED_THING_TYPES = new HashSet<>(
Arrays.asList(THING_TYPE_ZB_GATEWAY, THING_TYPE_BUS_GATEWAY));
public static final Set<ThingTypeUID> ALL_SUPPORTED_THING_TYPES = Stream
.of(DEVICE_SUPPORTED_THING_TYPES, BRIDGE_SUPPORTED_THING_TYPES).flatMap(Collection::stream)
.collect(Collectors.toCollection(HashSet::new));
// LIST OF ALL CHANNEL IDs
// lighting
public static final String CHANNEL_SWITCH = "switch";
public static final String CHANNEL_SWITCH_01 = "switch_01";
public static final String CHANNEL_SWITCH_02 = "switch_02";
public static final String CHANNEL_BRIGHTNESS = "brightness";
// automation
public static final String CHANNEL_SHUTTER = "shutter";
// devices config properties
public static final String CONFIG_PROPERTY_WHERE = "where";
public static final String CONFIG_PROPERTY_SHUTTER_RUN = "shutterRun";
// BUS gw config properties
public static final String CONFIG_PROPERTY_HOST = "host";
// properties
public static final String PROPERTY_OWNID = "ownId";
public static final String PROPERTY_ZIGBEEID = "zigbeeid";
public static final String PROPERTY_FIRMWARE_VERSION = "firmwareVersion";
public static final String PROPERTY_MODEL = "model";
public static final String PROPERTY_SERIAL_NO = "serialNumber";
}

View File

@@ -0,0 +1,426 @@
/**
* 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.openwebnet.handler;
import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_SHUTTER;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
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.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.UpDownType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.UnDefType;
import org.openwebnet4j.OpenGateway;
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.message.Automation;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.GatewayMgmt;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetAutomationHandler} is responsible for handling commands/messages for an Automation OpenWebNet
* device. It extends the abstract {@link OpenWebNetThingHandler}.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetAutomationHandler extends OpenWebNetThingHandler {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetAutomationHandler.class);
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("ss.SSS");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.AUTOMATION_SUPPORTED_THING_TYPES;
// moving states
public static final int MOVING_STATE_STOPPED = 0;
public static final int MOVING_STATE_MOVING_UP = 1;
public static final int MOVING_STATE_MOVING_DOWN = 2;
public static final int MOVING_STATE_UNKNOWN = -1;
// calibration states
public static final int CALIBRATION_INACTIVE = -1;
public static final int CALIBRATION_ACTIVATED = 0;
public static final int CALIBRATION_GOING_UP = 1;
public static final int CALIBRATION_GOING_DOWN = 2;
// positions
public static final int POSITION_MAX_STEPS = 100;
public static final int POSITION_DOWN = 100;
public static final int POSITION_UP = 0;
public static final int POSITION_UNKNOWN = -1;
public static final int SHUTTER_RUN_UNDEFINED = -1;
private int shutterRun = SHUTTER_RUN_UNDEFINED;
private static final String AUTO_CALIBRATION = "AUTO";
private long startedMovingAt = -1;
private int movingState = MOVING_STATE_UNKNOWN;
private int positionEstimation = POSITION_UNKNOWN;
private @Nullable ScheduledFuture<?> moveSchedule;
private int positionRequested = POSITION_UNKNOWN;
private int calibrating = CALIBRATION_INACTIVE;
private static final int MIN_STEP_TIME_MSEC = 50;
private @Nullable Command commandRequestedWhileMoving = null;
public OpenWebNetAutomationHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
super.initialize();
Object shutterRunConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN);
try {
if (shutterRunConfig == null) {
shutterRunConfig = AUTO_CALIBRATION;
logger.debug("shutterRun null --> default to AUTO");
} else if (shutterRunConfig instanceof String) {
if (AUTO_CALIBRATION.equalsIgnoreCase(((String) shutterRunConfig))) {
logger.debug("shutterRun set to AUTO via configuration");
shutterRun = SHUTTER_RUN_UNDEFINED; // reset shutterRun
} else { // try to parse int>=1000
int shutterRunInt = Integer.parseInt((String) shutterRunConfig);
if (shutterRunInt < 1000) {
throw new NumberFormatException();
}
shutterRun = shutterRunInt;
logger.debug("shutterRun set to {} via configuration", shutterRun);
}
} else {
throw new NumberFormatException();
}
} catch (NumberFormatException e) {
logger.debug("Wrong configuration: {} setting must be {} or an integer >= 1000",
OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN, AUTO_CALIBRATION);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"@text/offline.wrong-configuration");
shutterRun = SHUTTER_RUN_UNDEFINED;
}
updateState(CHANNEL_SHUTTER, UnDefType.UNDEF);
positionEstimation = POSITION_UNKNOWN;
}
@Override
protected void requestChannelState(ChannelUID channel) {
logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
Where w = deviceWhere;
if (w != null) {
try {
send(Automation.requestStatus(w.value()));
} catch (OWNException e) {
logger.debug("Exception while requesting channel {} state: {}", channel, e.getMessage(), e);
}
}
}
@Override
protected void handleChannelCommand(ChannelUID channel, Command command) {
switch (channel.getId()) {
case CHANNEL_SHUTTER:
handleShutterCommand(command);
break;
default: {
logger.info("Unsupported channel UID {}", channel);
}
}
}
/**
* Handles Automation Roller shutter command (UP/DOWN, STOP/MOVE, PERCENT xx%)
*/
private void handleShutterCommand(Command command) {
Where w = deviceWhere;
if (w != null) {
calibrating = CALIBRATION_INACTIVE; // cancel calibration if we receive a command
commandRequestedWhileMoving = null;
try {
if (StopMoveType.STOP.equals(command)) {
send(Automation.requestStop(w.value()));
} else if (command instanceof UpDownType || command instanceof PercentType) {
if (movingState == MOVING_STATE_MOVING_UP || movingState == MOVING_STATE_MOVING_DOWN) { // already
// moving
logger.debug("# {} # already moving, STOP then defer command", deviceWhere);
commandRequestedWhileMoving = command;
sendHighPriority(Automation.requestStop(w.value()));
return;
} else {
if (command instanceof UpDownType) {
if (UpDownType.UP.equals(command)) {
send(Automation.requestMoveUp(w.value()));
} else {
send(Automation.requestMoveDown(w.value()));
}
} else if (command instanceof PercentType) {
handlePercentCommand((PercentType) command, w.value());
}
}
} else {
logger.debug("Unsupported command {} for thing {}", command, thing.getUID());
}
} catch (OWNException e) {
logger.debug("Exception while sending request for command {}: {}", command, e.getMessage(), e);
}
}
}
/**
* Handles Automation PERCENT xx% command
*/
private void handlePercentCommand(PercentType command, String w) {
int percent = command.intValue();
if (percent == positionEstimation) {
logger.debug("# {} # handlePercentCommand() Command {}% == positionEstimation -> nothing to do", w,
percent);
return;
}
try {
if (percent == POSITION_DOWN) { // GO TO 100%
send(Automation.requestMoveDown(w));
} else if (percent == POSITION_UP) { // GO TO 0%
send(Automation.requestMoveUp(w));
} else { // GO TO XX%
logger.debug("# {} # {}% requested", deviceWhere, percent);
if (shutterRun == SHUTTER_RUN_UNDEFINED) {
logger.debug("& {} & CALIBRATION - shutterRun not configured, starting CALIBRATION...",
deviceWhere);
calibrating = CALIBRATION_ACTIVATED;
send(Automation.requestMoveUp(w));
positionRequested = percent;
} else if (shutterRun >= 1000 && positionEstimation != POSITION_UNKNOWN) {
// these two must be known to calculate moveTime.
// Calculate how much time we have to move and set a deadline to stop after that time
int moveTime = Math
.round(((float) Math.abs(percent - positionEstimation) / POSITION_MAX_STEPS * shutterRun));
logger.debug("# {} # target moveTime={}", deviceWhere, moveTime);
if (moveTime > MIN_STEP_TIME_MSEC) {
ScheduledFuture<?> mSch = moveSchedule;
if (mSch != null && !mSch.isDone()) {
// a moveSchedule was already scheduled and is not done... let's cancel the schedule
mSch.cancel(false);
logger.debug("# {} # new XX% requested, old moveSchedule cancelled", deviceWhere);
}
// send a requestFirmwareVersion message to BUS gateways to wake up the CMD connection before
// sending further cmds
OpenWebNetBridgeHandler h = bridgeHandler;
if (h != null && h.isBusGateway()) {
OpenGateway gw = h.gateway;
if (gw != null) {
if (!gw.isCmdConnectionReady()) {
logger.debug("# {} # waking-up CMD connection...", deviceWhere);
send(GatewayMgmt.requestFirmwareVersion());
}
}
}
// REMINDER: start the schedule BEFORE sending the command, because the synch command waits for
// ACK and can take some 300ms
logger.debug("# {} # Starting schedule...", deviceWhere);
moveSchedule = scheduler.schedule(() -> {
logger.debug("# {} # moveSchedule expired, sending STOP...", deviceWhere);
try {
sendHighPriority(Automation.requestStop(w));
} catch (OWNException ex) {
logger.debug("Exception while sending request for command {}: {}", command,
ex.getMessage(), ex);
}
}, moveTime, TimeUnit.MILLISECONDS);
logger.debug("# {} # ...schedule started, now sending highPriority command...", deviceWhere);
if (percent < positionEstimation) {
sendHighPriority(Automation.requestMoveUp(w));
} else {
sendHighPriority(Automation.requestMoveDown(w));
}
logger.debug("# {} # ...gateway.sendHighPriority() returned", deviceWhere);
} else {
logger.debug("# {} # moveTime <= MIN_STEP_TIME_MSEC ---> do nothing", deviceWhere);
}
} else {
logger.info(
"Command {} cannot be executed: unknown position or shutterRun configuration params not/wrongly set (thing={})",
command, thing.getUID());
}
}
} catch (OWNException e) {
logger.debug("Exception while sending request for command {}: {}", command, e.getMessage(), e);
}
}
@Override
protected String ownIdPrefix() {
return Who.AUTOMATION.value().toString();
}
@Override
protected void handleMessage(BaseOpenMessage msg) {
updateAutomationState((Automation) msg);
// REMINDER: update state, then update thing status in the super method, to avoid delays
super.handleMessage(msg);
}
/**
* Updates automation device state based on the Automation message received from OWN network
*
* @param msg the Automation message
*/
private void updateAutomationState(Automation msg) {
logger.debug("updateAutomationState() - msg={} what={}", msg, msg.getWhat());
try {
if (msg.isCommandTranslation()) {
logger.debug("msg is command translation, ignoring it");
return;
}
} catch (FrameException fe) {
logger.warn("Exception while checking WHERE command translation for frame {}: {}, ignoring it", msg,
fe.getMessage());
}
if (msg.isUp()) {
updateMovingState(MOVING_STATE_MOVING_UP);
if (calibrating == CALIBRATION_ACTIVATED) {
calibrating = CALIBRATION_GOING_UP;
logger.debug("& {} & CALIBRATION - started going ALL UP...", deviceWhere);
}
} else if (msg.isDown()) {
updateMovingState(MOVING_STATE_MOVING_DOWN);
if (calibrating == CALIBRATION_ACTIVATED) {
calibrating = CALIBRATION_GOING_DOWN;
logger.debug("& {} & CALIBRATION - started going ALL DOWN...", deviceWhere);
}
} else if (msg.isStop()) {
long stoppedAt = System.currentTimeMillis();
if (calibrating == CALIBRATION_GOING_DOWN && shutterRun == SHUTTER_RUN_UNDEFINED) {
shutterRun = (int) (stoppedAt - startedMovingAt);
logger.debug("& {} & CALIBRATION - reached DOWN ---> shutterRun={}", deviceWhere, shutterRun);
updateMovingState(MOVING_STATE_STOPPED);
logger.debug("& {} & CALIBRATION - COMPLETED, now going to {}%", deviceWhere, positionRequested);
handleShutterCommand(new PercentType(positionRequested));
Configuration configuration = editConfiguration();
configuration.put(OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN, Integer.toString(shutterRun));
updateConfiguration(configuration);
logger.debug("& {} & CALIBRATION - configuration updated: shutterRun = {}ms", deviceWhere, shutterRun);
} else if (calibrating == CALIBRATION_GOING_UP) {
updateMovingState(MOVING_STATE_STOPPED);
logger.debug("& {} & CALIBRATION - reached UP, now sending DOWN command...", deviceWhere);
calibrating = CALIBRATION_ACTIVATED;
if (deviceWhere != null) {
String w = deviceWhere.value();
try {
send(Automation.requestMoveDown(w));
} catch (OWNException e) {
logger.debug("Exception while sending DOWN command during calibration: {}", e.getMessage(), e);
calibrating = CALIBRATION_INACTIVE;
}
}
} else {
updateMovingState(MOVING_STATE_STOPPED);
// do deferred command, if present
Command cmd = commandRequestedWhileMoving;
if (cmd != null) {
handleShutterCommand(cmd);
}
}
} else {
logger.debug("Frame {} not supported for thing {}, ignoring it.", msg, thing.getUID());
}
}
/**
* Updates movingState to newState
*/
private void updateMovingState(int newState) {
if (movingState == MOVING_STATE_STOPPED) {
if (newState != MOVING_STATE_STOPPED) { // moving after stop
startedMovingAt = System.currentTimeMillis();
logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAt,
FORMATTER.format(new Date(startedMovingAt)));
}
} else { // we were moving
updatePosition();
if (newState != MOVING_STATE_STOPPED) { // moving after moving, take new timestamp
startedMovingAt = System.currentTimeMillis();
logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAt,
FORMATTER.format(new Date(startedMovingAt)));
}
// cancel the schedule
ScheduledFuture<?> mSc = moveSchedule;
if (mSc != null && !mSc.isDone()) {
mSc.cancel(false);
}
}
movingState = newState;
logger.debug("# {} # movingState={} positionEstimation={} - calibrating={} shutterRun={}", deviceWhere,
movingState, positionEstimation, calibrating, shutterRun);
}
/**
* Updates positionEstimation and then channel state based on movedTime and current movingState
*/
private void updatePosition() {
int newPos = POSITION_UNKNOWN;
if (shutterRun > 0) {// we have shutterRun defined, let's calculate new positionEstimation
long movedTime = System.currentTimeMillis() - startedMovingAt;
logger.debug("# {} # current positionEstimation={} movedTime={}", deviceWhere, positionEstimation,
movedTime);
int movedSteps = Math.round((float) movedTime / shutterRun * POSITION_MAX_STEPS);
logger.debug("# {} # movedSteps: {} {}", deviceWhere, movedSteps,
(movingState == MOVING_STATE_MOVING_DOWN) ? "DOWN(+)" : "UP(-)");
if (positionEstimation == POSITION_UNKNOWN && movedSteps >= POSITION_MAX_STEPS) { // we did a full run
newPos = (movingState == MOVING_STATE_MOVING_DOWN) ? POSITION_DOWN : POSITION_UP;
} else if (positionEstimation != POSITION_UNKNOWN) {
newPos = positionEstimation
+ ((movingState == MOVING_STATE_MOVING_DOWN) ? movedSteps : -1 * movedSteps);
logger.debug("# {} # {} {} {} = {}", deviceWhere, positionEstimation,
(movingState == MOVING_STATE_MOVING_DOWN) ? "+" : "-", movedSteps, newPos);
if (newPos > POSITION_DOWN) {
newPos = POSITION_DOWN;
} else if (newPos < POSITION_UP) {
newPos = POSITION_UP;
}
}
}
if (newPos != POSITION_UNKNOWN) {
if (newPos != positionEstimation) {
updateState(CHANNEL_SHUTTER, new PercentType(newPos));
}
} else {
updateState(CHANNEL_SHUTTER, UnDefType.UNDEF);
}
positionEstimation = newPos;
}
@Override
public void dispose() {
ScheduledFuture<?> mSc = moveSchedule;
if (mSc != null) {
mSc.cancel(true);
}
super.dispose();
}
}

View File

@@ -0,0 +1,544 @@
/**
* 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.openwebnet.handler;
import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.binding.openwebnet.handler.config.OpenWebNetBusBridgeConfig;
import org.openhab.binding.openwebnet.handler.config.OpenWebNetZigBeeBridgeConfig;
import org.openhab.binding.openwebnet.internal.discovery.OpenWebNetDeviceDiscoveryService;
import org.openhab.core.config.core.status.ConfigStatusMessage;
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.ThingTypeUID;
import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openwebnet4j.BUSGateway;
import org.openwebnet4j.GatewayListener;
import org.openwebnet4j.OpenDeviceType;
import org.openwebnet4j.OpenGateway;
import org.openwebnet4j.USBGateway;
import org.openwebnet4j.communication.OWNAuthException;
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.message.Automation;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.GatewayMgmt;
import org.openwebnet4j.message.Lighting;
import org.openwebnet4j.message.OpenMessage;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetBridgeHandler} is responsible for handling communication with gateways and handling events.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implements GatewayListener {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetBridgeHandler.class);
private static final int GATEWAY_ONLINE_TIMEOUT_SEC = 20; // Time to wait for the gateway to become connected
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.BRIDGE_SUPPORTED_THING_TYPES;
// ConcurrentHashMap of devices registered to this BridgeHandler
// association is: ownId (String) -> OpenWebNetThingHandler, with ownId = WHO.WHERE
private Map<String, @Nullable OpenWebNetThingHandler> registeredDevices = new ConcurrentHashMap<>();
protected @Nullable OpenGateway gateway;
private boolean isBusGateway = false;
private boolean isGatewayConnected = false;
public @Nullable OpenWebNetDeviceDiscoveryService deviceDiscoveryService;
private boolean reconnecting = false; // we are trying to reconnect to gateway
private boolean scanIsActive = false; // a device scan has been activated by OpenWebNetDeviceDiscoveryService;
private boolean discoveryByActivation;
public OpenWebNetBridgeHandler(Bridge bridge) {
super(bridge);
}
public boolean isBusGateway() {
return isBusGateway;
}
@Override
public void initialize() {
ThingTypeUID thingType = getThing().getThingTypeUID();
OpenGateway gw;
if (thingType.equals(THING_TYPE_ZB_GATEWAY)) {
gw = initZigBeeGateway();
} else {
gw = initBusGateway();
isBusGateway = true;
}
if (gw != null) {
gateway = gw;
gw.subscribe(this);
if (gw.isConnected()) { // gateway is already connected, device can go ONLINE
isGatewayConnected = true;
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.UNKNOWN);
logger.debug("Trying to connect gateway...");
try {
gw.connect();
scheduler.schedule(() -> {
// if status is still UNKNOWN after timer ends, set the device as OFFLINE
if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
logger.info("status still UNKNOWN. Setting device={} to OFFLINE", thing.getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Could not connect to gateway before " + GATEWAY_ONLINE_TIMEOUT_SEC + "s");
}
}, GATEWAY_ONLINE_TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (OWNException e) {
logger.debug("gw.connect() returned OWNException: {}", e.getMessage());
// status is updated by callback onConnectionError()
}
}
}
}
/**
* Init a ZigBee gateway based on config
*/
private @Nullable OpenGateway initZigBeeGateway() {
logger.debug("Initializing ZigBee USB gateway");
OpenWebNetZigBeeBridgeConfig zbBridgeConfig = getConfigAs(OpenWebNetZigBeeBridgeConfig.class);
String serialPort = zbBridgeConfig.getSerialPort();
if (serialPort == null || serialPort.isEmpty()) {
logger.warn("Cannot connect to gateway. No serial port has been provided in Bridge configuration.");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-no-serial-port");
return null;
} else {
return new USBGateway(serialPort);
}
}
/**
* Init a BUS gateway based on config
*/
private @Nullable OpenGateway initBusGateway() {
logger.debug("Initializing BUS gateway");
OpenWebNetBusBridgeConfig busBridgeConfig = getConfigAs(OpenWebNetBusBridgeConfig.class);
String host = busBridgeConfig.getHost();
if (host == null || host.isEmpty()) {
logger.warn("Cannot connect to gateway. No host/IP has been provided in Bridge configuration.");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.conf-error-no-ip-address");
return null;
} else {
int port = busBridgeConfig.getPort().intValue();
String passwd = busBridgeConfig.getPasswd();
String passwdMasked;
if (passwd.length() >= 4) {
passwdMasked = "******" + passwd.substring(passwd.length() - 3, passwd.length());
} else {
passwdMasked = "******";
}
discoveryByActivation = busBridgeConfig.getDiscoveryByActivation();
logger.debug("Creating new BUS gateway with config properties: {}:{}, pwd={}, discoveryByActivation={}",
host, port, passwdMasked, discoveryByActivation);
return new BUSGateway(host, port, passwd);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("handleCommand (command={} - channel={})", command, channelUID);
OpenGateway gw = gateway;
if (gw != null && !gw.isConnected()) {
logger.warn("Gateway is NOT connected, skipping command");
return;
} else {
logger.warn("Channel not supported: channel={}", channelUID);
}
}
@Override
public Collection<ConfigStatusMessage> getConfigStatus() {
return Collections.emptyList();
}
@Override
public void handleRemoval() {
disconnectGateway();
super.handleRemoval();
}
@Override
public void dispose() {
disconnectGateway();
super.dispose();
}
private void disconnectGateway() {
OpenGateway gw = gateway;
if (gw != null) {
gw.closeConnection();
gw.unsubscribe(this);
logger.debug("gateway {} connection closed and unsubscribed", gw.toString());
gateway = null;
}
reconnecting = false;
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(OpenWebNetDeviceDiscoveryService.class);
}
/**
* Search for devices connected to this bridge handler's gateway
*
* @param listener to receive device found notifications
*/
public synchronized void searchDevices() {
scanIsActive = true;
logger.debug("------$$ scanIsActive={}", scanIsActive);
OpenGateway gw = gateway;
if (gw != null) {
if (!gw.isDiscovering()) {
if (!gw.isConnected()) {
logger.debug("------$$ Gateway is NOT connected, cannot search for devices");
return;
}
logger.info("------$$ STARTED active SEARCH for devices on gateway '{}'", this.getThing().getUID());
try {
gw.discoverDevices();
} catch (OWNException e) {
logger.warn("------$$ OWNException while discovering devices on gateway {}: {}",
this.getThing().getUID(), e.getMessage());
}
} else {
logger.debug("------$$ Searching devices on gateway {} already activated", this.getThing().getUID());
return;
}
} else {
logger.debug("------$$ Cannot search devices: no gateway associated to this handler");
}
}
@Override
public void onNewDevice(@Nullable Where w, @Nullable OpenDeviceType deviceType, @Nullable BaseOpenMessage message) {
OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
if (discService != null) {
if (w != null && deviceType != null) {
discService.newDiscoveryResult(w, deviceType, message);
} else {
logger.warn("onNewDevice with null where/deviceType, msg={}", message);
}
} else {
logger.warn("onNewDevice but null deviceDiscoveryService");
}
}
@Override
public void onDiscoveryCompleted() {
logger.info("------$$ FINISHED active SEARCH for devices on gateway '{}'", this.getThing().getUID());
}
/**
* Notifies that the scan has been stopped/aborted by OpenWebNetDeviceDiscoveryService
*/
public void scanStopped() {
scanIsActive = false;
logger.debug("------$$ scanIsActive={}", scanIsActive);
}
private void discoverByActivation(BaseOpenMessage baseMsg) {
logger.debug("BridgeHandler.discoverByActivation() msg={}", baseMsg);
OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
if (discService == null) {
logger.warn("discoverByActivation: null OpenWebNetDeviceDiscoveryService, ignoring msg={}", baseMsg);
return;
}
if (baseMsg instanceof Lighting) {
OpenDeviceType type = null;
try {
type = baseMsg.detectDeviceType();
} catch (FrameException e) {
logger.warn("Exception while detecting device type: {}", e.getMessage());
}
if (type != null) {
discService.newDiscoveryResult(baseMsg.getWhere(), type, baseMsg);
} else {
logger.debug("discoverByActivation: no device type detected from msg: {}", baseMsg);
}
}
}
/**
* Register a device ThingHandler to this BridgHandler
*
* @param ownId the device OpenWebNet id
* @param thingHandler the thing handler to be registered
*/
protected void registerDevice(String ownId, OpenWebNetThingHandler thingHandler) {
if (registeredDevices.containsKey(ownId)) {
logger.warn("registering device with an existing ownId={}", ownId);
}
registeredDevices.put(ownId, thingHandler);
logger.debug("registered device ownId={}, thing={}", ownId, thingHandler.getThing().getUID());
}
/**
* Un-register a device from this bridge handler
*
* @param oId the device OpenWebNet id
*/
protected void unregisterDevice(String oId) {
if (registeredDevices.remove(oId) != null) {
logger.debug("un-registered device ownId={}", oId);
} else {
logger.warn("could not un-register ownId={} (not found)", oId);
}
}
@Override
public void onEventMessage(@Nullable OpenMessage msg) {
logger.trace("RECEIVED <<<<< {}", msg);
if (msg == null) {
logger.warn("received event msg is null");
return;
}
if (msg.isACK() || msg.isNACK()) {
return; // we ignore ACKS/NACKS
}
// GATEWAY MANAGEMENT
if (msg instanceof GatewayMgmt) {
// noop
return;
}
BaseOpenMessage baseMsg = (BaseOpenMessage) msg;
// let's try to get the Thing associated with this message...
if (baseMsg instanceof Lighting || baseMsg instanceof Automation) {
String ownId = ownIdFromMessage(baseMsg);
logger.debug("ownId={}", ownId);
OpenWebNetThingHandler deviceHandler = registeredDevices.get(ownId);
if (deviceHandler == null) {
OpenGateway gw = gateway;
if (isBusGateway && ((gw != null && !gw.isDiscovering() && scanIsActive)
|| (discoveryByActivation && !scanIsActive))) {
discoverByActivation(baseMsg);
} else {
logger.debug("ownId={} has NO DEVICE associated, ignoring it", ownId);
}
} else {
deviceHandler.handleMessage(baseMsg);
}
} else {
logger.debug("BridgeHandler ignoring frame {}. WHO={} is not supported by this binding", baseMsg,
baseMsg.getWho());
}
}
@Override
public void onConnected() {
isGatewayConnected = true;
Map<String, String> properties = editProperties();
boolean propertiesChanged = false;
OpenGateway gw = gateway;
if (gw == null) {
logger.warn("received onConnected() but gateway is null");
return;
}
if (gw instanceof USBGateway) {
logger.info("------------------- CONNECTED to USB (ZigBee) gateway - USB port: {}",
((USBGateway) gw).getSerialPortName());
} else {
logger.info("------------------- CONNECTED to BUS gateway '{}' ({}:{})", thing.getUID(),
((BUSGateway) gw).getHost(), ((BUSGateway) gw).getPort());
// update serial number property (with MAC address)
if (properties.get(PROPERTY_SERIAL_NO) != gw.getMACAddr().toUpperCase()) {
properties.put(PROPERTY_SERIAL_NO, gw.getMACAddr().toUpperCase());
propertiesChanged = true;
logger.debug("updated property gw serialNumber: {}", properties.get(PROPERTY_SERIAL_NO));
}
}
if (properties.get(PROPERTY_FIRMWARE_VERSION) != gw.getFirmwareVersion()) {
properties.put(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
propertiesChanged = true;
logger.debug("updated property gw firmware version: {}", properties.get(PROPERTY_FIRMWARE_VERSION));
}
if (propertiesChanged) {
updateProperties(properties);
logger.info("properties updated for '{}'", thing.getUID());
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void onConnectionError(@Nullable OWNException error) {
String errMsg;
if (error == null) {
errMsg = "unknown error";
} else {
errMsg = error.getMessage();
}
logger.info("------------------- ON CONNECTION ERROR: {}", errMsg);
isGatewayConnected = false;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, errMsg);
tryReconnectGateway();
}
@Override
public void onConnectionClosed() {
isGatewayConnected = false;
logger.debug("onConnectionClosed() - isGatewayConnected={}", isGatewayConnected);
// NOTE: cannot change to OFFLINE here because we are already in REMOVING state
}
@Override
public void onDisconnected(@Nullable OWNException e) {
isGatewayConnected = false;
String errMsg;
if (e == null) {
errMsg = "unknown error";
} else {
errMsg = e.getMessage();
}
logger.info("------------------- DISCONNECTED from gateway. OWNException={}", errMsg);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Disconnected from gateway (onDisconnected - " + errMsg + ")");
tryReconnectGateway();
}
private void tryReconnectGateway() {
OpenGateway gw = gateway;
if (gw != null) {
if (!reconnecting) {
reconnecting = true;
logger.info("------------------- Starting RECONNECT cycle to gateway");
try {
gw.reconnect();
} catch (OWNAuthException e) {
logger.info("------------------- AUTH error from gateway. Stopping reconnect");
reconnecting = false;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Authentication error. Check gateway password in Thing Configuration Parameters (" + e
+ ")");
}
} else {
logger.debug("------------------- reconnecting=true, do nothing");
}
} else {
logger.debug("------------------- cannot start RECONNECT, gateway is null");
}
}
@Override
public void onReconnected() {
reconnecting = false;
logger.info("------------------- RE-CONNECTED to gateway!");
OpenGateway gw = gateway;
if (gw != null) {
updateStatus(ThingStatus.ONLINE);
if (gw.getFirmwareVersion() != null) {
this.updateProperty(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
logger.debug("gw firmware version: {}", gw.getFirmwareVersion());
}
}
}
/**
* Return a ownId string (=WHO.WHERE) from a deviceWhere thing config parameter (already normalized) and its
* handler.
*
* @param deviceWhere the device WHERE config parameter
* @param handler the thing handler
* @return the ownId
*/
protected String ownIdFromDeviceWhere(@Nullable String deviceWhere, OpenWebNetThingHandler handler) {
return handler.ownIdPrefix() + "." + deviceWhere;
}
/**
* Returns a ownId string (=WHO.WHERE) from a Where address and Who
*
* @param where the Where address (to be normalized)
* @param who the Who
* @return the ownId
*/
public String ownIdFromWhoWhere(Where where, Who who) {
return who.value() + "." + normalizeWhere(where);
}
/**
* Return a ownId string (=WHO.WHERE) from a BaseOpenMessage
*
* @param baseMsg the BaseOpenMessage
* @return the ownId String
*/
private String ownIdFromMessage(BaseOpenMessage baseMsg) {
return baseMsg.getWho().value() + "." + normalizeWhere(baseMsg.getWhere());
}
/**
* Transform a Where address into a Thing id string based on bridge type (BUS/USB ZigBee).
* '#' in WHERE are changed to 'h'
*
* @param where the Where address
* @return the thing Id
*/
public String thingIdFromWhere(Where where) {
return normalizeWhere(where).replace('#', 'h'); // '#' cannot be used in ThingUID;
}
/**
* Normalize a Where address for Thermo and Zigbee devices
*
* @param where the Where address
* @return the normalized address
*/
public String normalizeWhere(Where where) {
String str = "";
if (isBusGateway) {
if (where.value().indexOf('#') < 0) { // no hash present
str = where.value();
} else if (where.value().indexOf("#4#") > 0) { // local bus: APL#4#bus
str = where.value();
} else if (where.value().indexOf('#') == 0) { // thermo zone via central unit: #0 or #Z (Z=[1-99]) --> Z
str = where.value().substring(1);
} else if (where.value().indexOf('#') > 0) { // thermo zone and actuator N: Z#N (Z=[1-99], N=[1-9]) -- > Z
str = where.value().substring(0, where.value().indexOf('#'));
} else {
logger.warn("normalizeWhere() unexpected WHERE: {}", where);
str = where.value();
}
return str;
} else {
return where.value();
}
}
}

View File

@@ -0,0 +1,74 @@
/**
* 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.openwebnet.handler;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openwebnet4j.message.BaseOpenMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetGenericHandler} is responsible for handling Generic OpenWebNet
* devices. It does not too much, but it is needed to avoid handler errors and to tell the user
* that some device has been found by the gateway but it was not recognised.
* It extends the abstract {@link OpenWebNetThingHandler}.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetGenericHandler extends OpenWebNetThingHandler {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetGenericHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.GENERIC_SUPPORTED_THING_TYPES;
public OpenWebNetGenericHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
super.initialize();
}
@Override
protected void requestChannelState(ChannelUID channel) {
// do nothing
logger.warn("There are no channels");
}
@Override
protected void handleChannelCommand(ChannelUID channel, Command command) {
// do nothing
logger.warn("There are no channels");
}
@Override
protected String ownIdPrefix() {
return "G";
}
@Override
protected void handleMessage(BaseOpenMessage msg) {
super.handleMessage(msg);
// do nothing
logger.warn("handleMessage(): Nothing to do!");
}
} // class

View File

@@ -0,0 +1,362 @@
/**
* 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.openwebnet.handler;
import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.Lighting;
import org.openwebnet4j.message.What;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.WhereZigBee;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetLightingHandler} is responsible for handling commands/messages for a Lighting OpenWebNet device.
* It extends the abstract {@link OpenWebNetThingHandler}.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetLightingHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.LIGHTING_SUPPORTED_THING_TYPES;
private static final int BRIGHTNESS_CHANGE_DELAY_MSEC = 1500; // delay to wait before sending another brightness
// status request
private long lastBrightnessChangeSentTS = 0; // timestamp when last brightness change was sent to the device
private boolean brightnessLevelRequested = false; // was the brightness level requested ?
private int latestBrightnessWhat = -1; // latest brightness WHAT value (-1 = unknown)
private int latestBrightnessWhatBeforeOff = -1; // latest brightness WHAT value before device was set to off
public OpenWebNetLightingHandler(Thing thing) {
super(thing);
}
@Override
protected void requestChannelState(ChannelUID channel) {
logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
try {
send(Lighting.requestStatus(toWhere(channel)));
} catch (OWNException e) {
logger.warn("Exception while requesting channel {} state: {}", channel, e.getMessage());
}
}
@Override
protected void handleChannelCommand(ChannelUID channel, Command command) {
switch (channel.getId()) {
case CHANNEL_BRIGHTNESS:
handleBrightnessCommand(command);
break;
case CHANNEL_SWITCH:
case CHANNEL_SWITCH_01:
case CHANNEL_SWITCH_02:
handleSwitchCommand(channel, command);
break;
default: {
logger.warn("Unsupported channel UID {}", channel);
}
}
}
/**
* Handles Lighting switch command for a channel
*
* @param channel the channel
* @param command the command
*/
private void handleSwitchCommand(ChannelUID channel, Command command) {
logger.debug("handleSwitchCommand() (command={} - channel={})", command, channel);
if (command instanceof OnOffType) {
try {
if (OnOffType.ON.equals(command)) {
send(Lighting.requestTurnOn(toWhere(channel)));
} else if (OnOffType.OFF.equals(command)) {
send(Lighting.requestTurnOff(toWhere(channel)));
}
} catch (OWNException e) {
logger.warn("Exception while processing command {}: {}", command, e.getMessage());
}
} else {
logger.warn("Unsupported command: {}", command);
}
}
/**
* Handles Lighting brightness command (ON, OFF, xx%, INCREASE, DECREASE)
*
* @param command the command
*/
private void handleBrightnessCommand(Command command) {
logger.debug("handleBrightnessCommand() command={}", command);
if (command instanceof PercentType) {
int percent = ((PercentType) command).intValue();
dimLightTo(Lighting.percentToWhat(percent).value(), command);
} else if (command instanceof IncreaseDecreaseType) {
if (IncreaseDecreaseType.INCREASE.equals(command)) {
dimLightTo(latestBrightnessWhat + 1, command);
} else { // DECREASE
dimLightTo(latestBrightnessWhat - 1, command);
}
} else if (command instanceof OnOffType) {
if (OnOffType.ON.equals(command)) {
dimLightTo(latestBrightnessWhat, command);
} else { // OFF
dimLightTo(0, command);
}
} else {
logger.warn("Cannot handle command {} for thing {}", command, getThing().getUID());
}
}
/**
* Helper method to dim light to a valid OWN value
*/
private void dimLightTo(int whatInt, Command command) {
int newWhatInt = whatInt;
logger.debug("-DIM- dimLightTo() latestBriWhat={} latestBriBeforeOff={} briLevelRequested={}",
latestBrightnessWhat, latestBrightnessWhatBeforeOff, brightnessLevelRequested);
What newWhat;
if (OnOffType.ON.equals(command) && latestBrightnessWhat <= 0) {
// ON after OFF/Unknown -> we reset channel to last value before OFF (if exists)
if (latestBrightnessWhatBeforeOff > 0) { // we know last brightness -> set dimmer to it
newWhatInt = latestBrightnessWhatBeforeOff;
} else { // we do not know last brightness -> set dimmer to 100%
newWhatInt = 10;
}
}
logger.debug("-DIM- requested level={}", newWhatInt);
if (newWhatInt != latestBrightnessWhat) {
if (newWhatInt >= 0 && newWhatInt <= 10) {
newWhat = Lighting.WHAT.fromValue(newWhatInt);
if (newWhat.equals(Lighting.WHAT.ON)) {
// change it to WHAT.DIMMER_20 (dimming to 10% is not allowed in OWN)
newWhat = Lighting.WHAT.DIMMER_20;
}
// save current brightness level before sending bri=0 command to device
if (newWhatInt == 0) {
latestBrightnessWhatBeforeOff = latestBrightnessWhat;
}
Where w = deviceWhere;
if (w != null) {
try {
lastBrightnessChangeSentTS = System.currentTimeMillis();
send(Lighting.requestDimTo(w.value(), newWhat));
} catch (OWNException e) {
logger.warn("Exception while sending dimLightTo for command {}: {}", command, e.getMessage());
}
}
} else {
logger.debug("-DIM- do nothing");
}
} else {
logger.debug("-DIM- do nothing");
}
logger.debug("-DIM- latestBriWhat={} latestBriBeforeOff={} briLevelRequested={}", latestBrightnessWhat,
latestBrightnessWhatBeforeOff, brightnessLevelRequested);
}
@Override
protected String ownIdPrefix() {
return Who.LIGHTING.value().toString();
}
@Override
protected void handleMessage(BaseOpenMessage msg) {
super.handleMessage(msg);
updateLightState((Lighting) msg);
}
/**
* Updates light state based on a OWN Lighting event message received
*
* @param msg the Lighting message received
*/
private void updateLightState(Lighting msg) {
logger.debug("updateLightState() for thing: {}", thing.getUID());
ThingTypeUID thingType = thing.getThingTypeUID();
if (THING_TYPE_ZB_DIMMER.equals(thingType) || THING_TYPE_BUS_DIMMER.equals(thingType)) {
updateLightBrightnessState(msg);
} else {
updateLightOnOffState(msg);
}
}
/**
* Updates on/off state based on a OWN Lighting event message received
*
* @param msg the Lighting message received
*/
private void updateLightOnOffState(Lighting msg) {
String channelID;
OpenWebNetBridgeHandler brH = bridgeHandler;
if (brH != null) {
if (brH.isBusGateway()) {
channelID = CHANNEL_SWITCH;
} else {
WhereZigBee w = (WhereZigBee) (msg.getWhere());
if (WhereZigBee.UNIT_02.equals(w.getUnit())) {
channelID = CHANNEL_SWITCH_02;
} else {
channelID = CHANNEL_SWITCH_01;
}
}
if (msg.isOn()) {
updateState(channelID, OnOffType.ON);
} else if (msg.isOff()) {
updateState(channelID, OnOffType.OFF);
} else {
logger.debug("updateLightOnOffState() Ignoring unsupported WHAT for thing {}. Frame={}",
getThing().getUID(), msg.getFrameValue());
}
}
}
/**
* Updates brightness level based on a OWN Lighting event message received
*
* @param msg the Lighting message received
*/
private synchronized void updateLightBrightnessState(Lighting msg) {
final String channel = CHANNEL_BRIGHTNESS;
logger.debug(" $BRI updateLightBrightnessState() msg={}", msg);
logger.debug(" $BRI updateLightBr() latestBriWhat={} latestBriBeforeOff={} brightnessLevelRequested={}",
latestBrightnessWhat, latestBrightnessWhatBeforeOff, brightnessLevelRequested);
long now = System.currentTimeMillis();
long delta = now - lastBrightnessChangeSentTS;
logger.debug(" $BRI now={} -> delta={}", now, delta);
if (msg.isOn() && !brightnessLevelRequested) {
if (delta >= BRIGHTNESS_CHANGE_DELAY_MSEC) {
// we send a light brightness status request ONLY if last brightness change
// was not just sent (>=BRIGHTNESS_CHANGE_DELAY_MSEC ago)
logger.debug(" $BRI change sent >={}ms ago, sending requestStatus...", BRIGHTNESS_CHANGE_DELAY_MSEC);
Where w = deviceWhere;
if (w != null) {
try {
send(Lighting.requestStatus(w.value()));
brightnessLevelRequested = true;
} catch (OWNException e) {
logger.warn(" $BRI exception while requesting light state: {}", e.getMessage());
}
}
} else {
logger.debug(" $BRI change sent {}<{}ms, NO requestStatus needed", delta,
BRIGHTNESS_CHANGE_DELAY_MSEC);
}
} else {
logger.debug(" $BRI update from network -> level should be present in WHAT part of the message");
if (msg.getWhat() != null) {
int newLevel = msg.getWhat().value();
logger.debug(" $BRI current level={} ----> new level={}", latestBrightnessWhat, newLevel);
if (latestBrightnessWhat != newLevel) {
updateState(channel, new PercentType(Lighting.levelToPercent(newLevel)));
if (msg.isOff()) {
latestBrightnessWhatBeforeOff = latestBrightnessWhat;
}
latestBrightnessWhat = newLevel;
} else {
logger.debug(" $BRI no change");
}
brightnessLevelRequested = false;
} else { // dimension notification
if (msg.getDim() == Lighting.DIM.DIMMER_LEVEL_100) {
int newPercent;
try {
newPercent = msg.parseDimmerLevel100();
} catch (FrameException fe) {
logger.warn("updateLightBrightnessState() Wrong value for dimmerLevel100 in message: {}", msg);
return;
}
int newLevel = Lighting.percentToWhat(newPercent).value();
logger.debug(" $BRI latest level={} ----> new percent={} ----> new level={}", latestBrightnessWhat,
newPercent, newLevel);
updateState(channel, new PercentType(newPercent));
if (newPercent == 0) {
latestBrightnessWhatBeforeOff = latestBrightnessWhat;
}
latestBrightnessWhat = newLevel;
brightnessLevelRequested = false;
} else {
logger.warn("updateLightBrightnessState() Cannot handle message {} for thing {}", msg,
getThing().getUID());
return;
}
}
}
logger.debug(" $BRI latestBriWhat={} latestBriBeforeOff={} brightnessLevelRequested={}", latestBrightnessWhat,
latestBrightnessWhatBeforeOff, brightnessLevelRequested);
}
/**
* Returns a WHERE address string based on bridge type and unit (optional)
*
* @param unit the device unit
**/
@Nullable
protected String toWhere(String unit) {
Where w = deviceWhere;
if (w != null) {
OpenWebNetBridgeHandler brH = bridgeHandler;
if (brH != null && brH.isBusGateway()) {
return w.value();
} else {
return w + unit;
}
} else {
return null;
}
}
/**
* Returns a WHERE address string based on channel
*
* @param channel the channel
**/
@Nullable
protected String toWhere(ChannelUID channel) {
Where w = deviceWhere;
if (w != null) {
OpenWebNetBridgeHandler brH = bridgeHandler;
if (brH != null) {
if (brH.isBusGateway()) {
return w.value();
} else if (channel.getId().equals(CHANNEL_SWITCH_02)) {
return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_02);
} else { // CHANNEL_SWITCH_01 or other channels
return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_01);
}
}
}
return null;
}
}

View File

@@ -0,0 +1,206 @@
/**
* 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.openwebnet.handler;
import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
import java.util.Map;
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.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openwebnet4j.OpenGateway;
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.communication.Response;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.OpenMessage;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.WhereLightAutom;
import org.openwebnet4j.message.WhereZigBee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetThingHandler} is responsible for handling commands for a OpenWebNet device.
* It's the abstract class for all OpenWebNet things. It should be extended by each specific OpenWebNet category of
* device (WHO).
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public abstract class OpenWebNetThingHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetThingHandler.class);
protected @Nullable OpenWebNetBridgeHandler bridgeHandler;
protected @Nullable String ownId; // OpenWebNet identifier for this device: WHO.WHERE
protected @Nullable Where deviceWhere; // this device Where address
protected @Nullable ScheduledFuture<?> refreshTimeout;
public OpenWebNetThingHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
Bridge bridge = getBridge();
if (bridge != null) {
OpenWebNetBridgeHandler brH = (OpenWebNetBridgeHandler) bridge.getHandler();
if (brH != null) {
bridgeHandler = brH;
Object deviceWhereConfig = getConfig().get(CONFIG_PROPERTY_WHERE);
if (!(deviceWhereConfig instanceof String)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"WHERE parameter in configuration is null or invalid");
return;
} else {
String deviceWhereStr = (String) getConfig().get(CONFIG_PROPERTY_WHERE);
Where w;
if (brH.isBusGateway()) {
w = new WhereLightAutom(deviceWhereStr);
} else {
w = new WhereZigBee(deviceWhereStr);
}
deviceWhere = w;
final String oid = brH.ownIdFromDeviceWhere(w.value(), this);
ownId = oid;
Map<String, String> properties = editProperties();
properties.put(PROPERTY_OWNID, oid);
updateProperties(properties);
brH.registerDevice(oid, this);
logger.debug("associated thing to bridge with ownId={}", ownId);
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "waiting state update...");
}
return;
}
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"No bridge associated, please assign a bridge in thing configuration.");
}
@Override
public void handleCommand(ChannelUID channel, Command command) {
logger.debug("handleCommand() (command={} - channel={})", command, channel);
OpenWebNetBridgeHandler handler = bridgeHandler;
if (handler != null) {
OpenGateway gw = handler.gateway;
if (gw != null && !gw.isConnected()) {
logger.info("Cannot handle command {}:{} for {}: gateway is not connected", channel, command,
getThing().getUID());
return;
}
if (command instanceof RefreshType) {
requestChannelState(channel);
// set a schedule to put device OFFLINE if no answer is received after THING_STATE_REQ_TIMEOUT_SEC
refreshTimeout = scheduler.schedule(() -> {
if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Could not get channel state (timer expired)");
}
}, THING_STATE_REQ_TIMEOUT_SEC, TimeUnit.SECONDS);
} else {
handleChannelCommand(channel, command);
}
} else {
logger.debug("Thing {} is not associated to any gateway, skipping command", getThing().getUID());
}
}
/**
* Handles a command for the specific channel for this thing.
* It must be implemented by each specific OpenWebNet category of device (WHO), based on channel
*
* @param channel specific ChannleUID
* @param command the Command to be executed
*/
protected abstract void handleChannelCommand(ChannelUID channel, Command command);
/**
* Handle incoming message from OWN network via bridge Thing, directed to this device. It should be further
* implemented by each specific device handler.
*
* @param msg the message to handle
*/
protected void handleMessage(BaseOpenMessage msg) {
ThingStatus ts = getThing().getStatus();
if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
updateStatus(ThingStatus.ONLINE);
}
}
/**
* Helper method to send OWN messages from ThingsHandlers
*/
protected @Nullable Response send(OpenMessage msg) throws OWNException {
OpenWebNetBridgeHandler handler = bridgeHandler;
if (handler != null) {
OpenGateway gw = handler.gateway;
if (gw != null) {
return gw.send(msg);
}
}
return null;
}
/**
* Helper method to send with high priority OWN messages from ThingsHandlers
*/
protected @Nullable Response sendHighPriority(OpenMessage msg) throws OWNException {
OpenWebNetBridgeHandler handler = bridgeHandler;
if (handler != null) {
OpenGateway gw = handler.gateway;
if (gw != null) {
return gw.sendHighPriority(msg);
}
}
return null;
}
/**
* Request to gateway state for thing channel. It must be implemented by each specific device handler.
*
* @param channel the channel to request the state for
*/
protected abstract void requestChannelState(ChannelUID channel);
@Override
public void dispose() {
OpenWebNetBridgeHandler bh = bridgeHandler;
String oid = ownId;
if (bh != null && oid != null) {
bh.unregisterDevice(oid);
}
ScheduledFuture<?> sc = refreshTimeout;
if (sc != null) {
sc.cancel(true);
}
super.dispose();
}
/**
* Returns a prefix String for ownId specific for each handler. To be implemented by sub-classes.
*
* @return
*/
protected abstract String ownIdPrefix();
}

View File

@@ -0,0 +1,49 @@
/**
* 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.openwebnet.handler.config;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* BUS Bridge configuration object
*
* @author Massimo Valla - Initial contribution
*
*/
@NonNullByDefault
public class OpenWebNetBusBridgeConfig {
private BigDecimal port = new BigDecimal(20000);
private @Nullable String host;
private String passwd = "12345";
private boolean discoveryByActivation = false;
public BigDecimal getPort() {
return port;
}
public @Nullable String getHost() {
return host;
}
public String getPasswd() {
return passwd;
}
public Boolean getDiscoveryByActivation() {
return discoveryByActivation;
}
}

View File

@@ -0,0 +1,32 @@
/**
* 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.openwebnet.handler.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* ZigBee USB Bridge configuration object
*
* @author Massimo Valla - Initial contribution
*
*/
@NonNullByDefault
public class OpenWebNetZigBeeBridgeConfig {
private @Nullable String serialPort;
public @Nullable String getSerialPort() {
return serialPort;
}
}

View File

@@ -0,0 +1,67 @@
/**
* 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.openwebnet.internal;
import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.ALL_SUPPORTED_THING_TYPES;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.handler.OpenWebNetAutomationHandler;
import org.openhab.binding.openwebnet.handler.OpenWebNetBridgeHandler;
import org.openhab.binding.openwebnet.handler.OpenWebNetGenericHandler;
import org.openhab.binding.openwebnet.handler.OpenWebNetLightingHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetHandlerFactory} is responsible for creating thing handlers.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.openwebnet", service = ThingHandlerFactory.class)
public class OpenWebNetHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetHandlerFactory.class);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return ALL_SUPPORTED_THING_TYPES.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
if (OpenWebNetBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
logger.debug("creating NEW BRIDGE Handler");
return new OpenWebNetBridgeHandler((Bridge) thing);
} else if (OpenWebNetGenericHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
logger.debug("creating NEW GENERIC Handler");
return new OpenWebNetGenericHandler(thing);
} else if (OpenWebNetLightingHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
logger.debug("creating NEW LIGHTING Handler");
return new OpenWebNetLightingHandler(thing);
} else if (OpenWebNetAutomationHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
logger.debug("creating NEW AUTOMATION Handler");
return new OpenWebNetAutomationHandler(thing);
}
logger.warn("ThingType {} is not supported by this binding", thing.getThingTypeUID());
return null;
}
}

View File

@@ -0,0 +1,232 @@
/**
* 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.openwebnet.internal.discovery;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.model.meta.DeviceDetails;
import org.jupnp.model.meta.ManufacturerDetails;
import org.jupnp.model.meta.ModelDetails;
import org.jupnp.model.meta.RemoteDevice;
import org.jupnp.model.meta.RemoteDeviceIdentity;
import org.jupnp.model.types.UDN;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link BusGatewayUpnpDiscovery} is responsible for discovering supported BTicino BUS
* gateways devices using UPnP. It implements {@link UpnpDiscoveryParticipant}.
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
@Component(service = UpnpDiscoveryParticipant.class)
public class BusGatewayUpnpDiscovery implements UpnpDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(BusGatewayUpnpDiscovery.class);
public enum BusGatewayId {
MH201("IPscenarioModule", "MH201"),
MH202("scheduler", "MH202"),
F454("webserver", "F454"),
MY_HOME_SERVER1("myhomeserver1", "MYHOMESERVER1"),
TOUCH_SCREEN_10("ts10", "TOUCHSCREEN10"),
MH200N("lightingcontrolunit", "MH200N");
private final String value, thingId;
private BusGatewayId(String value, String thingId) {
this.value = value;
this.thingId = thingId;
}
public static @Nullable BusGatewayId fromValue(String s) {
Optional<BusGatewayId> m = Arrays.stream(values()).filter(val -> s.equals(val.value)).findFirst();
if (m.isPresent()) {
return m.get();
} else {
return null;
}
}
public String getThingId() {
return thingId;
}
}
/**
* DeviceInfo bean to store device useful info (and log them)
*/
public class DeviceInfo {
@Nullable
private String friendlyName;
private String modelName = "<unknown>";
private String modelDescription = "<unknown>";
private String modelNumber = "<unknown>";
private String serialNumber = "<unknown>";
@Nullable
private String host;
private String manufacturer = "<unknown>";
@Nullable
private UDN udn;
private boolean isBTicino = false;
private DeviceInfo(RemoteDevice device) {
String deviceLog = "Discovered device:\n+=== UPnP =========================================";
RemoteDeviceIdentity identity = device.getIdentity();
if (identity != null) {
this.udn = identity.getUdn();
deviceLog += "\n| ID.UDN : " + udn;
if (identity.getDescriptorURL() != null) {
deviceLog += "\n| ID.DESC URL : " + identity.getDescriptorURL();
this.host = identity.getDescriptorURL().getHost();
}
deviceLog += "\n| ID.MAX AGE : " + identity.getMaxAgeSeconds();
}
deviceLog += "\n| --------------";
DeviceDetails details = device.getDetails();
if (details != null) {
ManufacturerDetails manufacturerDetails = details.getManufacturerDetails();
if (manufacturerDetails != null) {
this.manufacturer = manufacturerDetails.getManufacturer();
deviceLog += "\n| MANUFACTURER : " + manufacturer + " (" + manufacturerDetails.getManufacturerURI()
+ ")";
if (manufacturer.toUpperCase().contains("BTICINO")) {
this.isBTicino = true;
}
}
ModelDetails modelDetails = details.getModelDetails();
if (modelDetails != null) {
// Model Name | Desc | Number (Uri)
this.modelName = modelDetails.getModelName();
this.modelDescription = modelDetails.getModelDescription();
this.modelNumber = modelDetails.getModelNumber();
deviceLog += "\n| MODEL : " + modelName + " | " + modelDescription + " | " + modelNumber
+ " (" + modelDetails.getModelURI() + ")";
}
if (isBTicino) {
this.friendlyName = details.getFriendlyName();
deviceLog += "\n| FRIENDLY NAME: " + friendlyName;
this.serialNumber = details.getSerialNumber();
deviceLog += "\n| SERIAL # : " + serialNumber;
deviceLog += "\n| BASE URL : " + details.getBaseURL();
deviceLog += "\n| UPC : " + details.getUpc();
}
}
deviceLog += "\n+==================================================";
logger.debug(deviceLog);
}
} /* DeviceInfo */
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.singleton(OpenWebNetBindingConstants.THING_TYPE_BUS_GATEWAY);
}
@Override
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
logger.info("Found device {}", device.getType());
DeviceInfo devInfo = new DeviceInfo(device);
if (!devInfo.manufacturer.matches("<unknown>")) {
logger.info(" |- {} ({})", devInfo.modelName, devInfo.manufacturer);
}
ThingUID thingId = generateThingUID(devInfo);
if (thingId != null) {
String host = devInfo.host;
if (host != null) {
String label = "BUS Gateway";
String fn = devInfo.friendlyName;
if (fn != null) {
if (!fn.isEmpty()) {
label = fn;
}
}
label = label + " (" + devInfo.modelName + ", " + devInfo.modelNumber + ", " + devInfo.host + ")";
Map<String, Object> properties = new HashMap<>(4);
properties.put(OpenWebNetBindingConstants.CONFIG_PROPERTY_HOST, host);
properties.put(OpenWebNetBindingConstants.PROPERTY_FIRMWARE_VERSION, devInfo.modelNumber);
properties.put(OpenWebNetBindingConstants.PROPERTY_MODEL, devInfo.modelName);
properties.put(OpenWebNetBindingConstants.PROPERTY_SERIAL_NO, devInfo.serialNumber);
DiscoveryResult result = DiscoveryResultBuilder.create(thingId).withProperties(properties)
.withRepresentationProperty(OpenWebNetBindingConstants.PROPERTY_SERIAL_NO).withLabel(label)
.build();
UDN udn = devInfo.udn;
String udnStr;
if (udn != null) {
udnStr = udn.getIdentifierString();
} else {
udnStr = null;
}
logger.info("Created a DiscoveryResult for gateway '{}' (UDN={})", devInfo.friendlyName, udnStr);
return result;
} else {
logger.warn("Could not get host for device (UDN={})", devInfo.udn);
return null;
}
} else {
return null;
}
}
@Override
public @Nullable ThingUID getThingUID(RemoteDevice device) {
return generateThingUID(new DeviceInfo(device));
}
/**
* Returns a ThingUID for supported devices from already extracted DeviceInfo
*
* @param devInfo the device info
* @return a new ThingUID, or null if the device is not supported by the binding
*/
private @Nullable ThingUID generateThingUID(DeviceInfo devInfo) {
if (devInfo.isBTicino) {
UDN udn = devInfo.udn;
String idString = null;
if (udn != null) {
idString = udn.getIdentifierString();
if (idString != null) {
String[] spl = idString.split("-");
if (spl.length > 3) {
BusGatewayId gwId = BusGatewayId.fromValue(spl[1]);
if (gwId != null) {
logger.debug("'{}' is a supported gateway", gwId);
String mac = spl[3]; // extract MAC address
String normalizedMac = mac.toLowerCase().replaceAll("[^a-f0-9]", "");
if (!normalizedMac.isEmpty()) {
return new ThingUID(OpenWebNetBindingConstants.THING_TYPE_BUS_GATEWAY,
gwId.getThingId() + "_" + normalizedMac);
}
}
}
}
}
logger.info("Found BTicino device: not a OpenWebNet gateway or is not supported (UDN={})", idString);
}
return null;
}
}

View File

@@ -0,0 +1,193 @@
/**
* 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.openwebnet.internal.discovery;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
import org.openhab.binding.openwebnet.handler.OpenWebNetBridgeHandler;
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.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openwebnet4j.OpenDeviceType;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.Where;
import org.openwebnet4j.message.WhereZigBee;
import org.openwebnet4j.message.Who;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetDeviceDiscoveryService} is responsible for discovering OpenWebNet devices connected to a
* bridge/gateway
*
* @author Massimo Valla - Initial contribution
*/
@NonNullByDefault
public class OpenWebNetDeviceDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetDeviceDiscoveryService.class);
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.DEVICE_SUPPORTED_THING_TYPES;
private static final int SEARCH_TIME_SEC = 60;
private @NonNullByDefault({}) OpenWebNetBridgeHandler bridgeHandler;
private @NonNullByDefault({}) ThingUID bridgeUID;
public OpenWebNetDeviceDiscoveryService() {
super(SUPPORTED_THING_TYPES, SEARCH_TIME_SEC);
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return OpenWebNetDeviceDiscoveryService.SUPPORTED_THING_TYPES;
}
@Override
protected void startScan() {
logger.info("------ SEARCHING for DEVICES on bridge '{}' ({}) ...", bridgeHandler.getThing().getLabel(),
bridgeUID);
bridgeHandler.searchDevices();
}
@Override
protected void stopScan() {
logger.debug("------ stopScan() on bridge '{}'", bridgeUID);
bridgeHandler.scanStopped();
}
@Override
public void abortScan() {
logger.debug("------ abortScan() on bridge '{}'", bridgeUID);
bridgeHandler.scanStopped();
}
/**
* Create and notify to Inbox a new DiscoveryResult based on WHERE, OpenDeviceType and BaseOpenMessage
*
* @param where the discovered device's address (WHERE)
* @param deviceType {@link OpenDeviceType} of the discovered device
* @param message the OWN message received that identified the device (optional)
*/
public void newDiscoveryResult(Where where, OpenDeviceType deviceType, @Nullable BaseOpenMessage baseMsg) {
logger.info("newDiscoveryResult() WHERE={}, deviceType={}", where, deviceType);
ThingTypeUID thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_GENERIC_DEVICE; // generic device
String thingLabel = OpenWebNetBindingConstants.THING_LABEL_GENERIC_DEVICE;
Who deviceWho = Who.UNKNOWN;
switch (deviceType) {
case ZIGBEE_ON_OFF_SWITCH:
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_ZB_ON_OFF_SWITCH;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_ZB_ON_OFF_SWITCH;
deviceWho = Who.LIGHTING;
break;
case ZIGBEE_DIMMER_SWITCH:
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_ZB_DIMMER;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_ZB_DIMMER;
deviceWho = Who.LIGHTING;
break;
case SCS_ON_OFF_SWITCH:
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_BUS_ON_OFF_SWITCH;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_BUS_ON_OFF_SWITCH;
deviceWho = Who.LIGHTING;
break;
case SCS_DIMMER_SWITCH:
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_BUS_DIMMER;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_BUS_DIMMER;
deviceWho = Who.LIGHTING;
break;
case SCS_SHUTTER_SWITCH:
case SCS_SHUTTER_CONTROL: {
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_BUS_AUTOMATION;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_BUS_AUTOMATION;
deviceWho = Who.AUTOMATION;
break;
}
case ZIGBEE_SHUTTER_SWITCH:
case ZIGBEE_SHUTTER_CONTROL: {
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_ZB_AUTOMATION;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_ZB_AUTOMATION;
deviceWho = Who.AUTOMATION;
break;
}
default:
logger.warn("Device type {} is not supported, default to GENERIC device (WHERE={})", deviceType, where);
if (where instanceof WhereZigBee) {
thingLabel = "ZigBee " + thingLabel;
}
if (baseMsg != null) {
deviceWho = baseMsg.getWho();
}
}
String tId = bridgeHandler.thingIdFromWhere(where);
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, tId);
DiscoveryResult discoveryResult = null;
String whereLabel = where.value();
if (where instanceof WhereZigBee && WhereZigBee.UNIT_02.equals(((WhereZigBee) where).getUnit())) {
logger.debug("UNIT=02 found (WHERE={})", where);
logger.debug("will remove previous result if exists");
thingRemoved(thingUID); // remove previously discovered thing
// re-create thingUID with new type
thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS;
thingLabel = OpenWebNetBindingConstants.THING_LABEL_ZB_ON_OFF_SWITCH_2UNITS;
thingUID = new ThingUID(thingTypeUID, bridgeUID, tId);
whereLabel = whereLabel.replace("02#", "00#"); // replace unit '02' with all unit '00'
logger.debug("UNIT=02, switching type from {} to {}",
OpenWebNetBindingConstants.THING_TYPE_ZB_ON_OFF_SWITCH,
OpenWebNetBindingConstants.THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS);
}
Map<String, Object> properties = new HashMap<>(2);
properties.put(OpenWebNetBindingConstants.CONFIG_PROPERTY_WHERE, bridgeHandler.normalizeWhere(where));
properties.put(OpenWebNetBindingConstants.PROPERTY_OWNID, bridgeHandler.ownIdFromWhoWhere(where, deviceWho));
if (thingTypeUID == OpenWebNetBindingConstants.THING_TYPE_GENERIC_DEVICE) {
thingLabel = thingLabel + " (WHO=" + deviceWho + ", WHERE=" + whereLabel + ")";
} else {
thingLabel = thingLabel + " (WHERE=" + whereLabel + ")";
}
discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID).withProperties(properties)
.withRepresentationProperty(OpenWebNetBindingConstants.PROPERTY_OWNID).withBridge(bridgeUID)
.withLabel(thingLabel).build();
thingDiscovered(discoveryResult);
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof OpenWebNetBridgeHandler) {
logger.debug("attaching {} to handler {} ", this, handler);
bridgeHandler = (OpenWebNetBridgeHandler) handler;
bridgeHandler.deviceDiscoveryService = this;
bridgeUID = bridgeHandler.getThing().getUID();
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="openwebnet" 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>OpenWebNet (BTicino/Legrand) Binding</name>
<description>The OpenWebNet Binding integrates the BTicino/Legrand 'MyHOME' connected home system using the OpenWebNet
protocol. It supports BUS (SCS) and ZigBee USB gateways and devices.</description>
<author>Massimo Valla</author>
</binding:binding>

View File

@@ -0,0 +1,4 @@
# Thing status descriptions
offline.conf-error-no-ip-address = Cannot connect to gateway. No host/IP has been provided in Bridge configuration.
offline.conf-error-no-serial-port = Cannot connect to gateway. No serial port has been provided in Bridge configuration.
offline.wrong-configuration = Invalid configuration. Check Thing configuration parameters.

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for BUS Automation (BTicino xxx/xxx/...) -->
<thing-type id="bus_automation">
<supported-bridge-type-refs>
<bridge-type-ref id="bus_gateway"/>
</supported-bridge-type-refs>
<label>Automation</label>
<description>A OpenWebNet BUS/SCS automation device to control roller shutters, blinds, etc. BTicino models:
xxx/yyyy/etc.</description>
<channels>
<channel id="shutter" typeId="shutter"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-xxxx/yyyy/etc.</property>
<property name="ownDeviceType">514</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="shutterRun" type="text" required="true">
<label>Shutter Run</label>
<description>Time (in ms) to go from max position (e.g. CLOSED) to the other position (e.g. OPEN). Example: 12000
(=12sec).
Use AUTO (default) to calibrate the shutter automatically (UP->DOWN->Position%) the first time a Position
command (%)
is sent.</description>
<default>AUTO</default>
</parameter>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address (WHERE)</label>
<description>Example: A/PL address: A=1 PL=3 --> WHERE=13. On local bus: WHERE=13#4#01</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for Dimmer (BTicino yyyy/zzzz/...) -->
<thing-type id="bus_dimmer">
<supported-bridge-type-refs>
<bridge-type-ref id="bus_gateway"/>
</supported-bridge-type-refs>
<label>Dimmer</label>
<description>A OpenWebNet BUS/SCS dimmer for the dimmer control of 1 light. BTicino models: yyyy/zzzz/etc.</description>
<channels>
<channel id="brightness" typeId="brightness"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-yyyy/zzzz/etc.</property>
<property name="ownDeviceType">258</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address (WHERE)</label>
<description>Example: A/PL address: A=1 PL=3 --> WHERE=13. On local bus: WHERE=13#4#01</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- OpenWebNet BUS gateway -->
<bridge-type id="bus_gateway">
<label>BUS Gateway</label>
<description>This thing allows to connect to a IP BUS/SCS gateway that supports the OpenWebNet protocol (models: F454,
MyHOMEServer1, F455, MH200N, F453, etc.)</description>
<properties>
<property name="vendor">BTicino</property>
<property name="model">Unknown</property>
<property name="firmwareVersion">Unknown</property>
</properties>
<representation-property>serialNumber</representation-property>
<config-description>
<parameter name="host" type="text" required="true">
<context>network-address</context>
<label>Host</label>
<description>OpenWebNet gateway IP address / hostname (example: 192.168.1.35)</description>
</parameter>
<parameter name="port" type="integer" min="1" max="65535">
<label>Port</label>
<description>OpenWebNet gateway port (default: 20000)</description>
<default>20000</default>
</parameter>
<parameter name="passwd" type="text">
<context>password</context>
<label>Password</label>
<description>OpenWebNet gateway password (default: 12345)</description>
<default>12345</default>
</parameter>
<parameter name="discoveryByActivation" type="boolean">
<label>Discovery By Activation</label>
<description>Discover BUS devices when they are activated (also when a device scan is not active) (default: false)</description>
<default>false</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for BUS On Off Switch (BTicino xxx/xxx/...) -->
<thing-type id="bus_on_off_switch">
<supported-bridge-type-refs>
<bridge-type-ref id="bus_gateway"/>
</supported-bridge-type-refs>
<label>Switch</label>
<description>A OpenWebNet BUS/SCS switch for the control of 1 light/load. BTicino models: xxx/yyyy/etc.</description>
<channels>
<channel id="switch" typeId="switch"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-xxxx/yyyy/etc.</property>
<property name="ownDeviceType">261</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address (WHERE)</label>
<description>Example: A/PL address: A=1 PL=3 --> WHERE=13. On local bus: WHERE=13#4#01</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- OpenWebNet Generic Device -->
<thing-type id="device">
<supported-bridge-type-refs>
<bridge-type-ref id="bus_gateway"/>
<bridge-type-ref id="zb_gateway"/>
</supported-bridge-type-refs>
<label>Generic Device</label>
<description>An OpenWebNet Generic Device.</description>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">Unknown</property>
<property name="ownDeviceType">0</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address (WHERE)</label>
<description>It identifies one OpenWebNet device</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for Automation (BTicino xxx/xxx/...) -->
<thing-type id="zb_automation">
<supported-bridge-type-refs>
<bridge-type-ref id="zb_gateway"/>
</supported-bridge-type-refs>
<label>ZigBee Automation</label>
<description>A OpenWebNet ZigBee automation device to control roller shutters, blinds, etc. BTicino models:
xxx/yyyy/etc.</description>
<channels>
<channel id="shutter" typeId="shutter"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-xxxx/yyyy/etc.</property>
<property name="ownDeviceType">512</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="shutterRun" type="text">
<label>Shutter Run</label>
<description>Time (in ms) to go from max position (e.g. CLOSED) to the other position (e.g. OPEN). Example: 12000
(=12sec).
Use AUTO (default) to calibrate the shutter automatically (UP->DOWN->Position%) the first time a Position
command (%)
is sent.</description>
<required>true</required>
<default>AUTO</default>
</parameter>
<parameter name="where" type="text">
<label>OpenWebNet Device Address</label>
<description>It identifies one ZigBee device. Use decimal format address without the UNIT part and network: ZigBee
WHERE=414122201#9 -> OpenWebNet Device Address = 4141222</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for ZigBee Dimmer (BTicino 4585/4594/...) -->
<thing-type id="zb_dimmer">
<supported-bridge-type-refs>
<bridge-type-ref id="zb_gateway"/>
</supported-bridge-type-refs>
<label>ZigBee Dimmer</label>
<description>A OpenWebNet ZigBee dimmer for the dimmer control of 1 light. BTicino models: 4585/4594/etc.</description>
<channels>
<channel id="brightness" typeId="brightness"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-4585/4594/etc.</property>
<property name="ownDeviceType">258</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address</label>
<description>It identifies one ZigBee device. Use decimal format address without the UNIT part and network: ZigBee
WHERE=414122201#9 -> OpenWebNet Device Address = 4141222</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- OpenWebNet ZigBee USB Gateway -->
<bridge-type id="zb_gateway">
<label>ZigBee USB Gateway</label>
<description>This USB gateway (BTicino/Legrand models: BTI-3578/088328) connects to a BTicino/Legrand ZigBee network
and uses the OpenWebNet protocol. For more information see: https://catalogo.bticino.it/BTI-3578-IT and
https://www.legrand.com/ecatalogue/088328-openweb-net-zigbee-gateway-radio-interface.html</description>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-3578/088328</property>
<property name="firmwareVersion">Unknown</property>
</properties>
<representation-property>zigbeeid</representation-property>
<config-description>
<parameter name="serialPort" type="text" required="true">
<context>serial-port</context>
<label>Serial Port</label>
<description>Serial port where the OpenWebNet ZigBee USB Gateway is connected. Example: COM3 (Win), /dev/ttyUSB0
(Linux), etc.</description>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for OnOff Switch (BTicino 4591/3584/...) -->
<thing-type id="zb_on_off_switch">
<supported-bridge-type-refs>
<bridge-type-ref id="zb_gateway"/>
</supported-bridge-type-refs>
<label>ZigBee Switch</label>
<description>A OpenWebNet ZigBee switch (actuator) for the control of 1 load/light. BTicino models: 4591/3684/etc.</description>
<channels>
<channel id="switch_01" typeId="switch"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-4591/3684/etc.</property>
<property name="ownDeviceType">256</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address</label>
<description>It identifies one ZigBee device. Use decimal format address without the UNIT part and network: ZigBee
WHERE=414122201#9 -> OpenWebNet Device Address = 4141222</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Thing for ZigBee OnOff Switch with 2 units (BTicino 4592) -->
<thing-type id="zb_on_off_switch2u">
<supported-bridge-type-refs>
<bridge-type-ref id="zb_gateway"/>
</supported-bridge-type-refs>
<label>ZigBee 2-units Switch</label>
<description>A OpenWebNet ZigBee 2-units switch (actuator) for the control of 2 loads/lights. BTicino model: 4592</description>
<channels>
<channel id="switch_01" typeId="switch"/>
<channel id="switch_02" typeId="switch"/>
</channels>
<properties>
<property name="vendor">BTicino/Legrand</property>
<property name="model">BTI-4592</property>
<property name="ownDeviceType">256</property>
</properties>
<representation-property>ownId</representation-property>
<config-description>
<parameter name="where" type="text" required="true">
<label>OpenWebNet Device Address</label>
<description>It identifies one ZigBee device. Use decimal format address without the UNIT part and network: ZigBee
WHERE=414122201#9 -> OpenWebNet Device Address = 4141222</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="openwebnet"
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">
<!-- Switch Channel -->
<channel-type id="switch">
<item-type>Switch</item-type>
<label>Switch</label>
<description>Switch the power ON and OFF</description>
<category>Light</category>
<tags>
<tag>Lighting</tag>
</tags>
</channel-type>
<!-- Brightness Channel -->
<channel-type id="brightness">
<item-type>Dimmer</item-type>
<label>Brightness</label>
<description>Control the brightness and switch the light ON and OFF</description>
<category>DimmableLight</category>
<tags>
<tag>Lighting</tag>
</tags>
</channel-type>
<!-- Shutter Channel -->
<channel-type id="shutter">
<item-type>Rollershutter</item-type>
<label>Roller shutter</label>
<description>Control the roller shutter position</description>
<category>Blinds</category>
<tags>
<tag>Blinds</tag>
</tags>
</channel-type>
</thing:thing-descriptions>