]> git.basschouten.com Git - openhab-addons.git/commitdiff
[openwebnet] refactoring to internal package (#10995)
authorM Valla <12682715+mvalla@users.noreply.github.com>
Sat, 17 Jul 2021 20:40:58 +0000 (22:40 +0200)
committerGitHub <noreply@github.com>
Sat, 17 Jul 2021 20:40:58 +0000 (22:40 +0200)
Signed-off-by: Massimo Valla <mvcode00@gmail.com>
26 files changed:
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/OpenWebNetBindingConstants.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetAutomationHandler.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetBridgeHandler.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetEnergyHandler.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetGenericHandler.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetLightingHandler.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetThermoregulationHandler.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetThingHandler.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/config/OpenWebNetBusBridgeConfig.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/config/OpenWebNetZigBeeBridgeConfig.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetHandlerFactory.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/BusGatewayUpnpDiscovery.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/OpenWebNetDeviceDiscoveryService.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/UsbGatewayDiscoveryService.java
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetAutomationHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetBridgeHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetEnergyHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetGenericHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetLightingHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThermoregulationHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThingHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/config/OpenWebNetBusBridgeConfig.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/config/OpenWebNetZigBeeBridgeConfig.java [new file with mode: 0644]
bundles/org.openhab.binding.openwebnet/src/test/java/org/openhab/binding/openwebnet/handler/OwnIdTest.java [deleted file]
bundles/org.openhab.binding.openwebnet/src/test/java/org/openhab/binding/openwebnet/internal/handler/OwnIdTest.java [new file with mode: 0644]

diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/OpenWebNetBindingConstants.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/OpenWebNetBindingConstants.java
deleted file mode 100644 (file)
index b7fbf14..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.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
- * @author Andrea Conte - Energy management, Thermoregulation
- * @author Gilberto Cocchi - Thermoregulation
- */
-
-@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";
-    public static final ThingTypeUID THING_TYPE_BUS_ENERGY_METER = new ThingTypeUID(BINDING_ID, "bus_energy_meter");
-    public static final String THING_LABEL_BUS_ENERGY_METER = "Energy Meter";
-    public static final ThingTypeUID THING_TYPE_BUS_THERMO_SENSOR = new ThingTypeUID(BINDING_ID, "bus_thermo_sensor");
-    public static final String THING_LABEL_BUS_THERMO_SENSOR = "Thermo Sensor";
-    public static final ThingTypeUID THING_TYPE_BUS_THERMO_ZONE = new ThingTypeUID(BINDING_ID, "bus_thermo_zone");
-    public static final String THING_LABEL_BUS_THERMO_ZONE = "Thermo Zone";
-
-    // 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 = Set.of(THING_TYPE_GENERIC_DEVICE);
-    // ## Lighting
-    public static final Set<ThingTypeUID> LIGHTING_SUPPORTED_THING_TYPES = Set.of(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 = Set.of(THING_TYPE_ZB_AUTOMATION,
-            THING_TYPE_BUS_AUTOMATION);
-
-    // ## Thermoregulation
-    public static final Set<ThingTypeUID> THERMOREGULATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_THERMO_ZONE,
-            THING_TYPE_BUS_THERMO_SENSOR);
-
-    // ## Energy Management
-    public static final Set<ThingTypeUID> ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_ENERGY_METER);
-
-    // ## Groups
-    public static final Set<ThingTypeUID> DEVICE_SUPPORTED_THING_TYPES = Stream
-            .of(LIGHTING_SUPPORTED_THING_TYPES, AUTOMATION_SUPPORTED_THING_TYPES,
-                    THERMOREGULATION_SUPPORTED_THING_TYPES, ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES,
-                    GENERIC_SUPPORTED_THING_TYPES)
-            .flatMap(Collection::stream).collect(Collectors.toCollection(HashSet::new));
-
-    public static final Set<ThingTypeUID> BRIDGE_SUPPORTED_THING_TYPES = Set.of(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";
-
-    // thermo
-    public static final String CHANNEL_TEMPERATURE = "temperature";
-    public static final String CHANNEL_FUNCTION = "function";
-    public static final String CHANNEL_TEMP_SETPOINT = "setpointTemperature";
-    public static final String CHANNEL_MODE = "mode";
-    public static final String CHANNEL_FAN_SPEED = "speedFanCoil";
-    public static final String CHANNEL_CONDITIONING_VALVES = "conditioningValves";
-    public static final String CHANNEL_HEATING_VALVES = "heatingValves";
-    public static final String CHANNEL_ACTUATORS = "actuators";
-
-    // energy management
-    public static final String CHANNEL_POWER = "power";
-
-    // 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";
-    public static final String CONFIG_PROPERTY_SERIAL_PORT = "serialPort";
-
-    // 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";
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetAutomationHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetAutomationHandler.java
deleted file mode 100644 (file)
index dbf2661..0000000
+++ /dev/null
@@ -1,467 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.WhereLightAutom;
-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 DATE_FORMATTER = new SimpleDateFormat("ss.SSS");
-
-    private static long lastAllDevicesRefreshTS = -1; // timestamp when the last request for all device refresh was sent
-    protected static final int ALL_DEVICES_REFRESH_INTERVAL_MSEC = 60000; // interval in msec before sending another all
-                                                                          // devices refresh request
-
-    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 startedMovingAtTS = -1; // timestamp when device started moving UP/DOWN
-    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 Where buildBusWhere(String wStr) throws IllegalArgumentException {
-        return new WhereLightAutom(wStr);
-    }
-
-    @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);
-            }
-        } else {
-            logger.warn("Could not requestChannelState(): deviceWhere is null");
-        }
-    }
-
-    @Override
-    protected void refreshDevice(boolean refreshAll) {
-        OpenWebNetBridgeHandler brH = bridgeHandler;
-        if (brH != null) {
-            if (brH.isBusGateway() && refreshAll) {
-                long now = System.currentTimeMillis();
-                if (now - lastAllDevicesRefreshTS > ALL_DEVICES_REFRESH_INTERVAL_MSEC) {
-                    try {
-                        send(Automation.requestStatus(WhereLightAutom.GENERAL.value()));
-                        lastAllDevicesRefreshTS = now;
-                    } catch (OWNException e) {
-                        logger.warn("Excpetion while requesting all devices refresh: {}", e.getMessage());
-                    }
-                } else {
-                    logger.debug("Refresh all devices just sent...");
-                }
-            } else {
-                requestChannelState(new ChannelUID("any")); // channel here does not make any difference
-            }
-        }
-    }
-
-    @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) {
-        logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
-        updateAutomationState((Automation) msg);
-        // REMINDER: update automation state, and only after update thing status using 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 - startedMovingAtTS);
-                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;
-                Where dw = deviceWhere;
-                if (dw != null) {
-                    String w = dw.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
-                startedMovingAtTS = System.currentTimeMillis();
-                synchronized (DATE_FORMATTER) {
-                    logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAtTS,
-                            DATE_FORMATTER.format(new Date(startedMovingAtTS)));
-                }
-            }
-        } else { // we were moving
-            updatePosition();
-            if (newState != MOVING_STATE_STOPPED) { // moving after moving, take new timestamp
-                startedMovingAtTS = System.currentTimeMillis();
-                synchronized (DATE_FORMATTER) {
-                    logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAtTS,
-                            DATE_FORMATTER.format(new Date(startedMovingAtTS)));
-                }
-            }
-            // 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 && startedMovingAtTS != -1) {// we have shutterRun and startedMovingAtTS defined, let's
-                                                        // calculate new positionEstimation
-            long movedTime = System.currentTimeMillis() - startedMovingAtTS;
-            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();
-    }
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetBridgeHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetBridgeHandler.java
deleted file mode 100644 (file)
index 713b832..0000000
+++ /dev/null
@@ -1,623 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.PROPERTY_FIRMWARE_VERSION;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.PROPERTY_SERIAL_NO;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.THING_TYPE_ZB_GATEWAY;
-
-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.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.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.Thing;
-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.openhab.core.types.RefreshType;
-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.EnergyManagement;
-import org.openwebnet4j.message.FrameException;
-import org.openwebnet4j.message.GatewayMgmt;
-import org.openwebnet4j.message.Lighting;
-import org.openwebnet4j.message.OpenMessage;
-import org.openwebnet4j.message.Thermoregulation;
-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 OpenWebNetBridgeHandler} is responsible for handling communication with gateways and handling events.
- *
- * @author Massimo Valla - Initial contribution
- * @author Andrea Conte - Energy management, Thermoregulation
- * @author Gilberto Cocchi - Thermoregulation
- */
-@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
-
-    private static final int REFRESH_ALL_DEVICES_DELAY_MSEC = 500; // Delay to wait before sending all devices refresh
-                                                                   // request after a connect/reconnect
-
-    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<>();
-    private Map<String, Long> discoveringDevices = 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 @Nullable ScheduledFuture<?> refreshSchedule;
-
-    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 {}... ", gw);
-                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,
-                                    "@text/offline.comm-error-timeout");
-                        }
-                    }, GATEWAY_ONLINE_TIMEOUT_SEC, TimeUnit.SECONDS);
-                    logger.debug("bridge {} initialization completed", thing.getUID());
-                } 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 ZigBee USB 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 BUS 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 {
-            if (command instanceof RefreshType) {
-                refreshAllDevices();
-            } else {
-                logger.warn("Command or channel not supported: channel={} command={}", channelUID, command);
-            }
-        }
-    }
-
-    @Override
-    public Collection<ConfigStatusMessage> getConfigStatus() {
-        return Collections.emptyList();
-    }
-
-    @Override
-    public void handleRemoval() {
-        disconnectGateway();
-        super.handleRemoval();
-    }
-
-    @Override
-    public void dispose() {
-        ScheduledFuture<?> rSc = refreshSchedule;
-        if (rSc != null) {
-            rSc.cancel(true);
-        }
-        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", gw);
-                    return;
-                }
-                logger.info("------$$ STARTED active SEARCH for devices on bridge '{}'", thing.getUID());
-                try {
-                    gw.discoverDevices();
-                } catch (OWNException e) {
-                    logger.warn("------$$ OWNException while discovering devices on bridge '{}': {}", thing.getUID(),
-                            e.getMessage());
-                }
-            } else {
-                logger.debug("------$$ Searching devices on bridge '{}' already activated", thing.getUID());
-                return;
-            }
-        } else {
-            logger.warn("------$$ 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 bridge '{}'", thing.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("discoverByActivation: msg={}", baseMsg);
-        OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
-        if (discService == null) {
-            logger.warn("discoverByActivation: null OpenWebNetDeviceDiscoveryService, ignoring msg={}", baseMsg);
-            return;
-        }
-        // we support these types only
-        if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
-                || baseMsg instanceof Thermoregulation) {
-            BaseOpenMessage bmsg = baseMsg;
-            if (baseMsg instanceof Lighting) {
-                What what = baseMsg.getWhat();
-                if (Lighting.WhatLighting.OFF.equals(what)) { // skipping OFF msg: cannot distinguish dimmer/switch
-                    logger.debug("discoverByActivation: skipping OFF msg: cannot distinguish dimmer/switch");
-                    return;
-                }
-                if (Lighting.WhatLighting.ON.equals(what)) { // if not already done just now, request light status to
-                    // distinguish dimmer from switch
-                    if (discoveringDevices.containsKey(ownIdFromMessage(baseMsg))) {
-                        logger.debug(
-                                "discoverByActivation: we just requested status for this device and it's ON -> it's a switch");
-                    } else {
-                        OpenGateway gw = gateway;
-                        if (gw != null) {
-                            try {
-                                discoveringDevices.put(ownIdFromMessage(baseMsg),
-                                        Long.valueOf(System.currentTimeMillis()));
-                                gw.send(Lighting.requestStatus(baseMsg.getWhere().value()));
-                                return;
-                            } catch (OWNException e) {
-                                logger.warn("discoverByActivation: Exception while requesting light state: {}",
-                                        e.getMessage());
-                                return;
-                            }
-                        }
-                    }
-                }
-                discoveringDevices.remove(ownIdFromMessage(baseMsg));
-            }
-            OpenDeviceType type = null;
-            try {
-                type = bmsg.detectDeviceType();
-            } catch (FrameException e) {
-                logger.warn("Exception while detecting device type: {}", e.getMessage());
-            }
-            if (type != null) {
-                discService.newDiscoveryResult(bmsg.getWhere(), type, bmsg);
-            } else {
-                logger.debug("discoverByActivation: no device type detected from msg: {}", bmsg);
-            }
-        }
-    }
-
-    /**
-     * 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 ownId the device OpenWebNet id
-     */
-    protected void unregisterDevice(String ownId) {
-        if (registeredDevices.remove(ownId) != null) {
-            logger.debug("un-registered device ownId={}", ownId);
-        } else {
-            logger.warn("could not un-register ownId={} (not found)", ownId);
-        }
-    }
-
-    /**
-     * Get an already registered device on this bridge handler
-     *
-     * @param ownId the device OpenWebNet id
-     * @return the registered device Thing handler or null if the id cannot be found
-     */
-    public @Nullable OpenWebNetThingHandler getRegisteredDevice(String ownId) {
-        return registeredDevices.get(ownId);
-    }
-
-    private void refreshAllDevices() {
-        logger.debug("Refreshing all devices for bridge {}", thing.getUID());
-        for (Thing ownThing : getThing().getThings()) {
-            OpenWebNetThingHandler hndlr = (OpenWebNetThingHandler) ownThing.getHandler();
-            if (hndlr != null) {
-                hndlr.refreshDevice(true);
-            }
-        }
-    }
-
-    @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 || baseMsg instanceof EnergyManagement
-                || baseMsg instanceof Thermoregulation) {
-            String ownId = ownIdFromMessage(baseMsg);
-            logger.debug("ownIdFromMessage({}) --> {}", baseMsg, 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 ZigBee USB gateway bridge '{}' (serialPort: {})", thing.getUID(),
-                    ((USBGateway) gw).getSerialPortName());
-        } else {
-            logger.info("---- CONNECTED to BUS gateway bridge '{}' ({}:{})", 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 bridge '{}'", thing.getUID());
-        }
-        updateStatus(ThingStatus.ONLINE);
-        // schedule a refresh for all devices
-        refreshSchedule = scheduler.schedule(this::refreshAllDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
-                TimeUnit.MILLISECONDS);
-    }
-
-    @Override
-    public void onConnectionError(@Nullable OWNException error) {
-        String errMsg;
-        if (error == null) {
-            errMsg = "unknown error";
-        } else {
-            errMsg = error.getMessage();
-        }
-        logger.info("---- ON CONNECTION ERROR for gateway {}: {}", gateway, errMsg);
-        isGatewayConnected = false;
-        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                "@text/offline.comm-error-connection" + " (onConnectionError - " + 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: {}", gateway, errMsg);
-        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                "@text/offline.comm-error-disconnected" + " (onDisconnected - " + errMsg + ")");
-        tryReconnectGateway();
-    }
-
-    private void tryReconnectGateway() {
-        OpenGateway gw = gateway;
-        if (gw != null) {
-            if (!reconnecting) {
-                reconnecting = true;
-                logger.info("---- Starting RECONNECT cycle to gateway {}", gw);
-                try {
-                    gw.reconnect();
-                } catch (OWNAuthException e) {
-                    logger.info("---- AUTH error from gateway. Stopping re-connect");
-                    reconnecting = false;
-                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
-                            "@text/offline.conf-error-auth" + " (" + e + ")");
-                }
-            } else {
-                logger.debug("---- reconnecting=true");
-            }
-        } else {
-            logger.warn("---- cannot start RECONNECT, gateway is null");
-        }
-    }
-
-    @Override
-    public void onReconnected() {
-        reconnecting = false;
-        OpenGateway gw = gateway;
-        logger.info("---- RE-CONNECTED to bridge {}", thing.getUID());
-        if (gw != null) {
-            updateStatus(ThingStatus.ONLINE);
-            if (gw.getFirmwareVersion() != null) {
-                this.updateProperty(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
-                logger.debug("gw firmware version: {}", gw.getFirmwareVersion());
-            }
-
-            // schedule a refresh for all devices
-            refreshSchedule = scheduler.schedule(this::refreshAllDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
-                    TimeUnit.MILLISECONDS);
-        }
-    }
-
-    /**
-     * Return a ownId string (=WHO.WHERE) from the device Where address and handler
-     *
-     * @param where the Where address (to be normalized)
-     * @param handler the device handler
-     * @return the ownId String
-     */
-    protected String ownIdFromDeviceWhere(Where where, OpenWebNetThingHandler handler) {
-        return handler.ownIdPrefix() + "." + normalizeWhere(where);
-    }
-
-    /**
-     * Returns a ownId string (=WHO.WHERE) from a Who and Where address
-     *
-     * @param who the Who
-     * @param where the Where address (to be normalized)
-     * @return the ownId String
-     */
-    public String ownIdFromWhoWhere(Who who, Where where) {
-        return who.value() + "." + normalizeWhere(where);
-    }
-
-    /**
-     * Return a ownId string (=WHO.WHERE) from a BaseOpenMessage
-     *
-     * @param baseMsg the BaseOpenMessage
-     * @return the ownId String
-     */
-    public String ownIdFromMessage(BaseOpenMessage baseMsg) {
-        return baseMsg.getWho().value() + "." + normalizeWhere(baseMsg.getWhere());
-    }
-
-    /**
-     * Transform a Where address into a Thing id string
-     *
-     * @param where the Where address
-     * @return the thing Id string
-     */
-    public String thingIdFromWhere(Where where) {
-        return normalizeWhere(where); // '#' cannot be used in ThingUID;
-    }
-
-    /**
-     * Normalize a Where address
-     *
-     * @param where the Where address
-     * @return the normalized address as String
-     */
-    public String normalizeWhere(Where where) {
-        String str = where.value();
-        if (where instanceof WhereZigBee) {
-            str = ((WhereZigBee) where).valueWithUnit(WhereZigBee.UNIT_ALL); // 76543210X#9 --> 765432100#9
-        } else {
-            if (str.indexOf("#4#") == -1) { // skip APL#4#bus case
-                if (str.indexOf('#') == 0) { // Thermo central unit (#0) or zone via central unit (#Z, Z=[1-99]) --> Z
-                    str = str.substring(1);
-                } else if (str.indexOf('#') > 0) { // Thermo zone Z and actuator N (Z#N, Z=[1-99], N=[1-9]) --> Z
-                    str = str.substring(0, str.indexOf('#'));
-                }
-            }
-        }
-        return str.replace('#', 'h');
-    }
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetEnergyHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetEnergyHandler.java
deleted file mode 100644 (file)
index ff7060c..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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_POWER;
-
-import java.util.Set;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import javax.measure.quantity.Power;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.unit.Units;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusInfo;
-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.BaseOpenMessage;
-import org.openwebnet4j.message.EnergyManagement;
-import org.openwebnet4j.message.FrameException;
-import org.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereEnergyManagement;
-import org.openwebnet4j.message.Who;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link OpenWebNetEnergyHandler} is responsible for handling commands/messages for a Energy Management OpenWebNet
- * device. It extends the abstract {@link OpenWebNetThingHandler}.
- *
- * @author Massimo Valla - Initial contribution
- * @author Andrea Conte - Energy management
- */
-@NonNullByDefault
-public class OpenWebNetEnergyHandler extends OpenWebNetThingHandler {
-
-    private final Logger logger = LoggerFactory.getLogger(OpenWebNetEnergyHandler.class);
-
-    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES;
-    public static final int ENERGY_SUBSCRIPTION_PERIOD = 10; // minutes
-    private @Nullable ScheduledFuture<?> notificationSchedule;
-
-    public OpenWebNetEnergyHandler(Thing thing) {
-        super(thing);
-    }
-
-    public Boolean isFirstSchedulerLaunch = true;
-
-    @Override
-    public void initialize() {
-        super.initialize();
-
-        // In order to get data from the probe we must send a command over the bus, this could be done only when the
-        // bridge is online.
-        // a) usual flow: binding is starting, the bridge isn't online (startup flow not yet completed) --> subscriber
-        // can't be started here, it will be started inside the bridgeStatusChanged event.
-        // b) thing's discovery: binding is up and running, the bridge is online --> subscriber must be started here
-        // otherwise data will be read only after a reboot.
-
-        OpenWebNetBridgeHandler h = bridgeHandler;
-        if (h != null && h.isBusGateway()) {
-            OpenGateway gw = h.gateway;
-            if (gw != null && gw.isConnected()) {
-                // bridge is online
-                subscribeToActivePowerChanges();
-            }
-        }
-    }
-
-    @Override
-    public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
-        super.bridgeStatusChanged(bridgeStatusInfo);
-
-        // subscribe the scheduler only after the bridge is online
-        if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
-            subscribeToActivePowerChanges();
-        }
-    }
-
-    private void subscribeToActivePowerChanges() {
-        notificationSchedule = scheduler.scheduleWithFixedDelay(() -> {
-            if (isFirstSchedulerLaunch) {
-                logger.debug(
-                        "subscribeToActivePowerChanges() For WHERE={} subscribing to active power changes notification for the next {}min",
-                        deviceWhere, ENERGY_SUBSCRIPTION_PERIOD);
-            } else {
-                logger.debug(
-                        "subscribeToActivePowerChanges() Refreshing subscription for the next {}min for WHERE={} to active power changes notification",
-                        ENERGY_SUBSCRIPTION_PERIOD, deviceWhere);
-            }
-
-            try {
-                bridgeHandler.gateway.send(EnergyManagement.setActivePowerNotificationsTime(deviceWhere.value(),
-                        ENERGY_SUBSCRIPTION_PERIOD));
-                isFirstSchedulerLaunch = false;
-            } catch (Exception e) {
-                if (isFirstSchedulerLaunch) {
-                    logger.warn(
-                            "subscribeToActivePowerChanges() For WHERE={} could not subscribe to active power changes notifications. Exception={}",
-                            deviceWhere, e.getMessage());
-                } else {
-                    logger.warn(
-                            "subscribeToActivePowerChanges() Unable to refresh subscription to active power changes notifications for WHERE={}. Exception={}",
-                            deviceWhere, e.getMessage());
-                }
-            }
-        }, 0, ENERGY_SUBSCRIPTION_PERIOD - 1, TimeUnit.MINUTES);
-    }
-
-    @Override
-    public void dispose() {
-        if (notificationSchedule != null) {
-            logger.debug("dispose() scheduler stopped.");
-
-            notificationSchedule.cancel(false);
-        }
-        super.dispose();
-    }
-
-    @Override
-    protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
-        return new WhereEnergyManagement(wStr);
-    }
-
-    @Override
-    protected void requestChannelState(ChannelUID channel) {
-        logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
-        Where w = deviceWhere;
-        if (w != null) {
-            try {
-                send(EnergyManagement.requestActivePower(w.value()));
-            } catch (OWNException e) {
-                logger.debug("Exception while requesting channel {} state: {}", channel, e.getMessage(), e);
-            }
-        } else {
-            logger.warn("Could not requestChannelState(): deviceWhere is null");
-        }
-    }
-
-    @Override
-    protected void refreshDevice(boolean refreshAll) {
-        requestChannelState(new ChannelUID("any:any:any:any"));
-    }
-
-    @Override
-    protected void handleChannelCommand(ChannelUID channel, Command command) {
-        logger.warn("handleChannelCommand() Read only channel, unsupported command {}", command);
-    }
-
-    @Override
-    protected String ownIdPrefix() {
-        return Who.ENERGY_MANAGEMENT.value().toString();
-    }
-
-    @Override
-    protected void handleMessage(BaseOpenMessage msg) {
-        super.handleMessage(msg);
-
-        if (msg.isCommand()) {
-            logger.warn("handleMessage() Ignoring unsupported command for thing {}. Frame={}", getThing().getUID(),
-                    msg);
-            return;
-        } else {
-            // fix: check for correct DIM (ActivePower / 113)
-            if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.ACTIVE_POWER)) {
-                updateActivePower(msg);
-            } else {
-                logger.debug("handleMessage() Ignoring message {} because it's not related to active power value.",
-                        msg);
-            }
-        }
-    }
-
-    /**
-     * Updates energy power state based on a EnergyManagement message received from the OWN network
-     *
-     * @param msg the EnergyManagement message received
-     * @throws FrameException
-     */
-    private void updateActivePower(BaseOpenMessage msg) {
-        Integer activePower;
-        try {
-            activePower = Integer.parseInt(msg.getDimValues()[0]);
-            updateState(CHANNEL_POWER, new QuantityType<Power>(activePower, Units.WATT));
-        } catch (FrameException e) {
-            logger.warn("FrameException on frame {}: {}", msg, e.getMessage());
-            updateState(CHANNEL_POWER, UnDefType.UNDEF);
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetGenericHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetGenericHandler.java
deleted file mode 100644 (file)
index 374dd20..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereLightAutom;
-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("Generic: there are no channels");
-    }
-
-    @Override
-    protected void refreshDevice(boolean refreshAll) {
-        // do nothing
-        logger.warn("Generic: nothing to refresh");
-    }
-
-    @Override
-    protected void handleChannelCommand(ChannelUID channel, Command command) {
-        // do nothing
-        logger.warn("Generic: there are no channels");
-    }
-
-    @Override
-    protected String ownIdPrefix() {
-        return "G";
-    }
-
-    @Override
-    protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
-        return new WhereLightAutom(wStr);
-    }
-
-    @Override
-    protected void handleMessage(BaseOpenMessage msg) {
-        super.handleMessage(msg);
-        // do nothing
-        logger.warn("Generic: handleMessage() nothing to do!");
-    }
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetLightingHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetLightingHandler.java
deleted file mode 100644 (file)
index 554b4f0..0000000
+++ /dev/null
@@ -1,434 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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_BRIGHTNESS;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_SWITCH;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_SWITCH_01;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_SWITCH_02;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.THING_TYPE_BUS_DIMMER;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.THING_TYPE_ZB_DIMMER;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS;
-
-import java.util.Set;
-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.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.ThingStatus;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.types.Command;
-import org.openwebnet4j.communication.OWNException;
-import org.openwebnet4j.communication.Response;
-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.WhereLightAutom;
-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;
-
-    // interval to interpret ON as response to requestStatus
-    private static final int BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC = 250;
-
-    // time to wait before sending a statusRequest, to avoid repeated requests and ensure dimmer has reached its final
-    // level
-    private static final int BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC = 900;
-
-    private static final int UNKNOWN_STATE = 1000;
-
-    private static long lastAllDevicesRefreshTS = -1; // timestamp when the last request for all device refresh was sent
-                                                      // for this handler
-
-    protected static final int ALL_DEVICES_REFRESH_INTERVAL_MSEC = 60000; // interval in msec before sending another all
-                                                                          // devices refresh request
-
-    private long lastBrightnessChangeSentTS = 0; // timestamp when last brightness change was sent to the device
-
-    private long lastStatusRequestSentTS = 0; // timestamp when last status request was sent to the device
-
-    private int brightness = UNKNOWN_STATE; // current brightness percent value for this device
-
-    private int brightnessBeforeOff = UNKNOWN_STATE; // latest brightness before device was set to off
-
-    private int sw[] = { UNKNOWN_STATE, UNKNOWN_STATE }; // current switch(es) state
-
-    public OpenWebNetLightingHandler(Thing thing) {
-        super(thing);
-    }
-
-    @Override
-    protected void requestChannelState(ChannelUID channel) {
-        logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
-        requestStatus(channel.getId());
-    }
-
-    /** helper method to request light status based on channel */
-    private void requestStatus(String channelId) {
-        Where w = deviceWhere;
-        if (w != null) {
-            try {
-                lastStatusRequestSentTS = System.currentTimeMillis();
-                Response res = send(Lighting.requestStatus(toWhere(channelId)));
-                if (res != null && res.isSuccess()) {
-                    // set thing online, if not already
-                    ThingStatus ts = getThing().getStatus();
-                    if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
-                        updateStatus(ThingStatus.ONLINE);
-                    }
-                }
-            } catch (OWNException e) {
-                logger.warn("requestStatus() Exception while requesting light state: {}", e.getMessage());
-            }
-        } else {
-            logger.warn("Could not requestStatus(): deviceWhere is null");
-        }
-    }
-
-    @Override
-    protected void refreshDevice(boolean refreshAll) {
-        OpenWebNetBridgeHandler brH = bridgeHandler;
-        if (brH != null) {
-            if (brH.isBusGateway() && refreshAll) {
-                long now = System.currentTimeMillis();
-                if (now - lastAllDevicesRefreshTS > ALL_DEVICES_REFRESH_INTERVAL_MSEC) {
-                    try {
-                        send(Lighting.requestStatus(WhereLightAutom.GENERAL.value()));
-                        lastAllDevicesRefreshTS = now;
-                    } catch (OWNException e) {
-                        logger.warn("Excpetion while requesting all devices refresh: {}", e.getMessage());
-                    }
-                } else {
-                    logger.debug("Refresh all devices just sent...");
-                }
-            } else { // USB or BUS-single device
-                ThingTypeUID thingType = thing.getThingTypeUID();
-                if (THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS.equals(thingType)) {
-                    // Unfortunately using USB Gateway OpenWebNet both switch endpoints cannot be requested at the same
-                    // time using UNIT 00 because USB stick returns NACK, so we need to send a request status for both
-                    // endpoints
-                    requestStatus(CHANNEL_SWITCH_02);
-                }
-                requestStatus(""); // channel here does not make any difference, see {@link #toWhere()}
-            }
-        }
-    }
-
-    @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 ChannelUID {}", 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.getId())));
-                } else if (OnOffType.OFF.equals(command)) {
-                    send(Lighting.requestTurnOff(toWhere(channel.getId())));
-                }
-            } catch (OWNException e) {
-                logger.warn("Exception while processing command {}: {}", command, e.getMessage());
-            }
-        } else {
-            logger.warn("Unsupported command: {}", command);
-        }
-    }
-
-    /**
-     * Handles Lighting brightness command (xx%, INCREASE, DECREASE, ON, OFF)
-     *
-     * @param command the command
-     */
-    private void handleBrightnessCommand(Command command) {
-        logger.debug("handleBrightnessCommand() command={}", command);
-        if (command instanceof PercentType) {
-            dimLightTo(((PercentType) command).intValue(), command);
-        } else if (command instanceof IncreaseDecreaseType) {
-            if (IncreaseDecreaseType.INCREASE.equals(command)) {
-                dimLightTo(brightness + 10, command);
-            } else { // DECREASE
-                dimLightTo(brightness - 10, command);
-            }
-        } else if (command instanceof OnOffType) {
-            if (OnOffType.ON.equals(command)) {
-                dimLightTo(brightnessBeforeOff, command);
-            } else { // OFF
-                dimLightTo(0, command);
-            }
-        } else {
-            logger.warn("Cannot handle command {} for thing {}", command, getThing().getUID());
-        }
-    }
-
-    /**
-     * Helper method to dim light to given percent
-     */
-    private void dimLightTo(int percent, Command command) {
-        logger.debug("   DIM dimLightTo({}) bri={} briBeforeOff={}", percent, brightness, brightnessBeforeOff);
-        int newBrightness = percent;
-        if (newBrightness == UNKNOWN_STATE) {
-            // we do not know last brightness -> set dimmer to 100%
-            newBrightness = 100;
-        } else if (newBrightness <= 0) {
-            newBrightness = 0;
-            brightnessBeforeOff = brightness;
-            logger.debug("   DIM saved bri before sending bri=0 command to device");
-        } else if (newBrightness > 100) {
-            newBrightness = 100;
-        }
-        What newBrightnessWhat = Lighting.percentToWhat(newBrightness);
-        logger.debug("   DIM newBrightness={} newBrightnessWhat={}", newBrightness, newBrightnessWhat);
-        @Nullable
-        What brightnessWhat = null;
-        if (brightness != UNKNOWN_STATE) {
-            brightnessWhat = Lighting.percentToWhat(brightness);
-        }
-        if (brightnessWhat == null || !newBrightnessWhat.value().equals(brightnessWhat.value())) {
-            logger.debug("   DIM brightnessWhat {} --> {}  WHAT level change needed", brightnessWhat,
-                    newBrightnessWhat);
-            Where w = deviceWhere;
-            if (w != null) {
-                try {
-                    lastBrightnessChangeSentTS = System.currentTimeMillis();
-                    send(Lighting.requestDimTo(w.value(), newBrightnessWhat));
-                } catch (OWNException e) {
-                    logger.warn("Exception while sending dimTo request for command {}: {}", command, e.getMessage());
-                }
-            }
-        } else {
-            logger.debug("   DIM brightnessWhat {} --> {}  NO WHAT level change needed", brightnessWhat,
-                    newBrightnessWhat);
-        }
-        brightness = newBrightness;
-        updateState(CHANNEL_BRIGHTNESS, new PercentType(brightness));
-        logger.debug("   DIM---END bri={} briBeforeOff={}", brightness, brightnessBeforeOff);
-    }
-
-    @Override
-    protected String ownIdPrefix() {
-        return Who.LIGHTING.value().toString();
-    }
-
-    @Override
-    protected void handleMessage(BaseOpenMessage msg) {
-        logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
-        super.handleMessage(msg);
-        ThingTypeUID thingType = thing.getThingTypeUID();
-        if (THING_TYPE_ZB_DIMMER.equals(thingType) || THING_TYPE_BUS_DIMMER.equals(thingType)) {
-            updateBrightness((Lighting) msg);
-        } else {
-            updateOnOffState((Lighting) msg);
-        }
-    }
-
-    /**
-     * Updates brightness based on OWN Lighting message received
-     *
-     * @param msg the Lighting message received
-     */
-    private synchronized void updateBrightness(Lighting msg) {
-        logger.debug("  $BRI updateBrightness({})       || bri={} briBeforeOff={}", msg, brightness,
-                brightnessBeforeOff);
-        long now = System.currentTimeMillis();
-        long delta = now - lastBrightnessChangeSentTS;
-        boolean belowThresh = delta < BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC;
-        logger.debug("  $BRI delta={}ms {}", delta, (belowThresh ? "< DELAY" : ""));
-        if (belowThresh) {
-            // we just sent a command from OH, so we can ignore this message from network
-            logger.debug("  $BRI a command was sent {} < {} ms --> no action needed", delta,
-                    BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC);
-        } else {
-            if (msg.isOn()) {
-                // if we have not just sent a requestStatus, on ON event we send requestStatus to know current level
-                long deltaStatusReq = now - lastStatusRequestSentTS;
-                if (deltaStatusReq > BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC) {
-                    logger.debug("  $BRI 'ON' is new notification from network, scheduling requestStatus...");
-                    // we must wait BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC to be sure dimmer has reached its final level
-                    scheduler.schedule(() -> {
-                        requestStatus(CHANNEL_BRIGHTNESS);
-                    }, BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC, TimeUnit.MILLISECONDS);
-                    return;
-                } else {
-                    // otherwise we interpret this ON event as the requestStatus response event with level=1
-                    // so we proceed to call updateBrightnessState()
-                    logger.debug("  $BRI 'ON' is the requestStatus response level");
-                }
-            }
-            logger.debug("  $BRI update from network");
-            if (msg.getWhat() != null) {
-                updateBrightnessState(msg);
-            } else { // dimension notification
-                if (msg.getDim() == Lighting.DimLighting.DIMMER_LEVEL_100) {
-                    int newBrightness;
-                    try {
-                        newBrightness = msg.parseDimmerLevel100();
-                    } catch (FrameException fe) {
-                        logger.warn("updateBrightness() Wrong value for dimmerLevel100 in message: {}", msg);
-                        return;
-                    }
-                    logger.debug("  $BRI DIMMER_LEVEL_100 newBrightness={}", newBrightness);
-                    updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
-                    if (newBrightness == 0) {
-                        brightnessBeforeOff = brightness;
-                    }
-                    brightness = newBrightness;
-                } else {
-                    logger.warn("updateBrightness() Cannot handle message {} for thing {}", msg, getThing().getUID());
-                    return;
-                }
-            }
-        }
-        logger.debug("  $BRI---END updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
-                brightnessBeforeOff);
-    }
-
-    /**
-     * Updates light brightness state based on a OWN Lighting message
-     *
-     * @param msg the Lighting message received
-     */
-    private void updateBrightnessState(Lighting msg) {
-        What w = msg.getWhat();
-        if (w != null) {
-            if (Lighting.WhatLighting.ON.equals(w)) {
-                w = Lighting.WhatLighting.DIMMER_LEVEL_2; // levels start at 2
-            }
-            int newBrightnessWhat = w.value();
-            int brightnessWhat = UNKNOWN_STATE;
-            if (brightness != UNKNOWN_STATE) {
-                brightnessWhat = Lighting.percentToWhat(brightness).value();
-            }
-            logger.debug("  $BRI brightnessWhat {} --> {}", brightnessWhat, newBrightnessWhat);
-            if (brightnessWhat != newBrightnessWhat) {
-                int newBrightness = Lighting.levelToPercent(newBrightnessWhat);
-                updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
-                if (msg.isOff()) {
-                    brightnessBeforeOff = brightness;
-                }
-                brightness = newBrightness;
-                logger.debug("  $BRI brightness CHANGED to {}", brightness);
-            } else {
-                logger.debug("  $BRI no change");
-            }
-        }
-    }
-
-    /**
-     * Updates light on/off state based on a OWN Lighting event message received
-     *
-     * @param msg the Lighting message received
-     */
-    private void updateOnOffState(Lighting msg) {
-        OpenWebNetBridgeHandler brH = bridgeHandler;
-        if (brH != null) {
-            if (msg.isOn() || msg.isOff()) {
-                String channelId;
-                int switchId = 0;
-                if (brH.isBusGateway()) {
-                    channelId = CHANNEL_SWITCH;
-                } else {
-                    WhereZigBee w = (WhereZigBee) (msg.getWhere());
-                    if (WhereZigBee.UNIT_02.equals(w.getUnit())) {
-                        channelId = CHANNEL_SWITCH_02;
-                        switchId = 1;
-                    } else {
-                        channelId = CHANNEL_SWITCH_01;
-                    }
-                }
-                int currentSt = sw[switchId];
-                int newSt = (msg.isOn() ? 1 : 0);
-                if (newSt != currentSt) {
-                    updateState(channelId, (newSt == 1 ? OnOffType.ON : OnOffType.OFF));
-                    sw[switchId] = newSt;
-                    logger.debug("  {} ONOFF CHANGED to {}", ownId, newSt);
-                } else {
-                    logger.debug("  {} ONOFF no change", ownId);
-                }
-            } else {
-                logger.debug("updateOnOffState() Ignoring unsupported WHAT for thing {}. Frame={}", getThing().getUID(),
-                        msg.getFrameValue());
-                return;
-            }
-        }
-    }
-
-    @Override
-    protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
-        return new WhereLightAutom(wStr);
-    }
-
-    /**
-     * Returns a WHERE address string based on channelId string
-     *
-     * @param channelId the channelId string
-     **/
-    @Nullable
-    private String toWhere(String channelId) {
-        Where w = deviceWhere;
-        if (w != null) {
-            OpenWebNetBridgeHandler brH = bridgeHandler;
-            if (brH != null) {
-                if (brH.isBusGateway()) {
-                    return w.value();
-                } else if (channelId.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;
-    }
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetThermoregulationHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetThermoregulationHandler.java
deleted file mode 100644 (file)
index b540a4b..0000000
+++ /dev/null
@@ -1,337 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.openhab.binding.openwebnet.OpenWebNetBindingConstants;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.library.unit.SIUnits;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusInfo;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.UnDefType;
-import org.openwebnet4j.communication.OWNException;
-import org.openwebnet4j.message.BaseOpenMessage;
-import org.openwebnet4j.message.FrameException;
-import org.openwebnet4j.message.MalformedFrameException;
-import org.openwebnet4j.message.Thermoregulation;
-import org.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereThermo;
-import org.openwebnet4j.message.Who;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link OpenWebNetThermoregulationHandler} is responsible for handling commands/messages for Thermoregulation
- * Things. It extends the abstract {@link OpenWebNetThingHandler}.
- *
- * @author Massimo Valla - Initial contribution
- * @author Andrea Conte - Thermoregulation
- * @author Gilberto Cocchi - Thermoregulation
- */
-@NonNullByDefault
-public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
-
-    private final Logger logger = LoggerFactory.getLogger(OpenWebNetThermoregulationHandler.class);
-
-    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.THERMOREGULATION_SUPPORTED_THING_TYPES;
-
-    private boolean isTempSensor = false; // is the thing a sensor ?
-
-    private double currentSetPointTemp = 11.5d; // 11.5 is the default setTemp used in MyHomeUP mobile app
-
-    private Thermoregulation.Function currentFunction = Thermoregulation.Function.GENERIC;
-
-    public OpenWebNetThermoregulationHandler(Thing thing) {
-        super(thing);
-    }
-
-    @Override
-    public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
-        super.bridgeStatusChanged(bridgeStatusInfo);
-        // when the bridge is ONLINE request for thing states (temp, setTemp, fanSpeed...)
-        if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
-            refreshDevice(false);
-        }
-    }
-
-    @Override
-    protected void handleChannelCommand(ChannelUID channel, Command command) {
-        switch (channel.getId()) {
-            case CHANNEL_TEMP_SETPOINT:
-                handleSetpoint(command);
-                break;
-            case CHANNEL_FUNCTION:
-                handleFunction(command);
-                break;
-            case CHANNEL_MODE:
-                handleMode(command);
-                break;
-            case CHANNEL_FAN_SPEED:
-                handleSetFanSpeed(command);
-                break;
-            default: {
-                logger.warn("handleChannelCommand() Unsupported ChannelUID {}", channel.getId());
-            }
-        }
-    }
-
-    @Override
-    protected void requestChannelState(ChannelUID channel) {
-        refreshDevice(false);
-    }
-
-    @Override
-    protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
-        WhereThermo wt = new WhereThermo(wStr);
-        if (wt.isProbe()) {
-            isTempSensor = true;
-        }
-        return wt;
-    }
-
-    @Override
-    protected String ownIdPrefix() {
-        return Who.THERMOREGULATION.value().toString();
-    }
-
-    private void handleSetFanSpeed(Command command) {
-        if (command instanceof StringType) {
-            Where w = deviceWhere;
-            if (w != null) {
-                try {
-                    Thermoregulation.FanCoilSpeed speed = Thermoregulation.FanCoilSpeed.valueOf(command.toString());
-                    send(Thermoregulation.requestWriteFanCoilSpeed(w.value(), speed));
-                } catch (OWNException e) {
-                    logger.warn("handleSetFanSpeed() {}", e.getMessage());
-                } catch (IllegalArgumentException e) {
-                    logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command,
-                            getThing().getUID());
-                    return;
-                }
-            }
-        } else {
-            logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command, getThing().getUID());
-        }
-    }
-
-    private void handleSetpoint(Command command) {
-        if (command instanceof QuantityType || command instanceof DecimalType) {
-            Where w = deviceWhere;
-            if (w != null) {
-                double newTemp = 0;
-                if (command instanceof QuantityType) {
-                    QuantityType<?> tempCelsius = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
-                    if (tempCelsius != null) {
-                        newTemp = tempCelsius.doubleValue();
-                    }
-                } else {
-                    newTemp = ((DecimalType) command).doubleValue();
-                }
-                try {
-                    send(Thermoregulation.requestWriteSetpointTemperature(w.value(), newTemp, currentFunction));
-                } catch (MalformedFrameException | OWNException e) {
-                    logger.warn("handleSetpoint() {}", e.getMessage());
-                }
-            }
-        } else {
-            logger.warn("handleSetpoint() Unsupported command {} for thing {}", command, getThing().getUID());
-        }
-    }
-
-    private void handleMode(Command command) {
-        if (command instanceof StringType) {
-            Where w = deviceWhere;
-            if (w != null) {
-                try {
-                    Thermoregulation.OperationMode mode = Thermoregulation.OperationMode.valueOf(command.toString());
-                    send(Thermoregulation.requestWriteMode(w.value(), mode, currentFunction, currentSetPointTemp));
-                } catch (OWNException e) {
-                    logger.warn("handleMode() {}", e.getMessage());
-                } catch (IllegalArgumentException e) {
-                    logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
-                    return;
-                }
-            }
-        } else {
-            logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
-        }
-    }
-
-    private void handleFunction(Command command) {
-        if (command instanceof StringType) {
-            Where w = deviceWhere;
-            if (w != null) {
-                try {
-                    Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
-                    send(Thermoregulation.requestWriteFunction(w.value(), function));
-                } catch (OWNException e) {
-                    logger.warn("handleFunction() {}", e.getMessage());
-                } catch (IllegalArgumentException e) {
-                    logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
-                    return;
-                }
-            }
-        } else {
-            logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
-        }
-    }
-
-    @Override
-    protected void handleMessage(BaseOpenMessage msg) {
-        super.handleMessage(msg);
-        if (msg.isCommand()) {
-            updateModeAndFunction((Thermoregulation) msg);
-        } else {
-            if (msg.getDim() == null) {
-                return;
-            }
-            if (msg.getDim() == Thermoregulation.DimThermo.TEMPERATURE
-                    || msg.getDim() == Thermoregulation.DimThermo.PROBE_TEMPERATURE) {
-                updateTemperature((Thermoregulation) msg);
-            } else if (msg.getDim() == Thermoregulation.DimThermo.TEMP_SETPOINT
-                    || msg.getDim() == Thermoregulation.DimThermo.COMPLETE_PROBE_STATUS) {
-                updateSetpoint((Thermoregulation) msg);
-            } else if (msg.getDim() == Thermoregulation.DimThermo.VALVES_STATUS) {
-                updateValveStatus((Thermoregulation) msg);
-            } else if (msg.getDim() == Thermoregulation.DimThermo.ACTUATOR_STATUS) {
-                updateActuatorStatus((Thermoregulation) msg);
-            } else if (msg.getDim() == Thermoregulation.DimThermo.FAN_COIL_SPEED) {
-                updateFanCoilSpeed((Thermoregulation) msg);
-            } else {
-                logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", msg.getDim(),
-                        getThing().getUID(), msg);
-            }
-        }
-    }
-
-    private void updateModeAndFunction(Thermoregulation tmsg) {
-        if (tmsg.getWhat() == null) {
-            logger.debug("updateModeAndFunction() Could not parse Mode or Function from {} (what is null)",
-                    tmsg.getFrameValue());
-            return;
-        }
-
-        Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
-
-        if (w.mode() == null) {
-            logger.debug("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
-            return;
-        }
-        if (w.function() == null) {
-            logger.debug("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
-            return;
-        }
-
-        Thermoregulation.OperationMode mode = w.mode();
-        Thermoregulation.Function function = w.function();
-
-        if (w == Thermoregulation.WhatThermo.HEATING) {
-            function = Thermoregulation.Function.HEATING;
-        } else if (w == Thermoregulation.WhatThermo.CONDITIONING) {
-            function = Thermoregulation.Function.COOLING;
-        }
-
-        updateState(CHANNEL_MODE, new StringType(mode.toString()));
-        updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
-
-        // store current function
-        currentFunction = function;
-    }
-
-    private void updateTemperature(Thermoregulation tmsg) {
-        try {
-            double temp = Thermoregulation.parseTemperature(tmsg);
-            updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
-        } catch (FrameException e) {
-            logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
-            updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
-        }
-    }
-
-    private void updateSetpoint(Thermoregulation tmsg) {
-        try {
-            double temp = Thermoregulation.parseTemperature(tmsg);
-            updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
-            currentSetPointTemp = temp;
-        } catch (FrameException e) {
-            logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
-            updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
-        }
-    }
-
-    private void updateFanCoilSpeed(Thermoregulation tmsg) {
-        try {
-            Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
-            updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
-        } catch (FrameException e) {
-            logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
-            updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
-        }
-    }
-
-    private void updateValveStatus(Thermoregulation tmsg) {
-        try {
-            Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
-                    Thermoregulation.WhatThermo.CONDITIONING);
-            updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
-
-            Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
-                    Thermoregulation.WhatThermo.HEATING);
-            updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
-        } catch (FrameException e) {
-            logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
-            updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
-            updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
-        }
-    }
-
-    private void updateActuatorStatus(Thermoregulation tmsg) {
-        try {
-            Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
-            updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
-        } catch (FrameException e) {
-            logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
-            updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
-        }
-    }
-
-    @Override
-    protected void refreshDevice(boolean refreshAll) {
-        if (deviceWhere != null) {
-            String w = deviceWhere.value();
-            try {
-                send(Thermoregulation.requestTemperature(w));
-                if (!this.isTempSensor) {
-                    // for bus_thermo_zone request also other single channels updates
-                    send(Thermoregulation.requestSetPointTemperature(w));
-                    send(Thermoregulation.requestFanCoilSpeed(w));
-                    send(Thermoregulation.requestMode(w));
-                    send(Thermoregulation.requestValvesStatus(w));
-                    send(Thermoregulation.requestActuatorsStatus(w));
-                }
-            } catch (OWNException e) {
-                logger.warn("refreshDevice() where='{}' returned OWNException {}", w, e.getMessage());
-            }
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetThingHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetThingHandler.java
deleted file mode 100644 (file)
index 1ff15ef..0000000
+++ /dev/null
@@ -1,247 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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 javax.measure.Quantity;
-import javax.measure.Unit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.library.types.QuantityType;
-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.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-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.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;
-
-                final String configDeviceWhere = (String) getConfig().get(CONFIG_PROPERTY_WHERE);
-                if (configDeviceWhere == null) {
-                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
-                            "@text/offline.conf-error-where");
-                } else {
-                    Where w;
-                    try {
-                        if (brH.isBusGateway()) {
-                            w = buildBusWhere(configDeviceWhere);
-                        } else {
-                            w = new WhereZigBee(configDeviceWhere);
-                        }
-                    } catch (IllegalArgumentException ia) {
-                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
-                                "@text/offline.conf-error-where");
-                        return;
-                    }
-                    deviceWhere = w;
-                    final String oid = brH.ownIdFromDeviceWhere(w, 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, "@text/unknown.waiting-state");
-                }
-            }
-        } else {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
-                    "@text/offline.conf-error-no-bridge");
-        }
-    }
-
-    @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", command, getThing().getUID());
-                return;
-            }
-            if (deviceWhere == null) {
-                logger.info("Cannot handle {} command for {}: 'where' parameter is not configured or is invalid",
-                        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 ThingHandlers
-     */
-    protected @Nullable Response send(OpenMessage msg) throws OWNException {
-        OpenWebNetBridgeHandler bh = bridgeHandler;
-        if (bh != null) {
-            OpenGateway gw = bh.gateway;
-            if (gw != null) {
-                return gw.send(msg);
-            }
-        }
-        logger.warn("Couldn't send message {}: handler or gateway is null", 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 the state for the specified channel
-     *
-     * @param channel the {@link ChannelUID} to request the state for
-     */
-    protected abstract void requestChannelState(ChannelUID channel);
-
-    /**
-     * Refresh the device
-     *
-     * @param refreshAll set true if all devices of the binding should be refreshed with one command, if possible
-     */
-    protected abstract void refreshDevice(boolean refreshAll);
-
-    /**
-     * Abstract builder for device Where address, to be implemented by each subclass to choose the right Where subclass
-     * (the method is used only if the Thing is associated to a BUS gateway).
-     *
-     * @param wStr the WHERE string
-     */
-    protected abstract Where buildBusWhere(String wStr) throws IllegalArgumentException;
-
-    @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();
-    }
-
-    /**
-     * Helper method to return a Quantity from a Number value or UnDefType.NULL if value is null
-     *
-     * @param value to be used
-     * @param unit to be used
-     * @return Quantity
-     */
-    protected <U extends Quantity<U>> State getAsQuantityTypeOrNull(@Nullable Number value, Unit<U> unit) {
-        return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
-    }
-
-    /**
-     * Returns a prefix String for ownId specific for each handler. To be implemented by sub-classes.
-     *
-     * @return
-     */
-    protected abstract String ownIdPrefix();
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/config/OpenWebNetBusBridgeConfig.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/config/OpenWebNetBusBridgeConfig.java
deleted file mode 100644 (file)
index a9e013d..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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;
-    }
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/config/OpenWebNetZigBeeBridgeConfig.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/config/OpenWebNetZigBeeBridgeConfig.java
deleted file mode 100644 (file)
index 4481405..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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;
-    }
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetBindingConstants.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetBindingConstants.java
new file mode 100644 (file)
index 0000000..1f039a0
--- /dev/null
@@ -0,0 +1,143 @@
+/**
+ * Copyright (c) 2010-2021 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 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
+ * @author Andrea Conte - Energy management, Thermoregulation
+ * @author Gilberto Cocchi - Thermoregulation
+ */
+
+@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";
+    public static final ThingTypeUID THING_TYPE_BUS_ENERGY_METER = new ThingTypeUID(BINDING_ID, "bus_energy_meter");
+    public static final String THING_LABEL_BUS_ENERGY_METER = "Energy Meter";
+    public static final ThingTypeUID THING_TYPE_BUS_THERMO_SENSOR = new ThingTypeUID(BINDING_ID, "bus_thermo_sensor");
+    public static final String THING_LABEL_BUS_THERMO_SENSOR = "Thermo Sensor";
+    public static final ThingTypeUID THING_TYPE_BUS_THERMO_ZONE = new ThingTypeUID(BINDING_ID, "bus_thermo_zone");
+    public static final String THING_LABEL_BUS_THERMO_ZONE = "Thermo Zone";
+
+    // 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 = Set.of(THING_TYPE_GENERIC_DEVICE);
+    // ## Lighting
+    public static final Set<ThingTypeUID> LIGHTING_SUPPORTED_THING_TYPES = Set.of(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 = Set.of(THING_TYPE_ZB_AUTOMATION,
+            THING_TYPE_BUS_AUTOMATION);
+
+    // ## Thermoregulation
+    public static final Set<ThingTypeUID> THERMOREGULATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_THERMO_ZONE,
+            THING_TYPE_BUS_THERMO_SENSOR);
+
+    // ## Energy Management
+    public static final Set<ThingTypeUID> ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_ENERGY_METER);
+
+    // ## Groups
+    public static final Set<ThingTypeUID> DEVICE_SUPPORTED_THING_TYPES = Stream
+            .of(LIGHTING_SUPPORTED_THING_TYPES, AUTOMATION_SUPPORTED_THING_TYPES,
+                    THERMOREGULATION_SUPPORTED_THING_TYPES, ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES,
+                    GENERIC_SUPPORTED_THING_TYPES)
+            .flatMap(Collection::stream).collect(Collectors.toCollection(HashSet::new));
+
+    public static final Set<ThingTypeUID> BRIDGE_SUPPORTED_THING_TYPES = Set.of(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";
+
+    // thermo
+    public static final String CHANNEL_TEMPERATURE = "temperature";
+    public static final String CHANNEL_FUNCTION = "function";
+    public static final String CHANNEL_TEMP_SETPOINT = "setpointTemperature";
+    public static final String CHANNEL_MODE = "mode";
+    public static final String CHANNEL_FAN_SPEED = "speedFanCoil";
+    public static final String CHANNEL_CONDITIONING_VALVES = "conditioningValves";
+    public static final String CHANNEL_HEATING_VALVES = "heatingValves";
+    public static final String CHANNEL_ACTUATORS = "actuators";
+
+    // energy management
+    public static final String CHANNEL_POWER = "power";
+
+    // 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";
+    public static final String CONFIG_PROPERTY_SERIAL_PORT = "serialPort";
+
+    // 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";
+}
index 0d4205d36c7f7b6ce6b34f1b26836256d8361742..8b258df58c8d341494941770ee12c818375374b6 100644 (file)
  */
 package org.openhab.binding.openwebnet.internal;
 
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.ALL_SUPPORTED_THING_TYPES;
+import static org.openhab.binding.openwebnet.internal.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.OpenWebNetEnergyHandler;
-import org.openhab.binding.openwebnet.handler.OpenWebNetGenericHandler;
-import org.openhab.binding.openwebnet.handler.OpenWebNetLightingHandler;
-import org.openhab.binding.openwebnet.handler.OpenWebNetThermoregulationHandler;
+import org.openhab.binding.openwebnet.internal.handler.OpenWebNetAutomationHandler;
+import org.openhab.binding.openwebnet.internal.handler.OpenWebNetBridgeHandler;
+import org.openhab.binding.openwebnet.internal.handler.OpenWebNetEnergyHandler;
+import org.openhab.binding.openwebnet.internal.handler.OpenWebNetGenericHandler;
+import org.openhab.binding.openwebnet.internal.handler.OpenWebNetLightingHandler;
+import org.openhab.binding.openwebnet.internal.handler.OpenWebNetThermoregulationHandler;
 import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingTypeUID;
index a2bd6a66fcac6c398d3f4eaab3fffe7686193a37..2190cc9cb25ab665410cb918bc4c2b3b1ddc6f51 100644 (file)
@@ -27,7 +27,7 @@ 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.binding.openwebnet.internal.OpenWebNetBindingConstants;
 import org.openhab.core.config.discovery.DiscoveryResult;
 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
 import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
index e6c58cf0a204f092263b3889919c178b00595807..2a339cca5fea530281d075601eec2b98384ed4f6 100644 (file)
@@ -18,8 +18,8 @@ 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.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.binding.openwebnet.internal.handler.OpenWebNetBridgeHandler;
 import org.openhab.core.config.discovery.AbstractDiscoveryService;
 import org.openhab.core.config.discovery.DiscoveryResult;
 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
index 6bc1b8d36a38b20c7fa9b9969a03cf1f5274b816..a56d0d928c49f002df3bd55c7bfe75fc3d0bd3ef 100644 (file)
@@ -22,7 +22,7 @@ import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
 import org.openhab.core.config.discovery.AbstractDiscoveryService;
 import org.openhab.core.config.discovery.DiscoveryResult;
 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetAutomationHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetAutomationHandler.java
new file mode 100644 (file)
index 0000000..96ba3b1
--- /dev/null
@@ -0,0 +1,467 @@
+/**
+ * Copyright (c) 2010-2021 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.handler;
+
+import static org.openhab.binding.openwebnet.internal.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.internal.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.WhereLightAutom;
+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 DATE_FORMATTER = new SimpleDateFormat("ss.SSS");
+
+    private static long lastAllDevicesRefreshTS = -1; // timestamp when the last request for all device refresh was sent
+    protected static final int ALL_DEVICES_REFRESH_INTERVAL_MSEC = 60000; // interval in msec before sending another all
+                                                                          // devices refresh request
+
+    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 startedMovingAtTS = -1; // timestamp when device started moving UP/DOWN
+    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 Where buildBusWhere(String wStr) throws IllegalArgumentException {
+        return new WhereLightAutom(wStr);
+    }
+
+    @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);
+            }
+        } else {
+            logger.warn("Could not requestChannelState(): deviceWhere is null");
+        }
+    }
+
+    @Override
+    protected void refreshDevice(boolean refreshAll) {
+        OpenWebNetBridgeHandler brH = bridgeHandler;
+        if (brH != null) {
+            if (brH.isBusGateway() && refreshAll) {
+                long now = System.currentTimeMillis();
+                if (now - lastAllDevicesRefreshTS > ALL_DEVICES_REFRESH_INTERVAL_MSEC) {
+                    try {
+                        send(Automation.requestStatus(WhereLightAutom.GENERAL.value()));
+                        lastAllDevicesRefreshTS = now;
+                    } catch (OWNException e) {
+                        logger.warn("Excpetion while requesting all devices refresh: {}", e.getMessage());
+                    }
+                } else {
+                    logger.debug("Refresh all devices just sent...");
+                }
+            } else {
+                requestChannelState(new ChannelUID("any")); // channel here does not make any difference
+            }
+        }
+    }
+
+    @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) {
+        logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
+        updateAutomationState((Automation) msg);
+        // REMINDER: update automation state, and only after update thing status using 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 - startedMovingAtTS);
+                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;
+                Where dw = deviceWhere;
+                if (dw != null) {
+                    String w = dw.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
+                startedMovingAtTS = System.currentTimeMillis();
+                synchronized (DATE_FORMATTER) {
+                    logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAtTS,
+                            DATE_FORMATTER.format(new Date(startedMovingAtTS)));
+                }
+            }
+        } else { // we were moving
+            updatePosition();
+            if (newState != MOVING_STATE_STOPPED) { // moving after moving, take new timestamp
+                startedMovingAtTS = System.currentTimeMillis();
+                synchronized (DATE_FORMATTER) {
+                    logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAtTS,
+                            DATE_FORMATTER.format(new Date(startedMovingAtTS)));
+                }
+            }
+            // 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 && startedMovingAtTS != -1) {// we have shutterRun and startedMovingAtTS defined, let's
+                                                        // calculate new positionEstimation
+            long movedTime = System.currentTimeMillis() - startedMovingAtTS;
+            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();
+    }
+}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetBridgeHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetBridgeHandler.java
new file mode 100644 (file)
index 0000000..2b9c83f
--- /dev/null
@@ -0,0 +1,621 @@
+/**
+ * Copyright (c) 2010-2021 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.handler;
+
+import static org.openhab.binding.openwebnet.internal.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.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.binding.openwebnet.internal.discovery.OpenWebNetDeviceDiscoveryService;
+import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetBusBridgeConfig;
+import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetZigBeeBridgeConfig;
+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.Thing;
+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.openhab.core.types.RefreshType;
+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.EnergyManagement;
+import org.openwebnet4j.message.FrameException;
+import org.openwebnet4j.message.GatewayMgmt;
+import org.openwebnet4j.message.Lighting;
+import org.openwebnet4j.message.OpenMessage;
+import org.openwebnet4j.message.Thermoregulation;
+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 OpenWebNetBridgeHandler} is responsible for handling communication with gateways and handling events.
+ *
+ * @author Massimo Valla - Initial contribution
+ * @author Andrea Conte - Energy management, Thermoregulation
+ * @author Gilberto Cocchi - Thermoregulation
+ */
+@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
+
+    private static final int REFRESH_ALL_DEVICES_DELAY_MSEC = 500; // Delay to wait before sending all devices refresh
+                                                                   // request after a connect/reconnect
+
+    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<>();
+    private Map<String, Long> discoveringDevices = 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 @Nullable ScheduledFuture<?> refreshSchedule;
+
+    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 {}... ", gw);
+                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,
+                                    "@text/offline.comm-error-timeout");
+                        }
+                    }, GATEWAY_ONLINE_TIMEOUT_SEC, TimeUnit.SECONDS);
+                    logger.debug("bridge {} initialization completed", thing.getUID());
+                } 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 ZigBee USB 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 BUS 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 {
+            if (command instanceof RefreshType) {
+                refreshAllDevices();
+            } else {
+                logger.warn("Command or channel not supported: channel={} command={}", channelUID, command);
+            }
+        }
+    }
+
+    @Override
+    public Collection<ConfigStatusMessage> getConfigStatus() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void handleRemoval() {
+        disconnectGateway();
+        super.handleRemoval();
+    }
+
+    @Override
+    public void dispose() {
+        ScheduledFuture<?> rSc = refreshSchedule;
+        if (rSc != null) {
+            rSc.cancel(true);
+        }
+        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", gw);
+                    return;
+                }
+                logger.info("------$$ STARTED active SEARCH for devices on bridge '{}'", thing.getUID());
+                try {
+                    gw.discoverDevices();
+                } catch (OWNException e) {
+                    logger.warn("------$$ OWNException while discovering devices on bridge '{}': {}", thing.getUID(),
+                            e.getMessage());
+                }
+            } else {
+                logger.debug("------$$ Searching devices on bridge '{}' already activated", thing.getUID());
+                return;
+            }
+        } else {
+            logger.warn("------$$ 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 bridge '{}'", thing.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("discoverByActivation: msg={}", baseMsg);
+        OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
+        if (discService == null) {
+            logger.warn("discoverByActivation: null OpenWebNetDeviceDiscoveryService, ignoring msg={}", baseMsg);
+            return;
+        }
+        // we support these types only
+        if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
+                || baseMsg instanceof Thermoregulation) {
+            BaseOpenMessage bmsg = baseMsg;
+            if (baseMsg instanceof Lighting) {
+                What what = baseMsg.getWhat();
+                if (Lighting.WhatLighting.OFF.equals(what)) { // skipping OFF msg: cannot distinguish dimmer/switch
+                    logger.debug("discoverByActivation: skipping OFF msg: cannot distinguish dimmer/switch");
+                    return;
+                }
+                if (Lighting.WhatLighting.ON.equals(what)) { // if not already done just now, request light status to
+                    // distinguish dimmer from switch
+                    if (discoveringDevices.containsKey(ownIdFromMessage(baseMsg))) {
+                        logger.debug(
+                                "discoverByActivation: we just requested status for this device and it's ON -> it's a switch");
+                    } else {
+                        OpenGateway gw = gateway;
+                        if (gw != null) {
+                            try {
+                                discoveringDevices.put(ownIdFromMessage(baseMsg),
+                                        Long.valueOf(System.currentTimeMillis()));
+                                gw.send(Lighting.requestStatus(baseMsg.getWhere().value()));
+                                return;
+                            } catch (OWNException e) {
+                                logger.warn("discoverByActivation: Exception while requesting light state: {}",
+                                        e.getMessage());
+                                return;
+                            }
+                        }
+                    }
+                }
+                discoveringDevices.remove(ownIdFromMessage(baseMsg));
+            }
+            OpenDeviceType type = null;
+            try {
+                type = bmsg.detectDeviceType();
+            } catch (FrameException e) {
+                logger.warn("Exception while detecting device type: {}", e.getMessage());
+            }
+            if (type != null) {
+                discService.newDiscoveryResult(bmsg.getWhere(), type, bmsg);
+            } else {
+                logger.debug("discoverByActivation: no device type detected from msg: {}", bmsg);
+            }
+        }
+    }
+
+    /**
+     * 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 ownId the device OpenWebNet id
+     */
+    protected void unregisterDevice(String ownId) {
+        if (registeredDevices.remove(ownId) != null) {
+            logger.debug("un-registered device ownId={}", ownId);
+        } else {
+            logger.warn("could not un-register ownId={} (not found)", ownId);
+        }
+    }
+
+    /**
+     * Get an already registered device on this bridge handler
+     *
+     * @param ownId the device OpenWebNet id
+     * @return the registered device Thing handler or null if the id cannot be found
+     */
+    public @Nullable OpenWebNetThingHandler getRegisteredDevice(String ownId) {
+        return registeredDevices.get(ownId);
+    }
+
+    private void refreshAllDevices() {
+        logger.debug("Refreshing all devices for bridge {}", thing.getUID());
+        for (Thing ownThing : getThing().getThings()) {
+            OpenWebNetThingHandler hndlr = (OpenWebNetThingHandler) ownThing.getHandler();
+            if (hndlr != null) {
+                hndlr.refreshDevice(true);
+            }
+        }
+    }
+
+    @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 || baseMsg instanceof EnergyManagement
+                || baseMsg instanceof Thermoregulation) {
+            String ownId = ownIdFromMessage(baseMsg);
+            logger.debug("ownIdFromMessage({}) --> {}", baseMsg, 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 ZigBee USB gateway bridge '{}' (serialPort: {})", thing.getUID(),
+                    ((USBGateway) gw).getSerialPortName());
+        } else {
+            logger.info("---- CONNECTED to BUS gateway bridge '{}' ({}:{})", 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 bridge '{}'", thing.getUID());
+        }
+        updateStatus(ThingStatus.ONLINE);
+        // schedule a refresh for all devices
+        refreshSchedule = scheduler.schedule(this::refreshAllDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
+                TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void onConnectionError(@Nullable OWNException error) {
+        String errMsg;
+        if (error == null) {
+            errMsg = "unknown error";
+        } else {
+            errMsg = error.getMessage();
+        }
+        logger.info("---- ON CONNECTION ERROR for gateway {}: {}", gateway, errMsg);
+        isGatewayConnected = false;
+        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                "@text/offline.comm-error-connection" + " (onConnectionError - " + 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: {}", gateway, errMsg);
+        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                "@text/offline.comm-error-disconnected" + " (onDisconnected - " + errMsg + ")");
+        tryReconnectGateway();
+    }
+
+    private void tryReconnectGateway() {
+        OpenGateway gw = gateway;
+        if (gw != null) {
+            if (!reconnecting) {
+                reconnecting = true;
+                logger.info("---- Starting RECONNECT cycle to gateway {}", gw);
+                try {
+                    gw.reconnect();
+                } catch (OWNAuthException e) {
+                    logger.info("---- AUTH error from gateway. Stopping re-connect");
+                    reconnecting = false;
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
+                            "@text/offline.conf-error-auth" + " (" + e + ")");
+                }
+            } else {
+                logger.debug("---- reconnecting=true");
+            }
+        } else {
+            logger.warn("---- cannot start RECONNECT, gateway is null");
+        }
+    }
+
+    @Override
+    public void onReconnected() {
+        reconnecting = false;
+        OpenGateway gw = gateway;
+        logger.info("---- RE-CONNECTED to bridge {}", thing.getUID());
+        if (gw != null) {
+            updateStatus(ThingStatus.ONLINE);
+            if (gw.getFirmwareVersion() != null) {
+                this.updateProperty(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
+                logger.debug("gw firmware version: {}", gw.getFirmwareVersion());
+            }
+
+            // schedule a refresh for all devices
+            refreshSchedule = scheduler.schedule(this::refreshAllDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
+                    TimeUnit.MILLISECONDS);
+        }
+    }
+
+    /**
+     * Return a ownId string (=WHO.WHERE) from the device Where address and handler
+     *
+     * @param where the Where address (to be normalized)
+     * @param handler the device handler
+     * @return the ownId String
+     */
+    protected String ownIdFromDeviceWhere(Where where, OpenWebNetThingHandler handler) {
+        return handler.ownIdPrefix() + "." + normalizeWhere(where);
+    }
+
+    /**
+     * Returns a ownId string (=WHO.WHERE) from a Who and Where address
+     *
+     * @param who the Who
+     * @param where the Where address (to be normalized)
+     * @return the ownId String
+     */
+    public String ownIdFromWhoWhere(Who who, Where where) {
+        return who.value() + "." + normalizeWhere(where);
+    }
+
+    /**
+     * Return a ownId string (=WHO.WHERE) from a BaseOpenMessage
+     *
+     * @param baseMsg the BaseOpenMessage
+     * @return the ownId String
+     */
+    public String ownIdFromMessage(BaseOpenMessage baseMsg) {
+        return baseMsg.getWho().value() + "." + normalizeWhere(baseMsg.getWhere());
+    }
+
+    /**
+     * Transform a Where address into a Thing id string
+     *
+     * @param where the Where address
+     * @return the thing Id string
+     */
+    public String thingIdFromWhere(Where where) {
+        return normalizeWhere(where); // '#' cannot be used in ThingUID;
+    }
+
+    /**
+     * Normalize a Where address
+     *
+     * @param where the Where address
+     * @return the normalized address as String
+     */
+    public String normalizeWhere(Where where) {
+        String str = where.value();
+        if (where instanceof WhereZigBee) {
+            str = ((WhereZigBee) where).valueWithUnit(WhereZigBee.UNIT_ALL); // 76543210X#9 --> 765432100#9
+        } else {
+            if (str.indexOf("#4#") == -1) { // skip APL#4#bus case
+                if (str.indexOf('#') == 0) { // Thermo central unit (#0) or zone via central unit (#Z, Z=[1-99]) --> Z
+                    str = str.substring(1);
+                } else if (str.indexOf('#') > 0) { // Thermo zone Z and actuator N (Z#N, Z=[1-99], N=[1-9]) --> Z
+                    str = str.substring(0, str.indexOf('#'));
+                }
+            }
+        }
+        return str.replace('#', 'h');
+    }
+}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetEnergyHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetEnergyHandler.java
new file mode 100644 (file)
index 0000000..386e451
--- /dev/null
@@ -0,0 +1,209 @@
+/**
+ * Copyright (c) 2010-2021 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.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_POWER;
+
+import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.measure.quantity.Power;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusInfo;
+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.BaseOpenMessage;
+import org.openwebnet4j.message.EnergyManagement;
+import org.openwebnet4j.message.FrameException;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereEnergyManagement;
+import org.openwebnet4j.message.Who;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetEnergyHandler} is responsible for handling commands/messages for a Energy Management OpenWebNet
+ * device. It extends the abstract {@link OpenWebNetThingHandler}.
+ *
+ * @author Massimo Valla - Initial contribution
+ * @author Andrea Conte - Energy management
+ */
+@NonNullByDefault
+public class OpenWebNetEnergyHandler extends OpenWebNetThingHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(OpenWebNetEnergyHandler.class);
+
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES;
+    public static final int ENERGY_SUBSCRIPTION_PERIOD = 10; // minutes
+    private @Nullable ScheduledFuture<?> notificationSchedule;
+
+    public OpenWebNetEnergyHandler(Thing thing) {
+        super(thing);
+    }
+
+    public Boolean isFirstSchedulerLaunch = true;
+
+    @Override
+    public void initialize() {
+        super.initialize();
+
+        // In order to get data from the probe we must send a command over the bus, this could be done only when the
+        // bridge is online.
+        // a) usual flow: binding is starting, the bridge isn't online (startup flow not yet completed) --> subscriber
+        // can't be started here, it will be started inside the bridgeStatusChanged event.
+        // b) thing's discovery: binding is up and running, the bridge is online --> subscriber must be started here
+        // otherwise data will be read only after a reboot.
+
+        OpenWebNetBridgeHandler h = bridgeHandler;
+        if (h != null && h.isBusGateway()) {
+            OpenGateway gw = h.gateway;
+            if (gw != null && gw.isConnected()) {
+                // bridge is online
+                subscribeToActivePowerChanges();
+            }
+        }
+    }
+
+    @Override
+    public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+        super.bridgeStatusChanged(bridgeStatusInfo);
+
+        // subscribe the scheduler only after the bridge is online
+        if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
+            subscribeToActivePowerChanges();
+        }
+    }
+
+    private void subscribeToActivePowerChanges() {
+        notificationSchedule = scheduler.scheduleWithFixedDelay(() -> {
+            if (isFirstSchedulerLaunch) {
+                logger.debug(
+                        "subscribeToActivePowerChanges() For WHERE={} subscribing to active power changes notification for the next {}min",
+                        deviceWhere, ENERGY_SUBSCRIPTION_PERIOD);
+            } else {
+                logger.debug(
+                        "subscribeToActivePowerChanges() Refreshing subscription for the next {}min for WHERE={} to active power changes notification",
+                        ENERGY_SUBSCRIPTION_PERIOD, deviceWhere);
+            }
+
+            try {
+                bridgeHandler.gateway.send(EnergyManagement.setActivePowerNotificationsTime(deviceWhere.value(),
+                        ENERGY_SUBSCRIPTION_PERIOD));
+                isFirstSchedulerLaunch = false;
+            } catch (Exception e) {
+                if (isFirstSchedulerLaunch) {
+                    logger.warn(
+                            "subscribeToActivePowerChanges() For WHERE={} could not subscribe to active power changes notifications. Exception={}",
+                            deviceWhere, e.getMessage());
+                } else {
+                    logger.warn(
+                            "subscribeToActivePowerChanges() Unable to refresh subscription to active power changes notifications for WHERE={}. Exception={}",
+                            deviceWhere, e.getMessage());
+                }
+            }
+        }, 0, ENERGY_SUBSCRIPTION_PERIOD - 1, TimeUnit.MINUTES);
+    }
+
+    @Override
+    public void dispose() {
+        if (notificationSchedule != null) {
+            logger.debug("dispose() scheduler stopped.");
+
+            notificationSchedule.cancel(false);
+        }
+        super.dispose();
+    }
+
+    @Override
+    protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
+        return new WhereEnergyManagement(wStr);
+    }
+
+    @Override
+    protected void requestChannelState(ChannelUID channel) {
+        logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
+        Where w = deviceWhere;
+        if (w != null) {
+            try {
+                send(EnergyManagement.requestActivePower(w.value()));
+            } catch (OWNException e) {
+                logger.debug("Exception while requesting channel {} state: {}", channel, e.getMessage(), e);
+            }
+        } else {
+            logger.warn("Could not requestChannelState(): deviceWhere is null");
+        }
+    }
+
+    @Override
+    protected void refreshDevice(boolean refreshAll) {
+        requestChannelState(new ChannelUID("any:any:any:any"));
+    }
+
+    @Override
+    protected void handleChannelCommand(ChannelUID channel, Command command) {
+        logger.warn("handleChannelCommand() Read only channel, unsupported command {}", command);
+    }
+
+    @Override
+    protected String ownIdPrefix() {
+        return Who.ENERGY_MANAGEMENT.value().toString();
+    }
+
+    @Override
+    protected void handleMessage(BaseOpenMessage msg) {
+        super.handleMessage(msg);
+
+        if (msg.isCommand()) {
+            logger.warn("handleMessage() Ignoring unsupported command for thing {}. Frame={}", getThing().getUID(),
+                    msg);
+            return;
+        } else {
+            // fix: check for correct DIM (ActivePower / 113)
+            if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.ACTIVE_POWER)) {
+                updateActivePower(msg);
+            } else {
+                logger.debug("handleMessage() Ignoring message {} because it's not related to active power value.",
+                        msg);
+            }
+        }
+    }
+
+    /**
+     * Updates energy power state based on a EnergyManagement message received from the OWN network
+     *
+     * @param msg the EnergyManagement message received
+     * @throws FrameException
+     */
+    private void updateActivePower(BaseOpenMessage msg) {
+        Integer activePower;
+        try {
+            activePower = Integer.parseInt(msg.getDimValues()[0]);
+            updateState(CHANNEL_POWER, new QuantityType<Power>(activePower, Units.WATT));
+        } catch (FrameException e) {
+            logger.warn("FrameException on frame {}: {}", msg, e.getMessage());
+            updateState(CHANNEL_POWER, UnDefType.UNDEF);
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetGenericHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetGenericHandler.java
new file mode 100644 (file)
index 0000000..760beca
--- /dev/null
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2010-2021 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.handler;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.openwebnet.internal.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.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereLightAutom;
+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("Generic: there are no channels");
+    }
+
+    @Override
+    protected void refreshDevice(boolean refreshAll) {
+        // do nothing
+        logger.warn("Generic: nothing to refresh");
+    }
+
+    @Override
+    protected void handleChannelCommand(ChannelUID channel, Command command) {
+        // do nothing
+        logger.warn("Generic: there are no channels");
+    }
+
+    @Override
+    protected String ownIdPrefix() {
+        return "G";
+    }
+
+    @Override
+    protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
+        return new WhereLightAutom(wStr);
+    }
+
+    @Override
+    protected void handleMessage(BaseOpenMessage msg) {
+        super.handleMessage(msg);
+        // do nothing
+        logger.warn("Generic: handleMessage() nothing to do!");
+    }
+}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetLightingHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetLightingHandler.java
new file mode 100644 (file)
index 0000000..095cfb9
--- /dev/null
@@ -0,0 +1,428 @@
+/**
+ * Copyright (c) 2010-2021 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.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openwebnet.internal.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.ThingStatus;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.types.Command;
+import org.openwebnet4j.communication.OWNException;
+import org.openwebnet4j.communication.Response;
+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.WhereLightAutom;
+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;
+
+    // interval to interpret ON as response to requestStatus
+    private static final int BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC = 250;
+
+    // time to wait before sending a statusRequest, to avoid repeated requests and ensure dimmer has reached its final
+    // level
+    private static final int BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC = 900;
+
+    private static final int UNKNOWN_STATE = 1000;
+
+    private static long lastAllDevicesRefreshTS = -1; // timestamp when the last request for all device refresh was sent
+                                                      // for this handler
+
+    protected static final int ALL_DEVICES_REFRESH_INTERVAL_MSEC = 60000; // interval in msec before sending another all
+                                                                          // devices refresh request
+
+    private long lastBrightnessChangeSentTS = 0; // timestamp when last brightness change was sent to the device
+
+    private long lastStatusRequestSentTS = 0; // timestamp when last status request was sent to the device
+
+    private int brightness = UNKNOWN_STATE; // current brightness percent value for this device
+
+    private int brightnessBeforeOff = UNKNOWN_STATE; // latest brightness before device was set to off
+
+    private int sw[] = { UNKNOWN_STATE, UNKNOWN_STATE }; // current switch(es) state
+
+    public OpenWebNetLightingHandler(Thing thing) {
+        super(thing);
+    }
+
+    @Override
+    protected void requestChannelState(ChannelUID channel) {
+        logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
+        requestStatus(channel.getId());
+    }
+
+    /** helper method to request light status based on channel */
+    private void requestStatus(String channelId) {
+        Where w = deviceWhere;
+        if (w != null) {
+            try {
+                lastStatusRequestSentTS = System.currentTimeMillis();
+                Response res = send(Lighting.requestStatus(toWhere(channelId)));
+                if (res != null && res.isSuccess()) {
+                    // set thing online, if not already
+                    ThingStatus ts = getThing().getStatus();
+                    if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
+                        updateStatus(ThingStatus.ONLINE);
+                    }
+                }
+            } catch (OWNException e) {
+                logger.warn("requestStatus() Exception while requesting light state: {}", e.getMessage());
+            }
+        } else {
+            logger.warn("Could not requestStatus(): deviceWhere is null");
+        }
+    }
+
+    @Override
+    protected void refreshDevice(boolean refreshAll) {
+        OpenWebNetBridgeHandler brH = bridgeHandler;
+        if (brH != null) {
+            if (brH.isBusGateway() && refreshAll) {
+                long now = System.currentTimeMillis();
+                if (now - lastAllDevicesRefreshTS > ALL_DEVICES_REFRESH_INTERVAL_MSEC) {
+                    try {
+                        send(Lighting.requestStatus(WhereLightAutom.GENERAL.value()));
+                        lastAllDevicesRefreshTS = now;
+                    } catch (OWNException e) {
+                        logger.warn("Excpetion while requesting all devices refresh: {}", e.getMessage());
+                    }
+                } else {
+                    logger.debug("Refresh all devices just sent...");
+                }
+            } else { // USB or BUS-single device
+                ThingTypeUID thingType = thing.getThingTypeUID();
+                if (THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS.equals(thingType)) {
+                    // Unfortunately using USB Gateway OpenWebNet both switch endpoints cannot be requested at the same
+                    // time using UNIT 00 because USB stick returns NACK, so we need to send a request status for both
+                    // endpoints
+                    requestStatus(CHANNEL_SWITCH_02);
+                }
+                requestStatus(""); // channel here does not make any difference, see {@link #toWhere()}
+            }
+        }
+    }
+
+    @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 ChannelUID {}", 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.getId())));
+                } else if (OnOffType.OFF.equals(command)) {
+                    send(Lighting.requestTurnOff(toWhere(channel.getId())));
+                }
+            } catch (OWNException e) {
+                logger.warn("Exception while processing command {}: {}", command, e.getMessage());
+            }
+        } else {
+            logger.warn("Unsupported command: {}", command);
+        }
+    }
+
+    /**
+     * Handles Lighting brightness command (xx%, INCREASE, DECREASE, ON, OFF)
+     *
+     * @param command the command
+     */
+    private void handleBrightnessCommand(Command command) {
+        logger.debug("handleBrightnessCommand() command={}", command);
+        if (command instanceof PercentType) {
+            dimLightTo(((PercentType) command).intValue(), command);
+        } else if (command instanceof IncreaseDecreaseType) {
+            if (IncreaseDecreaseType.INCREASE.equals(command)) {
+                dimLightTo(brightness + 10, command);
+            } else { // DECREASE
+                dimLightTo(brightness - 10, command);
+            }
+        } else if (command instanceof OnOffType) {
+            if (OnOffType.ON.equals(command)) {
+                dimLightTo(brightnessBeforeOff, command);
+            } else { // OFF
+                dimLightTo(0, command);
+            }
+        } else {
+            logger.warn("Cannot handle command {} for thing {}", command, getThing().getUID());
+        }
+    }
+
+    /**
+     * Helper method to dim light to given percent
+     */
+    private void dimLightTo(int percent, Command command) {
+        logger.debug("   DIM dimLightTo({}) bri={} briBeforeOff={}", percent, brightness, brightnessBeforeOff);
+        int newBrightness = percent;
+        if (newBrightness == UNKNOWN_STATE) {
+            // we do not know last brightness -> set dimmer to 100%
+            newBrightness = 100;
+        } else if (newBrightness <= 0) {
+            newBrightness = 0;
+            brightnessBeforeOff = brightness;
+            logger.debug("   DIM saved bri before sending bri=0 command to device");
+        } else if (newBrightness > 100) {
+            newBrightness = 100;
+        }
+        What newBrightnessWhat = Lighting.percentToWhat(newBrightness);
+        logger.debug("   DIM newBrightness={} newBrightnessWhat={}", newBrightness, newBrightnessWhat);
+        @Nullable
+        What brightnessWhat = null;
+        if (brightness != UNKNOWN_STATE) {
+            brightnessWhat = Lighting.percentToWhat(brightness);
+        }
+        if (brightnessWhat == null || !newBrightnessWhat.value().equals(brightnessWhat.value())) {
+            logger.debug("   DIM brightnessWhat {} --> {}  WHAT level change needed", brightnessWhat,
+                    newBrightnessWhat);
+            Where w = deviceWhere;
+            if (w != null) {
+                try {
+                    lastBrightnessChangeSentTS = System.currentTimeMillis();
+                    send(Lighting.requestDimTo(w.value(), newBrightnessWhat));
+                } catch (OWNException e) {
+                    logger.warn("Exception while sending dimTo request for command {}: {}", command, e.getMessage());
+                }
+            }
+        } else {
+            logger.debug("   DIM brightnessWhat {} --> {}  NO WHAT level change needed", brightnessWhat,
+                    newBrightnessWhat);
+        }
+        brightness = newBrightness;
+        updateState(CHANNEL_BRIGHTNESS, new PercentType(brightness));
+        logger.debug("   DIM---END bri={} briBeforeOff={}", brightness, brightnessBeforeOff);
+    }
+
+    @Override
+    protected String ownIdPrefix() {
+        return Who.LIGHTING.value().toString();
+    }
+
+    @Override
+    protected void handleMessage(BaseOpenMessage msg) {
+        logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
+        super.handleMessage(msg);
+        ThingTypeUID thingType = thing.getThingTypeUID();
+        if (THING_TYPE_ZB_DIMMER.equals(thingType) || THING_TYPE_BUS_DIMMER.equals(thingType)) {
+            updateBrightness((Lighting) msg);
+        } else {
+            updateOnOffState((Lighting) msg);
+        }
+    }
+
+    /**
+     * Updates brightness based on OWN Lighting message received
+     *
+     * @param msg the Lighting message received
+     */
+    private synchronized void updateBrightness(Lighting msg) {
+        logger.debug("  $BRI updateBrightness({})       || bri={} briBeforeOff={}", msg, brightness,
+                brightnessBeforeOff);
+        long now = System.currentTimeMillis();
+        long delta = now - lastBrightnessChangeSentTS;
+        boolean belowThresh = delta < BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC;
+        logger.debug("  $BRI delta={}ms {}", delta, (belowThresh ? "< DELAY" : ""));
+        if (belowThresh) {
+            // we just sent a command from OH, so we can ignore this message from network
+            logger.debug("  $BRI a command was sent {} < {} ms --> no action needed", delta,
+                    BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC);
+        } else {
+            if (msg.isOn()) {
+                // if we have not just sent a requestStatus, on ON event we send requestStatus to know current level
+                long deltaStatusReq = now - lastStatusRequestSentTS;
+                if (deltaStatusReq > BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC) {
+                    logger.debug("  $BRI 'ON' is new notification from network, scheduling requestStatus...");
+                    // we must wait BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC to be sure dimmer has reached its final level
+                    scheduler.schedule(() -> {
+                        requestStatus(CHANNEL_BRIGHTNESS);
+                    }, BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC, TimeUnit.MILLISECONDS);
+                    return;
+                } else {
+                    // otherwise we interpret this ON event as the requestStatus response event with level=1
+                    // so we proceed to call updateBrightnessState()
+                    logger.debug("  $BRI 'ON' is the requestStatus response level");
+                }
+            }
+            logger.debug("  $BRI update from network");
+            if (msg.getWhat() != null) {
+                updateBrightnessState(msg);
+            } else { // dimension notification
+                if (msg.getDim() == Lighting.DimLighting.DIMMER_LEVEL_100) {
+                    int newBrightness;
+                    try {
+                        newBrightness = msg.parseDimmerLevel100();
+                    } catch (FrameException fe) {
+                        logger.warn("updateBrightness() Wrong value for dimmerLevel100 in message: {}", msg);
+                        return;
+                    }
+                    logger.debug("  $BRI DIMMER_LEVEL_100 newBrightness={}", newBrightness);
+                    updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
+                    if (newBrightness == 0) {
+                        brightnessBeforeOff = brightness;
+                    }
+                    brightness = newBrightness;
+                } else {
+                    logger.warn("updateBrightness() Cannot handle message {} for thing {}", msg, getThing().getUID());
+                    return;
+                }
+            }
+        }
+        logger.debug("  $BRI---END updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
+                brightnessBeforeOff);
+    }
+
+    /**
+     * Updates light brightness state based on a OWN Lighting message
+     *
+     * @param msg the Lighting message received
+     */
+    private void updateBrightnessState(Lighting msg) {
+        What w = msg.getWhat();
+        if (w != null) {
+            if (Lighting.WhatLighting.ON.equals(w)) {
+                w = Lighting.WhatLighting.DIMMER_LEVEL_2; // levels start at 2
+            }
+            int newBrightnessWhat = w.value();
+            int brightnessWhat = UNKNOWN_STATE;
+            if (brightness != UNKNOWN_STATE) {
+                brightnessWhat = Lighting.percentToWhat(brightness).value();
+            }
+            logger.debug("  $BRI brightnessWhat {} --> {}", brightnessWhat, newBrightnessWhat);
+            if (brightnessWhat != newBrightnessWhat) {
+                int newBrightness = Lighting.levelToPercent(newBrightnessWhat);
+                updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
+                if (msg.isOff()) {
+                    brightnessBeforeOff = brightness;
+                }
+                brightness = newBrightness;
+                logger.debug("  $BRI brightness CHANGED to {}", brightness);
+            } else {
+                logger.debug("  $BRI no change");
+            }
+        }
+    }
+
+    /**
+     * Updates light on/off state based on a OWN Lighting event message received
+     *
+     * @param msg the Lighting message received
+     */
+    private void updateOnOffState(Lighting msg) {
+        OpenWebNetBridgeHandler brH = bridgeHandler;
+        if (brH != null) {
+            if (msg.isOn() || msg.isOff()) {
+                String channelId;
+                int switchId = 0;
+                if (brH.isBusGateway()) {
+                    channelId = CHANNEL_SWITCH;
+                } else {
+                    WhereZigBee w = (WhereZigBee) (msg.getWhere());
+                    if (WhereZigBee.UNIT_02.equals(w.getUnit())) {
+                        channelId = CHANNEL_SWITCH_02;
+                        switchId = 1;
+                    } else {
+                        channelId = CHANNEL_SWITCH_01;
+                    }
+                }
+                int currentSt = sw[switchId];
+                int newSt = (msg.isOn() ? 1 : 0);
+                if (newSt != currentSt) {
+                    updateState(channelId, (newSt == 1 ? OnOffType.ON : OnOffType.OFF));
+                    sw[switchId] = newSt;
+                    logger.debug("  {} ONOFF CHANGED to {}", ownId, newSt);
+                } else {
+                    logger.debug("  {} ONOFF no change", ownId);
+                }
+            } else {
+                logger.debug("updateOnOffState() Ignoring unsupported WHAT for thing {}. Frame={}", getThing().getUID(),
+                        msg.getFrameValue());
+                return;
+            }
+        }
+    }
+
+    @Override
+    protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
+        return new WhereLightAutom(wStr);
+    }
+
+    /**
+     * Returns a WHERE address string based on channelId string
+     *
+     * @param channelId the channelId string
+     **/
+    @Nullable
+    private String toWhere(String channelId) {
+        Where w = deviceWhere;
+        if (w != null) {
+            OpenWebNetBridgeHandler brH = bridgeHandler;
+            if (brH != null) {
+                if (brH.isBusGateway()) {
+                    return w.value();
+                } else if (channelId.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;
+    }
+}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThermoregulationHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThermoregulationHandler.java
new file mode 100644 (file)
index 0000000..7f3d814
--- /dev/null
@@ -0,0 +1,337 @@
+/**
+ * Copyright (c) 2010-2021 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.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.UnDefType;
+import org.openwebnet4j.communication.OWNException;
+import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.FrameException;
+import org.openwebnet4j.message.MalformedFrameException;
+import org.openwebnet4j.message.Thermoregulation;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereThermo;
+import org.openwebnet4j.message.Who;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetThermoregulationHandler} is responsible for handling commands/messages for Thermoregulation
+ * Things. It extends the abstract {@link OpenWebNetThingHandler}.
+ *
+ * @author Massimo Valla - Initial contribution
+ * @author Andrea Conte - Thermoregulation
+ * @author Gilberto Cocchi - Thermoregulation
+ */
+@NonNullByDefault
+public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(OpenWebNetThermoregulationHandler.class);
+
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.THERMOREGULATION_SUPPORTED_THING_TYPES;
+
+    private boolean isTempSensor = false; // is the thing a sensor ?
+
+    private double currentSetPointTemp = 11.5d; // 11.5 is the default setTemp used in MyHomeUP mobile app
+
+    private Thermoregulation.Function currentFunction = Thermoregulation.Function.GENERIC;
+
+    public OpenWebNetThermoregulationHandler(Thing thing) {
+        super(thing);
+    }
+
+    @Override
+    public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+        super.bridgeStatusChanged(bridgeStatusInfo);
+        // when the bridge is ONLINE request for thing states (temp, setTemp, fanSpeed...)
+        if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
+            refreshDevice(false);
+        }
+    }
+
+    @Override
+    protected void handleChannelCommand(ChannelUID channel, Command command) {
+        switch (channel.getId()) {
+            case CHANNEL_TEMP_SETPOINT:
+                handleSetpoint(command);
+                break;
+            case CHANNEL_FUNCTION:
+                handleFunction(command);
+                break;
+            case CHANNEL_MODE:
+                handleMode(command);
+                break;
+            case CHANNEL_FAN_SPEED:
+                handleSetFanSpeed(command);
+                break;
+            default: {
+                logger.warn("handleChannelCommand() Unsupported ChannelUID {}", channel.getId());
+            }
+        }
+    }
+
+    @Override
+    protected void requestChannelState(ChannelUID channel) {
+        refreshDevice(false);
+    }
+
+    @Override
+    protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
+        WhereThermo wt = new WhereThermo(wStr);
+        if (wt.isProbe()) {
+            isTempSensor = true;
+        }
+        return wt;
+    }
+
+    @Override
+    protected String ownIdPrefix() {
+        return Who.THERMOREGULATION.value().toString();
+    }
+
+    private void handleSetFanSpeed(Command command) {
+        if (command instanceof StringType) {
+            Where w = deviceWhere;
+            if (w != null) {
+                try {
+                    Thermoregulation.FanCoilSpeed speed = Thermoregulation.FanCoilSpeed.valueOf(command.toString());
+                    send(Thermoregulation.requestWriteFanCoilSpeed(w.value(), speed));
+                } catch (OWNException e) {
+                    logger.warn("handleSetFanSpeed() {}", e.getMessage());
+                } catch (IllegalArgumentException e) {
+                    logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command,
+                            getThing().getUID());
+                    return;
+                }
+            }
+        } else {
+            logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command, getThing().getUID());
+        }
+    }
+
+    private void handleSetpoint(Command command) {
+        if (command instanceof QuantityType || command instanceof DecimalType) {
+            Where w = deviceWhere;
+            if (w != null) {
+                double newTemp = 0;
+                if (command instanceof QuantityType) {
+                    QuantityType<?> tempCelsius = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
+                    if (tempCelsius != null) {
+                        newTemp = tempCelsius.doubleValue();
+                    }
+                } else {
+                    newTemp = ((DecimalType) command).doubleValue();
+                }
+                try {
+                    send(Thermoregulation.requestWriteSetpointTemperature(w.value(), newTemp, currentFunction));
+                } catch (MalformedFrameException | OWNException e) {
+                    logger.warn("handleSetpoint() {}", e.getMessage());
+                }
+            }
+        } else {
+            logger.warn("handleSetpoint() Unsupported command {} for thing {}", command, getThing().getUID());
+        }
+    }
+
+    private void handleMode(Command command) {
+        if (command instanceof StringType) {
+            Where w = deviceWhere;
+            if (w != null) {
+                try {
+                    Thermoregulation.OperationMode mode = Thermoregulation.OperationMode.valueOf(command.toString());
+                    send(Thermoregulation.requestWriteMode(w.value(), mode, currentFunction, currentSetPointTemp));
+                } catch (OWNException e) {
+                    logger.warn("handleMode() {}", e.getMessage());
+                } catch (IllegalArgumentException e) {
+                    logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
+                    return;
+                }
+            }
+        } else {
+            logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
+        }
+    }
+
+    private void handleFunction(Command command) {
+        if (command instanceof StringType) {
+            Where w = deviceWhere;
+            if (w != null) {
+                try {
+                    Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
+                    send(Thermoregulation.requestWriteFunction(w.value(), function));
+                } catch (OWNException e) {
+                    logger.warn("handleFunction() {}", e.getMessage());
+                } catch (IllegalArgumentException e) {
+                    logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
+                    return;
+                }
+            }
+        } else {
+            logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
+        }
+    }
+
+    @Override
+    protected void handleMessage(BaseOpenMessage msg) {
+        super.handleMessage(msg);
+        if (msg.isCommand()) {
+            updateModeAndFunction((Thermoregulation) msg);
+        } else {
+            if (msg.getDim() == null) {
+                return;
+            }
+            if (msg.getDim() == Thermoregulation.DimThermo.TEMPERATURE
+                    || msg.getDim() == Thermoregulation.DimThermo.PROBE_TEMPERATURE) {
+                updateTemperature((Thermoregulation) msg);
+            } else if (msg.getDim() == Thermoregulation.DimThermo.TEMP_SETPOINT
+                    || msg.getDim() == Thermoregulation.DimThermo.COMPLETE_PROBE_STATUS) {
+                updateSetpoint((Thermoregulation) msg);
+            } else if (msg.getDim() == Thermoregulation.DimThermo.VALVES_STATUS) {
+                updateValveStatus((Thermoregulation) msg);
+            } else if (msg.getDim() == Thermoregulation.DimThermo.ACTUATOR_STATUS) {
+                updateActuatorStatus((Thermoregulation) msg);
+            } else if (msg.getDim() == Thermoregulation.DimThermo.FAN_COIL_SPEED) {
+                updateFanCoilSpeed((Thermoregulation) msg);
+            } else {
+                logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", msg.getDim(),
+                        getThing().getUID(), msg);
+            }
+        }
+    }
+
+    private void updateModeAndFunction(Thermoregulation tmsg) {
+        if (tmsg.getWhat() == null) {
+            logger.debug("updateModeAndFunction() Could not parse Mode or Function from {} (what is null)",
+                    tmsg.getFrameValue());
+            return;
+        }
+
+        Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
+
+        if (w.mode() == null) {
+            logger.debug("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
+            return;
+        }
+        if (w.function() == null) {
+            logger.debug("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
+            return;
+        }
+
+        Thermoregulation.OperationMode mode = w.mode();
+        Thermoregulation.Function function = w.function();
+
+        if (w == Thermoregulation.WhatThermo.HEATING) {
+            function = Thermoregulation.Function.HEATING;
+        } else if (w == Thermoregulation.WhatThermo.CONDITIONING) {
+            function = Thermoregulation.Function.COOLING;
+        }
+
+        updateState(CHANNEL_MODE, new StringType(mode.toString()));
+        updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
+
+        // store current function
+        currentFunction = function;
+    }
+
+    private void updateTemperature(Thermoregulation tmsg) {
+        try {
+            double temp = Thermoregulation.parseTemperature(tmsg);
+            updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
+        } catch (FrameException e) {
+            logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
+            updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
+        }
+    }
+
+    private void updateSetpoint(Thermoregulation tmsg) {
+        try {
+            double temp = Thermoregulation.parseTemperature(tmsg);
+            updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
+            currentSetPointTemp = temp;
+        } catch (FrameException e) {
+            logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
+            updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
+        }
+    }
+
+    private void updateFanCoilSpeed(Thermoregulation tmsg) {
+        try {
+            Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
+            updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
+        } catch (FrameException e) {
+            logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
+            updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
+        }
+    }
+
+    private void updateValveStatus(Thermoregulation tmsg) {
+        try {
+            Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
+                    Thermoregulation.WhatThermo.CONDITIONING);
+            updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
+
+            Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
+                    Thermoregulation.WhatThermo.HEATING);
+            updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
+        } catch (FrameException e) {
+            logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
+            updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
+            updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
+        }
+    }
+
+    private void updateActuatorStatus(Thermoregulation tmsg) {
+        try {
+            Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
+            updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
+        } catch (FrameException e) {
+            logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
+            updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
+        }
+    }
+
+    @Override
+    protected void refreshDevice(boolean refreshAll) {
+        if (deviceWhere != null) {
+            String w = deviceWhere.value();
+            try {
+                send(Thermoregulation.requestTemperature(w));
+                if (!this.isTempSensor) {
+                    // for bus_thermo_zone request also other single channels updates
+                    send(Thermoregulation.requestSetPointTemperature(w));
+                    send(Thermoregulation.requestFanCoilSpeed(w));
+                    send(Thermoregulation.requestMode(w));
+                    send(Thermoregulation.requestValvesStatus(w));
+                    send(Thermoregulation.requestActuatorsStatus(w));
+                }
+            } catch (OWNException e) {
+                logger.warn("refreshDevice() where='{}' returned OWNException {}", w, e.getMessage());
+            }
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThingHandler.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThingHandler.java
new file mode 100644 (file)
index 0000000..7e0ac4b
--- /dev/null
@@ -0,0 +1,247 @@
+/**
+ * Copyright (c) 2010-2021 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.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
+
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.measure.Quantity;
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.QuantityType;
+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.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+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.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;
+
+                final String configDeviceWhere = (String) getConfig().get(CONFIG_PROPERTY_WHERE);
+                if (configDeviceWhere == null) {
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                            "@text/offline.conf-error-where");
+                } else {
+                    Where w;
+                    try {
+                        if (brH.isBusGateway()) {
+                            w = buildBusWhere(configDeviceWhere);
+                        } else {
+                            w = new WhereZigBee(configDeviceWhere);
+                        }
+                    } catch (IllegalArgumentException ia) {
+                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                                "@text/offline.conf-error-where");
+                        return;
+                    }
+                    deviceWhere = w;
+                    final String oid = brH.ownIdFromDeviceWhere(w, 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, "@text/unknown.waiting-state");
+                }
+            }
+        } else {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    "@text/offline.conf-error-no-bridge");
+        }
+    }
+
+    @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", command, getThing().getUID());
+                return;
+            }
+            if (deviceWhere == null) {
+                logger.info("Cannot handle {} command for {}: 'where' parameter is not configured or is invalid",
+                        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 ThingHandlers
+     */
+    protected @Nullable Response send(OpenMessage msg) throws OWNException {
+        OpenWebNetBridgeHandler bh = bridgeHandler;
+        if (bh != null) {
+            OpenGateway gw = bh.gateway;
+            if (gw != null) {
+                return gw.send(msg);
+            }
+        }
+        logger.warn("Couldn't send message {}: handler or gateway is null", 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 the state for the specified channel
+     *
+     * @param channel the {@link ChannelUID} to request the state for
+     */
+    protected abstract void requestChannelState(ChannelUID channel);
+
+    /**
+     * Refresh the device
+     *
+     * @param refreshAll set true if all devices of the binding should be refreshed with one command, if possible
+     */
+    protected abstract void refreshDevice(boolean refreshAll);
+
+    /**
+     * Abstract builder for device Where address, to be implemented by each subclass to choose the right Where subclass
+     * (the method is used only if the Thing is associated to a BUS gateway).
+     *
+     * @param wStr the WHERE string
+     */
+    protected abstract Where buildBusWhere(String wStr) throws IllegalArgumentException;
+
+    @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();
+    }
+
+    /**
+     * Helper method to return a Quantity from a Number value or UnDefType.NULL if value is null
+     *
+     * @param value to be used
+     * @param unit to be used
+     * @return Quantity
+     */
+    protected <U extends Quantity<U>> State getAsQuantityTypeOrNull(@Nullable Number value, Unit<U> unit) {
+        return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
+    }
+
+    /**
+     * Returns a prefix String for ownId specific for each handler. To be implemented by sub-classes.
+     *
+     * @return
+     */
+    protected abstract String ownIdPrefix();
+}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/config/OpenWebNetBusBridgeConfig.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/config/OpenWebNetBusBridgeConfig.java
new file mode 100644 (file)
index 0000000..e9000fa
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2021 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.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;
+    }
+}
diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/config/OpenWebNetZigBeeBridgeConfig.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/config/OpenWebNetZigBeeBridgeConfig.java
new file mode 100644 (file)
index 0000000..0271a2a
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2010-2021 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.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;
+    }
+}
diff --git a/bundles/org.openhab.binding.openwebnet/src/test/java/org/openhab/binding/openwebnet/handler/OwnIdTest.java b/bundles/org.openhab.binding.openwebnet/src/test/java/org/openhab/binding/openwebnet/handler/OwnIdTest.java
deleted file mode 100644 (file)
index ce8910e..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.mock;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.junit.jupiter.api.Test;
-import org.openhab.core.thing.Bridge;
-import org.openwebnet4j.message.BaseOpenMessage;
-import org.openwebnet4j.message.FrameException;
-import org.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereEnergyManagement;
-import org.openwebnet4j.message.WhereLightAutom;
-import org.openwebnet4j.message.WhereThermo;
-import org.openwebnet4j.message.WhereZigBee;
-import org.openwebnet4j.message.Who;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Test class for {@link OpenWebNetBridgeHandler#ownID} and ThingID calculation using {@link OpenWebNetBridgeHandler}
- * methods: normalizeWhere(), ownIdFromWhoWhere(), ownIdFromMessage(), thingIdFromWhere()
- *
- * @author Massimo Valla - Initial contribution
- * @author Andrea Conte - Energy management
- */
-@NonNullByDefault
-public class OwnIdTest {
-
-    private final Logger logger = LoggerFactory.getLogger(OwnIdTest.class);
-
- // @formatter:off
-    /**
-     *
-     *                                      deviceWhere
-     *                                      (DevAddrParam)
-     * TYPE                 WHERE           = normalizeWhere()  ownId           ThingID
-     * ---------------------------------------------------------------------------------
-     * Zigbee Switch        789309801#9     789309800h9         1.789309800h9   789309800h9
-     * Zigbee Switch_2u u1  789301201#9     789301200h9         1.789309800h9   789309800h9
-     * Zigbee Switch_2u u2  789301202#9     789301200h9         1.789309800h9   789309800h9
-     * BUS Switch           51              51                  1.51            51
-     * BUS Local Bus        25#4#01         25h4h01             1.25h4h01       25h4h01
-     * BUS Autom            93              93                  2.93            93
-     * BUS Thermo           #1 or 1         1                   4.1             1
-     * BUS Thermo actuator  1#2             1                   4.1             1
-     * BUS TempSensor       500             500                 4.500           500
-     * BUS Energy           51              51                  18.51           51
-     * -INACTIVE- BUS CEN              51              51                  15.51           51
-     * -INACTIVE- BUS CEN+             212             212                 25.212          212
-     * -INACTIVE- BUS DryContact       399             399                 25.399          399
-     *
-     */
-// @formatter:on
-
-    public enum TEST {
-        // @formatter:off
-        zb_switch(new WhereZigBee("789309801#9"), Who.fromValue(1), "*1*1*789309801#9##", "789309800h9", "1.789309800h9", "789309800h9"),
-        zb_switch_2u_1(new WhereZigBee("789301201#9"), Who.fromValue(1), "*1*1*789301201#9##", "789301200h9", "1.789301200h9", "789301200h9"),
-        zb_switch_2u_2(new WhereZigBee("789301202#9"), Who.fromValue(1), "*1*1*789301202#9##", "789301200h9", "1.789301200h9", "789301200h9"),
-        bus_switch(new WhereLightAutom("51"), Who.fromValue(1), "*1*1*51##", "51", "1.51", "51"),
-        bus_localbus(new WhereLightAutom("25#4#01"), Who.fromValue(1), "*1*1*25#4#01##", "25h4h01", "1.25h4h01", "25h4h01"),
-        bus_autom(new WhereLightAutom("93"), Who.fromValue(2), "*2*0*93##", "93", "2.93", "93"),
-        bus_thermo_via_cu(new WhereThermo("#1"), Who.fromValue(4),"*#4*#1*0*0020##" ,"1", "4.1", "1"),
-        bus_thermo(new WhereThermo("1"), Who.fromValue(4),"*#4*1*0*0020##" , "1", "4.1", "1"),
-        bus_thermo_act(new WhereThermo("1#2"), Who.fromValue(4),"*#4*1#2*20*0##" ,"1", "4.1", "1"),
-        bus_tempSensor(new WhereThermo("500"), Who.fromValue(4), "*#4*500*15*1*0020*0001##", "500", "4.500", "500"),
-        bus_energy(new WhereEnergyManagement("51"), Who.fromValue(18), "*#18*51*113##", "51", "18.51", "51");
-
-        // @formatter:on
-
-        private final Logger logger = LoggerFactory.getLogger(TEST.class);
-
-        public final Where where;
-        public final Who who;
-        public final @Nullable BaseOpenMessage msg;
-        public final String norm, ownId, thingId;
-
-        private TEST(Where where, Who who, String msg, String norm, String ownId, String thingId) {
-            this.where = where;
-            this.who = who;
-            BaseOpenMessage bmsg = null;
-            try {
-                bmsg = (BaseOpenMessage) BaseOpenMessage.parse(msg);
-            } catch (FrameException e) {
-                logger.warn("something is wrong in the test table ({}). ownIdFromMessage test will be skipped",
-                        e.getMessage());
-            }
-            this.msg = bmsg;
-            this.norm = norm;
-            this.ownId = ownId;
-            this.thingId = thingId;
-        }
-    }
-
-    @Test
-    public void testOwnId() {
-        Bridge mockBridge = mock(Bridge.class);
-        OpenWebNetBridgeHandler brH = new OpenWebNetBridgeHandler(mockBridge);
-        BaseOpenMessage bmsg;
-        for (int i = 0; i < TEST.values().length; i++) {
-            TEST test = TEST.values()[i];
-            logger.info("testing where={}", test.where);
-            assertEquals(test.norm, brH.normalizeWhere(test.where));
-            assertEquals(test.ownId, brH.ownIdFromWhoWhere(test.who, test.where));
-            bmsg = test.msg;
-            if (bmsg != null) {
-                assertEquals(test.ownId, brH.ownIdFromMessage(bmsg));
-            }
-            assertEquals(test.thingId, brH.thingIdFromWhere(test.where));
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.openwebnet/src/test/java/org/openhab/binding/openwebnet/internal/handler/OwnIdTest.java b/bundles/org.openhab.binding.openwebnet/src/test/java/org/openhab/binding/openwebnet/internal/handler/OwnIdTest.java
new file mode 100644 (file)
index 0000000..3fc51cb
--- /dev/null
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2010-2021 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.handler;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.junit.jupiter.api.Test;
+import org.openhab.core.thing.Bridge;
+import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.FrameException;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereEnergyManagement;
+import org.openwebnet4j.message.WhereLightAutom;
+import org.openwebnet4j.message.WhereThermo;
+import org.openwebnet4j.message.WhereZigBee;
+import org.openwebnet4j.message.Who;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test class for {@link OpenWebNetBridgeHandler#ownID} and ThingID
+ * calculation using {@link OpenWebNetBridgeHandler}
+ * methods: normalizeWhere(), ownIdFromWhoWhere(), ownIdFromMessage(), thingIdFromWhere()
+ *
+ * @author Massimo Valla - Initial contribution
+ * @author Andrea Conte - Energy management
+ */
+@NonNullByDefault
+public class OwnIdTest {
+
+    private final Logger logger = LoggerFactory.getLogger(OwnIdTest.class);
+
+ // @formatter:off
+    /**
+     *
+     *                                      deviceWhere
+     *                                      (DevAddrParam)
+     * TYPE                 WHERE           = normalizeWhere()  ownId           ThingID
+     * ---------------------------------------------------------------------------------
+     * Zigbee Switch        789309801#9     789309800h9         1.789309800h9   789309800h9
+     * Zigbee Switch_2u u1  789301201#9     789301200h9         1.789309800h9   789309800h9
+     * Zigbee Switch_2u u2  789301202#9     789301200h9         1.789309800h9   789309800h9
+     * BUS Switch           51              51                  1.51            51
+     * BUS Local Bus        25#4#01         25h4h01             1.25h4h01       25h4h01
+     * BUS Autom            93              93                  2.93            93
+     * BUS Thermo           #1 or 1         1                   4.1             1
+     * BUS Thermo actuator  1#2             1                   4.1             1
+     * BUS TempSensor       500             500                 4.500           500
+     * BUS Energy           51              51                  18.51           51
+     * -INACTIVE- BUS CEN              51              51                  15.51           51
+     * -INACTIVE- BUS CEN+             212             212                 25.212          212
+     * -INACTIVE- BUS DryContact       399             399                 25.399          399
+     *
+     */
+// @formatter:on
+
+    public enum TEST {
+        // @formatter:off
+        zb_switch(new WhereZigBee("789309801#9"), Who.fromValue(1), "*1*1*789309801#9##", "789309800h9", "1.789309800h9", "789309800h9"),
+        zb_switch_2u_1(new WhereZigBee("789301201#9"), Who.fromValue(1), "*1*1*789301201#9##", "789301200h9", "1.789301200h9", "789301200h9"),
+        zb_switch_2u_2(new WhereZigBee("789301202#9"), Who.fromValue(1), "*1*1*789301202#9##", "789301200h9", "1.789301200h9", "789301200h9"),
+        bus_switch(new WhereLightAutom("51"), Who.fromValue(1), "*1*1*51##", "51", "1.51", "51"),
+        bus_localbus(new WhereLightAutom("25#4#01"), Who.fromValue(1), "*1*1*25#4#01##", "25h4h01", "1.25h4h01", "25h4h01"),
+        bus_autom(new WhereLightAutom("93"), Who.fromValue(2), "*2*0*93##", "93", "2.93", "93"),
+        bus_thermo_via_cu(new WhereThermo("#1"), Who.fromValue(4),"*#4*#1*0*0020##" ,"1", "4.1", "1"),
+        bus_thermo(new WhereThermo("1"), Who.fromValue(4),"*#4*1*0*0020##" , "1", "4.1", "1"),
+        bus_thermo_act(new WhereThermo("1#2"), Who.fromValue(4),"*#4*1#2*20*0##" ,"1", "4.1", "1"),
+        bus_tempSensor(new WhereThermo("500"), Who.fromValue(4), "*#4*500*15*1*0020*0001##", "500", "4.500", "500"),
+        bus_energy(new WhereEnergyManagement("51"), Who.fromValue(18), "*#18*51*113##", "51", "18.51", "51");
+
+        // @formatter:on
+
+        private final Logger logger = LoggerFactory.getLogger(TEST.class);
+
+        public final Where where;
+        public final Who who;
+        public final @Nullable BaseOpenMessage msg;
+        public final String norm, ownId, thingId;
+
+        private TEST(Where where, Who who, String msg, String norm, String ownId, String thingId) {
+            this.where = where;
+            this.who = who;
+            BaseOpenMessage bmsg = null;
+            try {
+                bmsg = (BaseOpenMessage) BaseOpenMessage.parse(msg);
+            } catch (FrameException e) {
+                logger.warn("something is wrong in the test table ({}). ownIdFromMessage test will be skipped",
+                        e.getMessage());
+            }
+            this.msg = bmsg;
+            this.norm = norm;
+            this.ownId = ownId;
+            this.thingId = thingId;
+        }
+    }
+
+    @Test
+    public void testOwnId() {
+        Bridge mockBridge = mock(Bridge.class);
+        OpenWebNetBridgeHandler brH = new OpenWebNetBridgeHandler(mockBridge);
+        BaseOpenMessage bmsg;
+        for (int i = 0; i < TEST.values().length; i++) {
+            TEST test = TEST.values()[i];
+            logger.info("testing where={}", test.where);
+            assertEquals(test.norm, brH.normalizeWhere(test.where));
+            assertEquals(test.ownId, brH.ownIdFromWhoWhere(test.who, test.where));
+            bmsg = test.msg;
+            if (bmsg != null) {
+                assertEquals(test.ownId, brH.ownIdFromMessage(bmsg));
+            }
+            assertEquals(test.thingId, brH.thingIdFromWhere(test.where));
+        }
+    }
+}