From: M Valla <12682715+mvalla@users.noreply.github.com> Date: Sat, 17 Jul 2021 20:40:58 +0000 (+0200) Subject: [openwebnet] refactoring to internal package (#10995) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=bd1d8f4980f5ef055dbb21f81599e6cd206959f2;p=openhab-addons.git [openwebnet] refactoring to internal package (#10995) Signed-off-by: Massimo Valla --- 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 index b7fbf1492a..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/OpenWebNetBindingConstants.java +++ /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 GENERIC_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GENERIC_DEVICE); - // ## Lighting - public static final Set 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 AUTOMATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_AUTOMATION, - THING_TYPE_BUS_AUTOMATION); - - // ## Thermoregulation - public static final Set THERMOREGULATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_THERMO_ZONE, - THING_TYPE_BUS_THERMO_SENSOR); - - // ## Energy Management - public static final Set ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_ENERGY_METER); - - // ## Groups - public static final Set 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 BRIDGE_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_GATEWAY, - THING_TYPE_BUS_GATEWAY); - - public static final Set 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 index dbf26618eb..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetAutomationHandler.java +++ /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 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 index 713b832682..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetBridgeHandler.java +++ /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 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 registeredDevices = new ConcurrentHashMap<>(); - private Map 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 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> 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 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 index ff7060cad6..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetEnergyHandler.java +++ /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 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(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 index 374dd20d7c..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetGenericHandler.java +++ /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 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 index 554b4f0a5e..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetLightingHandler.java +++ /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 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 index b540a4bd2e..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetThermoregulationHandler.java +++ /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 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 index 1ff15ef96c..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/OpenWebNetThingHandler.java +++ /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 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 > State getAsQuantityTypeOrNull(@Nullable Number value, Unit 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 index a9e013df9f..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/config/OpenWebNetBusBridgeConfig.java +++ /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 index 4481405510..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/handler/config/OpenWebNetZigBeeBridgeConfig.java +++ /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 index 0000000000..1f039a06fa --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetBindingConstants.java @@ -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 GENERIC_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GENERIC_DEVICE); + // ## Lighting + public static final Set 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 AUTOMATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_AUTOMATION, + THING_TYPE_BUS_AUTOMATION); + + // ## Thermoregulation + public static final Set THERMOREGULATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_THERMO_ZONE, + THING_TYPE_BUS_THERMO_SENSOR); + + // ## Energy Management + public static final Set ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_ENERGY_METER); + + // ## Groups + public static final Set 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 BRIDGE_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_GATEWAY, + THING_TYPE_BUS_GATEWAY); + + public static final Set 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/internal/OpenWebNetHandlerFactory.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetHandlerFactory.java index 0d4205d36c..8b258df58c 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetHandlerFactory.java +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/OpenWebNetHandlerFactory.java @@ -12,16 +12,16 @@ */ 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; diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/BusGatewayUpnpDiscovery.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/BusGatewayUpnpDiscovery.java index a2bd6a66fc..2190cc9cb2 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/BusGatewayUpnpDiscovery.java +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/BusGatewayUpnpDiscovery.java @@ -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; diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/OpenWebNetDeviceDiscoveryService.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/OpenWebNetDeviceDiscoveryService.java index e6c58cf0a2..2a339cca5f 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/OpenWebNetDeviceDiscoveryService.java +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/OpenWebNetDeviceDiscoveryService.java @@ -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; diff --git a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/UsbGatewayDiscoveryService.java b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/UsbGatewayDiscoveryService.java index 6bc1b8d36a..a56d0d928c 100644 --- a/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/UsbGatewayDiscoveryService.java +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/discovery/UsbGatewayDiscoveryService.java @@ -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 index 0000000000..96ba3b1471 --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetAutomationHandler.java @@ -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 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 index 0000000000..2b9c83fd88 --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetBridgeHandler.java @@ -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 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 registeredDevices = new ConcurrentHashMap<>(); + private Map 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 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> 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 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 index 0000000000..386e45126d --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetEnergyHandler.java @@ -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 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(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 index 0000000000..760becaa65 --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetGenericHandler.java @@ -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 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 index 0000000000..095cfb970c --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetLightingHandler.java @@ -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 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 index 0000000000..7f3d814839 --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThermoregulationHandler.java @@ -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 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 index 0000000000..7e0ac4bfe3 --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/OpenWebNetThingHandler.java @@ -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 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 > State getAsQuantityTypeOrNull(@Nullable Number value, Unit 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 index 0000000000..e9000fae4b --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/config/OpenWebNetBusBridgeConfig.java @@ -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 index 0000000000..0271a2a95f --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/main/java/org/openhab/binding/openwebnet/internal/handler/config/OpenWebNetZigBeeBridgeConfig.java @@ -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 index ce8910e12d..0000000000 --- a/bundles/org.openhab.binding.openwebnet/src/test/java/org/openhab/binding/openwebnet/handler/OwnIdTest.java +++ /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 index 0000000000..3fc51cb546 --- /dev/null +++ b/bundles/org.openhab.binding.openwebnet/src/test/java/org/openhab/binding/openwebnet/internal/handler/OwnIdTest.java @@ -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)); + } + } +}