+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openwebnet;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.thing.ThingTypeUID;
-
-/**
- * The {@link OpenWebNetBindingConstants} class defines common constants, which are used across the whole binding.
- *
- * @author Massimo Valla - Initial contribution
- * @author Andrea Conte - Energy management, Thermoregulation
- * @author Gilberto Cocchi - Thermoregulation
- */
-
-@NonNullByDefault
-public class OpenWebNetBindingConstants {
-
- public static final String BINDING_ID = "openwebnet";
-
- public static final int THING_STATE_REQ_TIMEOUT_SEC = 5;
-
- // #LIST OF Thing Type UIDs
- // generic device (used for not identified devices)
- public static final ThingTypeUID THING_TYPE_GENERIC_DEVICE = new ThingTypeUID(BINDING_ID, "generic_device");
- public static final String THING_LABEL_GENERIC_DEVICE = "GENERIC Device";
- // bridges
- public static final ThingTypeUID THING_TYPE_ZB_GATEWAY = new ThingTypeUID(BINDING_ID, "zb_gateway");
- public static final String THING_LABEL_ZB_GATEWAY = "ZigBee USB Gateway";
- public static final ThingTypeUID THING_TYPE_BUS_GATEWAY = new ThingTypeUID(BINDING_ID, "bus_gateway");
- public static final String THING_LABEL_BUS_GATEWAY = "BUS Gateway";
- // other thing types
- // BUS
- public static final ThingTypeUID THING_TYPE_BUS_ON_OFF_SWITCH = new ThingTypeUID(BINDING_ID, "bus_on_off_switch");
- public static final String THING_LABEL_BUS_ON_OFF_SWITCH = "Switch";
- public static final ThingTypeUID THING_TYPE_BUS_DIMMER = new ThingTypeUID(BINDING_ID, "bus_dimmer");
- public static final String THING_LABEL_BUS_DIMMER = "Dimmer";
- public static final ThingTypeUID THING_TYPE_BUS_AUTOMATION = new ThingTypeUID(BINDING_ID, "bus_automation");
- public static final String THING_LABEL_BUS_AUTOMATION = "Automation";
- public static final ThingTypeUID THING_TYPE_BUS_ENERGY_METER = new ThingTypeUID(BINDING_ID, "bus_energy_meter");
- public static final String THING_LABEL_BUS_ENERGY_METER = "Energy Meter";
- public static final ThingTypeUID THING_TYPE_BUS_THERMO_SENSOR = new ThingTypeUID(BINDING_ID, "bus_thermo_sensor");
- public static final String THING_LABEL_BUS_THERMO_SENSOR = "Thermo Sensor";
- public static final ThingTypeUID THING_TYPE_BUS_THERMO_ZONE = new ThingTypeUID(BINDING_ID, "bus_thermo_zone");
- public static final String THING_LABEL_BUS_THERMO_ZONE = "Thermo Zone";
-
- // ZIGBEE
- public static final ThingTypeUID THING_TYPE_ZB_ON_OFF_SWITCH = new ThingTypeUID(BINDING_ID, "zb_on_off_switch");
- public static final String THING_LABEL_ZB_ON_OFF_SWITCH = "ZigBee Switch";
- public static final ThingTypeUID THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS = new ThingTypeUID(BINDING_ID,
- "zb_on_off_switch2u");
- public static final String THING_LABEL_ZB_ON_OFF_SWITCH_2UNITS = "ZigBee 2-units Switch";
- public static final ThingTypeUID THING_TYPE_ZB_DIMMER = new ThingTypeUID(BINDING_ID, "zb_dimmer");
- public static final String THING_LABEL_ZB_DIMMER = "ZigBee Dimmer";
- public static final ThingTypeUID THING_TYPE_ZB_AUTOMATION = new ThingTypeUID(BINDING_ID, "zb_automation");
- public static final String THING_LABEL_ZB_AUTOMATION = "ZigBee Automation";
-
- // #SUPPORTED THINGS SETS
- // ## Generic
- public static final Set<ThingTypeUID> GENERIC_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GENERIC_DEVICE);
- // ## Lighting
- public static final Set<ThingTypeUID> LIGHTING_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_ON_OFF_SWITCH,
- THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS, THING_TYPE_ZB_DIMMER, THING_TYPE_BUS_ON_OFF_SWITCH,
- THING_TYPE_BUS_DIMMER);
- // ## Automation
- public static final Set<ThingTypeUID> AUTOMATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_AUTOMATION,
- THING_TYPE_BUS_AUTOMATION);
-
- // ## Thermoregulation
- public static final Set<ThingTypeUID> THERMOREGULATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_THERMO_ZONE,
- THING_TYPE_BUS_THERMO_SENSOR);
-
- // ## Energy Management
- public static final Set<ThingTypeUID> ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_ENERGY_METER);
-
- // ## Groups
- public static final Set<ThingTypeUID> DEVICE_SUPPORTED_THING_TYPES = Stream
- .of(LIGHTING_SUPPORTED_THING_TYPES, AUTOMATION_SUPPORTED_THING_TYPES,
- THERMOREGULATION_SUPPORTED_THING_TYPES, ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES,
- GENERIC_SUPPORTED_THING_TYPES)
- .flatMap(Collection::stream).collect(Collectors.toCollection(HashSet::new));
-
- public static final Set<ThingTypeUID> BRIDGE_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_GATEWAY,
- THING_TYPE_BUS_GATEWAY);
-
- public static final Set<ThingTypeUID> ALL_SUPPORTED_THING_TYPES = Stream
- .of(DEVICE_SUPPORTED_THING_TYPES, BRIDGE_SUPPORTED_THING_TYPES).flatMap(Collection::stream)
- .collect(Collectors.toCollection(HashSet::new));
-
- // LIST OF ALL CHANNEL IDs
- // lighting
- public static final String CHANNEL_SWITCH = "switch";
- public static final String CHANNEL_SWITCH_01 = "switch_01";
- public static final String CHANNEL_SWITCH_02 = "switch_02";
- public static final String CHANNEL_BRIGHTNESS = "brightness";
-
- // automation
- public static final String CHANNEL_SHUTTER = "shutter";
-
- // thermo
- public static final String CHANNEL_TEMPERATURE = "temperature";
- public static final String CHANNEL_FUNCTION = "function";
- public static final String CHANNEL_TEMP_SETPOINT = "setpointTemperature";
- public static final String CHANNEL_MODE = "mode";
- public static final String CHANNEL_FAN_SPEED = "speedFanCoil";
- public static final String CHANNEL_CONDITIONING_VALVES = "conditioningValves";
- public static final String CHANNEL_HEATING_VALVES = "heatingValves";
- public static final String CHANNEL_ACTUATORS = "actuators";
-
- // energy management
- public static final String CHANNEL_POWER = "power";
-
- // devices config properties
- public static final String CONFIG_PROPERTY_WHERE = "where";
- public static final String CONFIG_PROPERTY_SHUTTER_RUN = "shutterRun";
-
- // BUS gw config properties
- public static final String CONFIG_PROPERTY_HOST = "host";
- public static final String CONFIG_PROPERTY_SERIAL_PORT = "serialPort";
-
- // properties
- public static final String PROPERTY_OWNID = "ownId";
- public static final String PROPERTY_ZIGBEEID = "zigbeeid";
- public static final String PROPERTY_FIRMWARE_VERSION = "firmwareVersion";
- public static final String PROPERTY_MODEL = "model";
- public static final String PROPERTY_SERIAL_NO = "serialNumber";
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openwebnet.handler;
-
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_SHUTTER;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Set;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
-import org.openhab.core.config.core.Configuration;
-import org.openhab.core.library.types.PercentType;
-import org.openhab.core.library.types.StopMoveType;
-import org.openhab.core.library.types.UpDownType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.UnDefType;
-import org.openwebnet4j.OpenGateway;
-import org.openwebnet4j.communication.OWNException;
-import org.openwebnet4j.message.Automation;
-import org.openwebnet4j.message.BaseOpenMessage;
-import org.openwebnet4j.message.FrameException;
-import org.openwebnet4j.message.GatewayMgmt;
-import org.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereLightAutom;
-import org.openwebnet4j.message.Who;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link OpenWebNetAutomationHandler} is responsible for handling commands/messages for an Automation OpenWebNet
- * device. It extends the abstract {@link OpenWebNetThingHandler}.
- *
- * @author Massimo Valla - Initial contribution
- */
-@NonNullByDefault
-public class OpenWebNetAutomationHandler extends OpenWebNetThingHandler {
-
- private final Logger logger = LoggerFactory.getLogger(OpenWebNetAutomationHandler.class);
-
- private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("ss.SSS");
-
- private static long lastAllDevicesRefreshTS = -1; // timestamp when the last request for all device refresh was sent
- protected static final int ALL_DEVICES_REFRESH_INTERVAL_MSEC = 60000; // interval in msec before sending another all
- // devices refresh request
-
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.AUTOMATION_SUPPORTED_THING_TYPES;
-
- // moving states
- public static final int MOVING_STATE_STOPPED = 0;
- public static final int MOVING_STATE_MOVING_UP = 1;
- public static final int MOVING_STATE_MOVING_DOWN = 2;
- public static final int MOVING_STATE_UNKNOWN = -1;
-
- // calibration states
- public static final int CALIBRATION_INACTIVE = -1;
- public static final int CALIBRATION_ACTIVATED = 0;
- public static final int CALIBRATION_GOING_UP = 1;
- public static final int CALIBRATION_GOING_DOWN = 2;
-
- // positions
- public static final int POSITION_MAX_STEPS = 100;
- public static final int POSITION_DOWN = 100;
- public static final int POSITION_UP = 0;
- public static final int POSITION_UNKNOWN = -1;
-
- public static final int SHUTTER_RUN_UNDEFINED = -1;
- private int shutterRun = SHUTTER_RUN_UNDEFINED;
- private static final String AUTO_CALIBRATION = "AUTO";
-
- private long startedMovingAtTS = -1; // timestamp when device started moving UP/DOWN
- private int movingState = MOVING_STATE_UNKNOWN;
- private int positionEstimation = POSITION_UNKNOWN;
- private @Nullable ScheduledFuture<?> moveSchedule;
- private int positionRequested = POSITION_UNKNOWN;
- private int calibrating = CALIBRATION_INACTIVE;
- private static final int MIN_STEP_TIME_MSEC = 50;
- private @Nullable Command commandRequestedWhileMoving = null;
-
- public OpenWebNetAutomationHandler(Thing thing) {
- super(thing);
- }
-
- @Override
- public void initialize() {
- super.initialize();
- Object shutterRunConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN);
- try {
- if (shutterRunConfig == null) {
- shutterRunConfig = AUTO_CALIBRATION;
- logger.debug("shutterRun null --> default to AUTO");
- } else if (shutterRunConfig instanceof String) {
- if (AUTO_CALIBRATION.equalsIgnoreCase(((String) shutterRunConfig))) {
- logger.debug("shutterRun set to AUTO via configuration");
- shutterRun = SHUTTER_RUN_UNDEFINED; // reset shutterRun
- } else { // try to parse int>=1000
- int shutterRunInt = Integer.parseInt((String) shutterRunConfig);
- if (shutterRunInt < 1000) {
- throw new NumberFormatException();
- }
- shutterRun = shutterRunInt;
- logger.debug("shutterRun set to {} via configuration", shutterRun);
- }
- } else {
- throw new NumberFormatException();
- }
- } catch (NumberFormatException e) {
- logger.debug("Wrong configuration: {} setting must be {} or an integer >= 1000",
- OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN, AUTO_CALIBRATION);
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
- "@text/offline.wrong-configuration");
- shutterRun = SHUTTER_RUN_UNDEFINED;
- }
- updateState(CHANNEL_SHUTTER, UnDefType.UNDEF);
- positionEstimation = POSITION_UNKNOWN;
- }
-
- @Override
- protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
- return new WhereLightAutom(wStr);
- }
-
- @Override
- protected void requestChannelState(ChannelUID channel) {
- logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
- Where w = deviceWhere;
- if (w != null) {
- try {
- send(Automation.requestStatus(w.value()));
- } catch (OWNException e) {
- logger.debug("Exception while requesting channel {} state: {}", channel, e.getMessage(), e);
- }
- } else {
- logger.warn("Could not requestChannelState(): deviceWhere is null");
- }
- }
-
- @Override
- protected void refreshDevice(boolean refreshAll) {
- OpenWebNetBridgeHandler brH = bridgeHandler;
- if (brH != null) {
- if (brH.isBusGateway() && refreshAll) {
- long now = System.currentTimeMillis();
- if (now - lastAllDevicesRefreshTS > ALL_DEVICES_REFRESH_INTERVAL_MSEC) {
- try {
- send(Automation.requestStatus(WhereLightAutom.GENERAL.value()));
- lastAllDevicesRefreshTS = now;
- } catch (OWNException e) {
- logger.warn("Excpetion while requesting all devices refresh: {}", e.getMessage());
- }
- } else {
- logger.debug("Refresh all devices just sent...");
- }
- } else {
- requestChannelState(new ChannelUID("any")); // channel here does not make any difference
- }
- }
- }
-
- @Override
- protected void handleChannelCommand(ChannelUID channel, Command command) {
- switch (channel.getId()) {
- case CHANNEL_SHUTTER:
- handleShutterCommand(command);
- break;
- default: {
- logger.info("Unsupported channel UID {}", channel);
- }
- }
- }
-
- /**
- * Handles Automation Roller shutter command (UP/DOWN, STOP/MOVE, PERCENT xx%)
- */
- private void handleShutterCommand(Command command) {
- Where w = deviceWhere;
- if (w != null) {
- calibrating = CALIBRATION_INACTIVE; // cancel calibration if we receive a command
- commandRequestedWhileMoving = null;
- try {
- if (StopMoveType.STOP.equals(command)) {
- send(Automation.requestStop(w.value()));
- } else if (command instanceof UpDownType || command instanceof PercentType) {
- if (movingState == MOVING_STATE_MOVING_UP || movingState == MOVING_STATE_MOVING_DOWN) { // already
- // moving
- logger.debug("# {} # already moving, STOP then defer command", deviceWhere);
- commandRequestedWhileMoving = command;
- sendHighPriority(Automation.requestStop(w.value()));
- return;
- } else {
- if (command instanceof UpDownType) {
- if (UpDownType.UP.equals(command)) {
- send(Automation.requestMoveUp(w.value()));
- } else {
- send(Automation.requestMoveDown(w.value()));
- }
- } else if (command instanceof PercentType) {
- handlePercentCommand((PercentType) command, w.value());
- }
- }
- } else {
- logger.debug("Unsupported command {} for thing {}", command, thing.getUID());
- }
- } catch (OWNException e) {
- logger.debug("Exception while sending request for command {}: {}", command, e.getMessage(), e);
- }
- }
- }
-
- /**
- * Handles Automation PERCENT xx% command
- */
- private void handlePercentCommand(PercentType command, String w) {
- int percent = command.intValue();
- if (percent == positionEstimation) {
- logger.debug("# {} # handlePercentCommand() Command {}% == positionEstimation -> nothing to do", w,
- percent);
- return;
- }
- try {
- if (percent == POSITION_DOWN) { // GO TO 100%
- send(Automation.requestMoveDown(w));
- } else if (percent == POSITION_UP) { // GO TO 0%
- send(Automation.requestMoveUp(w));
- } else { // GO TO XX%
- logger.debug("# {} # {}% requested", deviceWhere, percent);
- if (shutterRun == SHUTTER_RUN_UNDEFINED) {
- logger.debug("& {} & CALIBRATION - shutterRun not configured, starting CALIBRATION...",
- deviceWhere);
- calibrating = CALIBRATION_ACTIVATED;
- send(Automation.requestMoveUp(w));
- positionRequested = percent;
- } else if (shutterRun >= 1000 && positionEstimation != POSITION_UNKNOWN) {
- // these two must be known to calculate moveTime.
- // Calculate how much time we have to move and set a deadline to stop after that time
- int moveTime = Math
- .round(((float) Math.abs(percent - positionEstimation) / POSITION_MAX_STEPS * shutterRun));
- logger.debug("# {} # target moveTime={}", deviceWhere, moveTime);
- if (moveTime > MIN_STEP_TIME_MSEC) {
- ScheduledFuture<?> mSch = moveSchedule;
- if (mSch != null && !mSch.isDone()) {
- // a moveSchedule was already scheduled and is not done... let's cancel the schedule
- mSch.cancel(false);
- logger.debug("# {} # new XX% requested, old moveSchedule cancelled", deviceWhere);
- }
- // send a requestFirmwareVersion message to BUS gateways to wake up the CMD connection before
- // sending further cmds
- OpenWebNetBridgeHandler h = bridgeHandler;
- if (h != null && h.isBusGateway()) {
- OpenGateway gw = h.gateway;
- if (gw != null) {
- if (!gw.isCmdConnectionReady()) {
- logger.debug("# {} # waking-up CMD connection...", deviceWhere);
- send(GatewayMgmt.requestFirmwareVersion());
- }
- }
- }
- // REMINDER: start the schedule BEFORE sending the command, because the synch command waits for
- // ACK and can take some 300ms
- logger.debug("# {} # Starting schedule...", deviceWhere);
- moveSchedule = scheduler.schedule(() -> {
- logger.debug("# {} # moveSchedule expired, sending STOP...", deviceWhere);
- try {
- sendHighPriority(Automation.requestStop(w));
- } catch (OWNException ex) {
- logger.debug("Exception while sending request for command {}: {}", command,
- ex.getMessage(), ex);
- }
- }, moveTime, TimeUnit.MILLISECONDS);
- logger.debug("# {} # ...schedule started, now sending highPriority command...", deviceWhere);
- if (percent < positionEstimation) {
- sendHighPriority(Automation.requestMoveUp(w));
- } else {
- sendHighPriority(Automation.requestMoveDown(w));
- }
- logger.debug("# {} # ...gateway.sendHighPriority() returned", deviceWhere);
- } else {
- logger.debug("# {} # moveTime <= MIN_STEP_TIME_MSEC ---> do nothing", deviceWhere);
- }
- } else {
- logger.info(
- "Command {} cannot be executed: unknown position or shutterRun configuration params not/wrongly set (thing={})",
- command, thing.getUID());
- }
- }
- } catch (OWNException e) {
- logger.debug("Exception while sending request for command {}: {}", command, e.getMessage(), e);
- }
- }
-
- @Override
- protected String ownIdPrefix() {
- return Who.AUTOMATION.value().toString();
- }
-
- @Override
- protected void handleMessage(BaseOpenMessage msg) {
- logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
- updateAutomationState((Automation) msg);
- // REMINDER: update automation state, and only after update thing status using the super method, to avoid delays
- super.handleMessage(msg);
- }
-
- /**
- * Updates automation device state based on the Automation message received from OWN network
- *
- * @param msg the Automation message
- */
- private void updateAutomationState(Automation msg) {
- logger.debug("updateAutomationState() - msg={} what={}", msg, msg.getWhat());
- try {
- if (msg.isCommandTranslation()) {
- logger.debug("msg is command translation, ignoring it");
- return;
- }
- } catch (FrameException fe) {
- logger.warn("Exception while checking WHERE command translation for frame {}: {}, ignoring it", msg,
- fe.getMessage());
- }
- if (msg.isUp()) {
- updateMovingState(MOVING_STATE_MOVING_UP);
- if (calibrating == CALIBRATION_ACTIVATED) {
- calibrating = CALIBRATION_GOING_UP;
- logger.debug("& {} & CALIBRATION - started going ALL UP...", deviceWhere);
- }
- } else if (msg.isDown()) {
- updateMovingState(MOVING_STATE_MOVING_DOWN);
- if (calibrating == CALIBRATION_ACTIVATED) {
- calibrating = CALIBRATION_GOING_DOWN;
- logger.debug("& {} & CALIBRATION - started going ALL DOWN...", deviceWhere);
- }
- } else if (msg.isStop()) {
- long stoppedAt = System.currentTimeMillis();
- if (calibrating == CALIBRATION_GOING_DOWN && shutterRun == SHUTTER_RUN_UNDEFINED) {
- shutterRun = (int) (stoppedAt - startedMovingAtTS);
- logger.debug("& {} & CALIBRATION - reached DOWN ---> shutterRun={}", deviceWhere, shutterRun);
- updateMovingState(MOVING_STATE_STOPPED);
- logger.debug("& {} & CALIBRATION - COMPLETED, now going to {}%", deviceWhere, positionRequested);
- handleShutterCommand(new PercentType(positionRequested));
- Configuration configuration = editConfiguration();
- configuration.put(OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN, Integer.toString(shutterRun));
- updateConfiguration(configuration);
- logger.debug("& {} & CALIBRATION - configuration updated: shutterRun = {}ms", deviceWhere, shutterRun);
- } else if (calibrating == CALIBRATION_GOING_UP) {
- updateMovingState(MOVING_STATE_STOPPED);
- logger.debug("& {} & CALIBRATION - reached UP, now sending DOWN command...", deviceWhere);
- calibrating = CALIBRATION_ACTIVATED;
- Where dw = deviceWhere;
- if (dw != null) {
- String w = dw.value();
- try {
- send(Automation.requestMoveDown(w));
- } catch (OWNException e) {
- logger.debug("Exception while sending DOWN command during calibration: {}", e.getMessage(), e);
- calibrating = CALIBRATION_INACTIVE;
- }
- }
- } else {
- updateMovingState(MOVING_STATE_STOPPED);
- // do deferred command, if present
- Command cmd = commandRequestedWhileMoving;
- if (cmd != null) {
- handleShutterCommand(cmd);
- }
- }
- } else {
- logger.debug("Frame {} not supported for thing {}, ignoring it.", msg, thing.getUID());
- }
- }
-
- /**
- * Updates movingState to newState
- */
- private void updateMovingState(int newState) {
- if (movingState == MOVING_STATE_STOPPED) {
- if (newState != MOVING_STATE_STOPPED) { // moving after stop
- startedMovingAtTS = System.currentTimeMillis();
- synchronized (DATE_FORMATTER) {
- logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAtTS,
- DATE_FORMATTER.format(new Date(startedMovingAtTS)));
- }
- }
- } else { // we were moving
- updatePosition();
- if (newState != MOVING_STATE_STOPPED) { // moving after moving, take new timestamp
- startedMovingAtTS = System.currentTimeMillis();
- synchronized (DATE_FORMATTER) {
- logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAtTS,
- DATE_FORMATTER.format(new Date(startedMovingAtTS)));
- }
- }
- // cancel the schedule
- ScheduledFuture<?> mSc = moveSchedule;
- if (mSc != null && !mSc.isDone()) {
- mSc.cancel(false);
- }
- }
- movingState = newState;
- logger.debug("# {} # movingState={} positionEstimation={} - calibrating={} shutterRun={}", deviceWhere,
- movingState, positionEstimation, calibrating, shutterRun);
- }
-
- /**
- * Updates positionEstimation and then channel state based on movedTime and current movingState
- */
- private void updatePosition() {
- int newPos = POSITION_UNKNOWN;
- if (shutterRun > 0 && startedMovingAtTS != -1) {// we have shutterRun and startedMovingAtTS defined, let's
- // calculate new positionEstimation
- long movedTime = System.currentTimeMillis() - startedMovingAtTS;
- logger.debug("# {} # current positionEstimation={} movedTime={}", deviceWhere, positionEstimation,
- movedTime);
- int movedSteps = Math.round((float) movedTime / shutterRun * POSITION_MAX_STEPS);
- logger.debug("# {} # movedSteps: {} {}", deviceWhere, movedSteps,
- (movingState == MOVING_STATE_MOVING_DOWN) ? "DOWN(+)" : "UP(-)");
- if (positionEstimation == POSITION_UNKNOWN && movedSteps >= POSITION_MAX_STEPS) { // we did a full run
- newPos = (movingState == MOVING_STATE_MOVING_DOWN) ? POSITION_DOWN : POSITION_UP;
- } else if (positionEstimation != POSITION_UNKNOWN) {
- newPos = positionEstimation
- + ((movingState == MOVING_STATE_MOVING_DOWN) ? movedSteps : -1 * movedSteps);
- logger.debug("# {} # {} {} {} = {}", deviceWhere, positionEstimation,
- (movingState == MOVING_STATE_MOVING_DOWN) ? "+" : "-", movedSteps, newPos);
- if (newPos > POSITION_DOWN) {
- newPos = POSITION_DOWN;
- } else if (newPos < POSITION_UP) {
- newPos = POSITION_UP;
- }
- }
- }
- if (newPos != POSITION_UNKNOWN) {
- if (newPos != positionEstimation) {
- updateState(CHANNEL_SHUTTER, new PercentType(newPos));
- }
- } else {
- updateState(CHANNEL_SHUTTER, UnDefType.UNDEF);
- }
- positionEstimation = newPos;
- }
-
- @Override
- public void dispose() {
- ScheduledFuture<?> mSc = moveSchedule;
- if (mSc != null) {
- mSc.cancel(true);
- }
- super.dispose();
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openwebnet.handler;
-
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.PROPERTY_FIRMWARE_VERSION;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.PROPERTY_SERIAL_NO;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.THING_TYPE_ZB_GATEWAY;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
-import org.openhab.binding.openwebnet.handler.config.OpenWebNetBusBridgeConfig;
-import org.openhab.binding.openwebnet.handler.config.OpenWebNetZigBeeBridgeConfig;
-import org.openhab.binding.openwebnet.internal.discovery.OpenWebNetDeviceDiscoveryService;
-import org.openhab.core.config.core.status.ConfigStatusMessage;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
-import org.openhab.core.thing.binding.ThingHandlerService;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.RefreshType;
-import org.openwebnet4j.BUSGateway;
-import org.openwebnet4j.GatewayListener;
-import org.openwebnet4j.OpenDeviceType;
-import org.openwebnet4j.OpenGateway;
-import org.openwebnet4j.USBGateway;
-import org.openwebnet4j.communication.OWNAuthException;
-import org.openwebnet4j.communication.OWNException;
-import org.openwebnet4j.message.Automation;
-import org.openwebnet4j.message.BaseOpenMessage;
-import org.openwebnet4j.message.EnergyManagement;
-import org.openwebnet4j.message.FrameException;
-import org.openwebnet4j.message.GatewayMgmt;
-import org.openwebnet4j.message.Lighting;
-import org.openwebnet4j.message.OpenMessage;
-import org.openwebnet4j.message.Thermoregulation;
-import org.openwebnet4j.message.What;
-import org.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereZigBee;
-import org.openwebnet4j.message.Who;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link OpenWebNetBridgeHandler} is responsible for handling communication with gateways and handling events.
- *
- * @author Massimo Valla - Initial contribution
- * @author Andrea Conte - Energy management, Thermoregulation
- * @author Gilberto Cocchi - Thermoregulation
- */
-@NonNullByDefault
-public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implements GatewayListener {
-
- private final Logger logger = LoggerFactory.getLogger(OpenWebNetBridgeHandler.class);
-
- private static final int GATEWAY_ONLINE_TIMEOUT_SEC = 20; // Time to wait for the gateway to become connected
-
- private static final int REFRESH_ALL_DEVICES_DELAY_MSEC = 500; // Delay to wait before sending all devices refresh
- // request after a connect/reconnect
-
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.BRIDGE_SUPPORTED_THING_TYPES;
-
- // ConcurrentHashMap of devices registered to this BridgeHandler
- // association is: ownId (String) -> OpenWebNetThingHandler, with ownId = WHO.WHERE
- private Map<String, @Nullable OpenWebNetThingHandler> registeredDevices = new ConcurrentHashMap<>();
- private Map<String, Long> discoveringDevices = new ConcurrentHashMap<>();
-
- protected @Nullable OpenGateway gateway;
- private boolean isBusGateway = false;
-
- private boolean isGatewayConnected = false;
-
- public @Nullable OpenWebNetDeviceDiscoveryService deviceDiscoveryService;
- private boolean reconnecting = false; // we are trying to reconnect to gateway
- private @Nullable ScheduledFuture<?> refreshSchedule;
-
- private boolean scanIsActive = false; // a device scan has been activated by OpenWebNetDeviceDiscoveryService;
- private boolean discoveryByActivation;
-
- public OpenWebNetBridgeHandler(Bridge bridge) {
- super(bridge);
- }
-
- public boolean isBusGateway() {
- return isBusGateway;
- }
-
- @Override
- public void initialize() {
- ThingTypeUID thingType = getThing().getThingTypeUID();
- OpenGateway gw;
- if (thingType.equals(THING_TYPE_ZB_GATEWAY)) {
- gw = initZigBeeGateway();
- } else {
- gw = initBusGateway();
- isBusGateway = true;
- }
- if (gw != null) {
- gateway = gw;
- gw.subscribe(this);
- if (gw.isConnected()) { // gateway is already connected, device can go ONLINE
- isGatewayConnected = true;
- updateStatus(ThingStatus.ONLINE);
- } else {
- updateStatus(ThingStatus.UNKNOWN);
- logger.debug("Trying to connect gateway {}... ", gw);
- try {
- gw.connect();
- scheduler.schedule(() -> {
- // if status is still UNKNOWN after timer ends, set the device as OFFLINE
- if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
- logger.info("status still UNKNOWN. Setting device={} to OFFLINE", thing.getUID());
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
- "@text/offline.comm-error-timeout");
- }
- }, GATEWAY_ONLINE_TIMEOUT_SEC, TimeUnit.SECONDS);
- logger.debug("bridge {} initialization completed", thing.getUID());
- } catch (OWNException e) {
- logger.debug("gw.connect() returned OWNException: {}", e.getMessage());
- // status is updated by callback onConnectionError()
- }
- }
- }
- }
-
- /**
- * Init a ZigBee gateway based on config
- */
- private @Nullable OpenGateway initZigBeeGateway() {
- logger.debug("Initializing ZigBee USB Gateway");
- OpenWebNetZigBeeBridgeConfig zbBridgeConfig = getConfigAs(OpenWebNetZigBeeBridgeConfig.class);
- String serialPort = zbBridgeConfig.getSerialPort();
- if (serialPort == null || serialPort.isEmpty()) {
- logger.warn("Cannot connect ZigBee USB Gateway. No serial port has been provided in Bridge configuration.");
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "@text/offline.conf-error-no-serial-port");
- return null;
- } else {
- return new USBGateway(serialPort);
- }
- }
-
- /**
- * Init a BUS gateway based on config
- */
- private @Nullable OpenGateway initBusGateway() {
- logger.debug("Initializing BUS gateway");
- OpenWebNetBusBridgeConfig busBridgeConfig = getConfigAs(OpenWebNetBusBridgeConfig.class);
- String host = busBridgeConfig.getHost();
- if (host == null || host.isEmpty()) {
- logger.warn("Cannot connect to BUS Gateway. No host/IP has been provided in Bridge configuration.");
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "@text/offline.conf-error-no-ip-address");
- return null;
- } else {
- int port = busBridgeConfig.getPort().intValue();
- String passwd = busBridgeConfig.getPasswd();
- String passwdMasked;
- if (passwd.length() >= 4) {
- passwdMasked = "******" + passwd.substring(passwd.length() - 3, passwd.length());
- } else {
- passwdMasked = "******";
- }
- discoveryByActivation = busBridgeConfig.getDiscoveryByActivation();
- logger.debug("Creating new BUS gateway with config properties: {}:{}, pwd={}, discoveryByActivation={}",
- host, port, passwdMasked, discoveryByActivation);
- return new BUSGateway(host, port, passwd);
- }
- }
-
- @Override
- public void handleCommand(ChannelUID channelUID, Command command) {
- logger.debug("handleCommand (command={} - channel={})", command, channelUID);
- OpenGateway gw = gateway;
- if (gw == null || !gw.isConnected()) {
- logger.warn("Gateway is NOT connected, skipping command");
- return;
- } else {
- if (command instanceof RefreshType) {
- refreshAllDevices();
- } else {
- logger.warn("Command or channel not supported: channel={} command={}", channelUID, command);
- }
- }
- }
-
- @Override
- public Collection<ConfigStatusMessage> getConfigStatus() {
- return Collections.emptyList();
- }
-
- @Override
- public void handleRemoval() {
- disconnectGateway();
- super.handleRemoval();
- }
-
- @Override
- public void dispose() {
- ScheduledFuture<?> rSc = refreshSchedule;
- if (rSc != null) {
- rSc.cancel(true);
- }
- disconnectGateway();
- super.dispose();
- }
-
- private void disconnectGateway() {
- OpenGateway gw = gateway;
- if (gw != null) {
- gw.closeConnection();
- gw.unsubscribe(this);
- logger.debug("Gateway {} connection closed and unsubscribed", gw.toString());
- gateway = null;
- }
- reconnecting = false;
- }
-
- @Override
- public Collection<Class<? extends ThingHandlerService>> getServices() {
- return Collections.singleton(OpenWebNetDeviceDiscoveryService.class);
- }
-
- /**
- * Search for devices connected to this bridge handler's gateway
- *
- * @param listener to receive device found notifications
- */
- public synchronized void searchDevices() {
- scanIsActive = true;
- logger.debug("------$$ scanIsActive={}", scanIsActive);
- OpenGateway gw = gateway;
- if (gw != null) {
- if (!gw.isDiscovering()) {
- if (!gw.isConnected()) {
- logger.debug("------$$ Gateway '{}' is NOT connected, cannot search for devices", gw);
- return;
- }
- logger.info("------$$ STARTED active SEARCH for devices on bridge '{}'", thing.getUID());
- try {
- gw.discoverDevices();
- } catch (OWNException e) {
- logger.warn("------$$ OWNException while discovering devices on bridge '{}': {}", thing.getUID(),
- e.getMessage());
- }
- } else {
- logger.debug("------$$ Searching devices on bridge '{}' already activated", thing.getUID());
- return;
- }
- } else {
- logger.warn("------$$ Cannot search devices: no gateway associated to this handler");
- }
- }
-
- @Override
- public void onNewDevice(@Nullable Where w, @Nullable OpenDeviceType deviceType, @Nullable BaseOpenMessage message) {
- OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
- if (discService != null) {
- if (w != null && deviceType != null) {
- discService.newDiscoveryResult(w, deviceType, message);
- } else {
- logger.warn("onNewDevice with null where/deviceType, msg={}", message);
- }
- } else {
- logger.warn("onNewDevice but null deviceDiscoveryService");
- }
- }
-
- @Override
- public void onDiscoveryCompleted() {
- logger.info("------$$ FINISHED active SEARCH for devices on bridge '{}'", thing.getUID());
- }
-
- /**
- * Notifies that the scan has been stopped/aborted by OpenWebNetDeviceDiscoveryService
- */
- public void scanStopped() {
- scanIsActive = false;
- logger.debug("------$$ scanIsActive={}", scanIsActive);
- }
-
- private void discoverByActivation(BaseOpenMessage baseMsg) {
- logger.debug("discoverByActivation: msg={}", baseMsg);
- OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
- if (discService == null) {
- logger.warn("discoverByActivation: null OpenWebNetDeviceDiscoveryService, ignoring msg={}", baseMsg);
- return;
- }
- // we support these types only
- if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
- || baseMsg instanceof Thermoregulation) {
- BaseOpenMessage bmsg = baseMsg;
- if (baseMsg instanceof Lighting) {
- What what = baseMsg.getWhat();
- if (Lighting.WhatLighting.OFF.equals(what)) { // skipping OFF msg: cannot distinguish dimmer/switch
- logger.debug("discoverByActivation: skipping OFF msg: cannot distinguish dimmer/switch");
- return;
- }
- if (Lighting.WhatLighting.ON.equals(what)) { // if not already done just now, request light status to
- // distinguish dimmer from switch
- if (discoveringDevices.containsKey(ownIdFromMessage(baseMsg))) {
- logger.debug(
- "discoverByActivation: we just requested status for this device and it's ON -> it's a switch");
- } else {
- OpenGateway gw = gateway;
- if (gw != null) {
- try {
- discoveringDevices.put(ownIdFromMessage(baseMsg),
- Long.valueOf(System.currentTimeMillis()));
- gw.send(Lighting.requestStatus(baseMsg.getWhere().value()));
- return;
- } catch (OWNException e) {
- logger.warn("discoverByActivation: Exception while requesting light state: {}",
- e.getMessage());
- return;
- }
- }
- }
- }
- discoveringDevices.remove(ownIdFromMessage(baseMsg));
- }
- OpenDeviceType type = null;
- try {
- type = bmsg.detectDeviceType();
- } catch (FrameException e) {
- logger.warn("Exception while detecting device type: {}", e.getMessage());
- }
- if (type != null) {
- discService.newDiscoveryResult(bmsg.getWhere(), type, bmsg);
- } else {
- logger.debug("discoverByActivation: no device type detected from msg: {}", bmsg);
- }
- }
- }
-
- /**
- * Register a device ThingHandler to this BridgHandler
- *
- * @param ownId the device OpenWebNet id
- * @param thingHandler the thing handler to be registered
- */
- protected void registerDevice(String ownId, OpenWebNetThingHandler thingHandler) {
- if (registeredDevices.containsKey(ownId)) {
- logger.warn("registering device with an existing ownId={}", ownId);
- }
- registeredDevices.put(ownId, thingHandler);
- logger.debug("registered device ownId={}, thing={}", ownId, thingHandler.getThing().getUID());
- }
-
- /**
- * Un-register a device from this bridge handler
- *
- * @param ownId the device OpenWebNet id
- */
- protected void unregisterDevice(String ownId) {
- if (registeredDevices.remove(ownId) != null) {
- logger.debug("un-registered device ownId={}", ownId);
- } else {
- logger.warn("could not un-register ownId={} (not found)", ownId);
- }
- }
-
- /**
- * Get an already registered device on this bridge handler
- *
- * @param ownId the device OpenWebNet id
- * @return the registered device Thing handler or null if the id cannot be found
- */
- public @Nullable OpenWebNetThingHandler getRegisteredDevice(String ownId) {
- return registeredDevices.get(ownId);
- }
-
- private void refreshAllDevices() {
- logger.debug("Refreshing all devices for bridge {}", thing.getUID());
- for (Thing ownThing : getThing().getThings()) {
- OpenWebNetThingHandler hndlr = (OpenWebNetThingHandler) ownThing.getHandler();
- if (hndlr != null) {
- hndlr.refreshDevice(true);
- }
- }
- }
-
- @Override
- public void onEventMessage(@Nullable OpenMessage msg) {
- logger.trace("RECEIVED <<<<< {}", msg);
- if (msg == null) {
- logger.warn("received event msg is null");
- return;
- }
- if (msg.isACK() || msg.isNACK()) {
- return; // we ignore ACKS/NACKS
- }
- // GATEWAY MANAGEMENT
- if (msg instanceof GatewayMgmt) {
- // noop
- return;
- }
-
- BaseOpenMessage baseMsg = (BaseOpenMessage) msg;
- // let's try to get the Thing associated with this message...
- if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
- || baseMsg instanceof Thermoregulation) {
- String ownId = ownIdFromMessage(baseMsg);
- logger.debug("ownIdFromMessage({}) --> {}", baseMsg, ownId);
- OpenWebNetThingHandler deviceHandler = registeredDevices.get(ownId);
- if (deviceHandler == null) {
- OpenGateway gw = gateway;
- if (isBusGateway && ((gw != null && !gw.isDiscovering() && scanIsActive)
- || (discoveryByActivation && !scanIsActive))) {
- discoverByActivation(baseMsg);
- } else {
- logger.debug("ownId={} has NO DEVICE associated, ignoring it", ownId);
- }
- } else {
- deviceHandler.handleMessage(baseMsg);
- }
- } else {
- logger.debug("BridgeHandler ignoring frame {}. WHO={} is not supported by this binding", baseMsg,
- baseMsg.getWho());
- }
- }
-
- @Override
- public void onConnected() {
- isGatewayConnected = true;
- Map<String, String> properties = editProperties();
- boolean propertiesChanged = false;
- OpenGateway gw = gateway;
- if (gw == null) {
- logger.warn("received onConnected() but gateway is null");
- return;
- }
- if (gw instanceof USBGateway) {
- logger.info("---- CONNECTED to ZigBee USB gateway bridge '{}' (serialPort: {})", thing.getUID(),
- ((USBGateway) gw).getSerialPortName());
- } else {
- logger.info("---- CONNECTED to BUS gateway bridge '{}' ({}:{})", thing.getUID(),
- ((BUSGateway) gw).getHost(), ((BUSGateway) gw).getPort());
- // update serial number property (with MAC address)
- if (properties.get(PROPERTY_SERIAL_NO) != gw.getMACAddr().toUpperCase()) {
- properties.put(PROPERTY_SERIAL_NO, gw.getMACAddr().toUpperCase());
- propertiesChanged = true;
- logger.debug("updated property gw serialNumber: {}", properties.get(PROPERTY_SERIAL_NO));
- }
- }
- if (properties.get(PROPERTY_FIRMWARE_VERSION) != gw.getFirmwareVersion()) {
- properties.put(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
- propertiesChanged = true;
- logger.debug("updated property gw firmware version: {}", properties.get(PROPERTY_FIRMWARE_VERSION));
- }
- if (propertiesChanged) {
- updateProperties(properties);
- logger.info("properties updated for bridge '{}'", thing.getUID());
- }
- updateStatus(ThingStatus.ONLINE);
- // schedule a refresh for all devices
- refreshSchedule = scheduler.schedule(this::refreshAllDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
- TimeUnit.MILLISECONDS);
- }
-
- @Override
- public void onConnectionError(@Nullable OWNException error) {
- String errMsg;
- if (error == null) {
- errMsg = "unknown error";
- } else {
- errMsg = error.getMessage();
- }
- logger.info("---- ON CONNECTION ERROR for gateway {}: {}", gateway, errMsg);
- isGatewayConnected = false;
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
- "@text/offline.comm-error-connection" + " (onConnectionError - " + errMsg + ")");
- tryReconnectGateway();
- }
-
- @Override
- public void onConnectionClosed() {
- isGatewayConnected = false;
- logger.debug("onConnectionClosed() - isGatewayConnected={}", isGatewayConnected);
- // NOTE: cannot change to OFFLINE here because we are already in REMOVING state
- }
-
- @Override
- public void onDisconnected(@Nullable OWNException e) {
- isGatewayConnected = false;
- String errMsg;
- if (e == null) {
- errMsg = "unknown error";
- } else {
- errMsg = e.getMessage();
- }
- logger.info("---- DISCONNECTED from gateway {}. OWNException: {}", gateway, errMsg);
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
- "@text/offline.comm-error-disconnected" + " (onDisconnected - " + errMsg + ")");
- tryReconnectGateway();
- }
-
- private void tryReconnectGateway() {
- OpenGateway gw = gateway;
- if (gw != null) {
- if (!reconnecting) {
- reconnecting = true;
- logger.info("---- Starting RECONNECT cycle to gateway {}", gw);
- try {
- gw.reconnect();
- } catch (OWNAuthException e) {
- logger.info("---- AUTH error from gateway. Stopping re-connect");
- reconnecting = false;
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
- "@text/offline.conf-error-auth" + " (" + e + ")");
- }
- } else {
- logger.debug("---- reconnecting=true");
- }
- } else {
- logger.warn("---- cannot start RECONNECT, gateway is null");
- }
- }
-
- @Override
- public void onReconnected() {
- reconnecting = false;
- OpenGateway gw = gateway;
- logger.info("---- RE-CONNECTED to bridge {}", thing.getUID());
- if (gw != null) {
- updateStatus(ThingStatus.ONLINE);
- if (gw.getFirmwareVersion() != null) {
- this.updateProperty(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
- logger.debug("gw firmware version: {}", gw.getFirmwareVersion());
- }
-
- // schedule a refresh for all devices
- refreshSchedule = scheduler.schedule(this::refreshAllDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
- TimeUnit.MILLISECONDS);
- }
- }
-
- /**
- * Return a ownId string (=WHO.WHERE) from the device Where address and handler
- *
- * @param where the Where address (to be normalized)
- * @param handler the device handler
- * @return the ownId String
- */
- protected String ownIdFromDeviceWhere(Where where, OpenWebNetThingHandler handler) {
- return handler.ownIdPrefix() + "." + normalizeWhere(where);
- }
-
- /**
- * Returns a ownId string (=WHO.WHERE) from a Who and Where address
- *
- * @param who the Who
- * @param where the Where address (to be normalized)
- * @return the ownId String
- */
- public String ownIdFromWhoWhere(Who who, Where where) {
- return who.value() + "." + normalizeWhere(where);
- }
-
- /**
- * Return a ownId string (=WHO.WHERE) from a BaseOpenMessage
- *
- * @param baseMsg the BaseOpenMessage
- * @return the ownId String
- */
- public String ownIdFromMessage(BaseOpenMessage baseMsg) {
- return baseMsg.getWho().value() + "." + normalizeWhere(baseMsg.getWhere());
- }
-
- /**
- * Transform a Where address into a Thing id string
- *
- * @param where the Where address
- * @return the thing Id string
- */
- public String thingIdFromWhere(Where where) {
- return normalizeWhere(where); // '#' cannot be used in ThingUID;
- }
-
- /**
- * Normalize a Where address
- *
- * @param where the Where address
- * @return the normalized address as String
- */
- public String normalizeWhere(Where where) {
- String str = where.value();
- if (where instanceof WhereZigBee) {
- str = ((WhereZigBee) where).valueWithUnit(WhereZigBee.UNIT_ALL); // 76543210X#9 --> 765432100#9
- } else {
- if (str.indexOf("#4#") == -1) { // skip APL#4#bus case
- if (str.indexOf('#') == 0) { // Thermo central unit (#0) or zone via central unit (#Z, Z=[1-99]) --> Z
- str = str.substring(1);
- } else if (str.indexOf('#') > 0) { // Thermo zone Z and actuator N (Z#N, Z=[1-99], N=[1-9]) --> Z
- str = str.substring(0, str.indexOf('#'));
- }
- }
- }
- return str.replace('#', 'h');
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openwebnet.handler;
-
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_POWER;
-
-import java.util.Set;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import javax.measure.quantity.Power;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.unit.Units;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusInfo;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.UnDefType;
-import org.openwebnet4j.OpenGateway;
-import org.openwebnet4j.communication.OWNException;
-import org.openwebnet4j.message.BaseOpenMessage;
-import org.openwebnet4j.message.EnergyManagement;
-import org.openwebnet4j.message.FrameException;
-import org.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereEnergyManagement;
-import org.openwebnet4j.message.Who;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link OpenWebNetEnergyHandler} is responsible for handling commands/messages for a Energy Management OpenWebNet
- * device. It extends the abstract {@link OpenWebNetThingHandler}.
- *
- * @author Massimo Valla - Initial contribution
- * @author Andrea Conte - Energy management
- */
-@NonNullByDefault
-public class OpenWebNetEnergyHandler extends OpenWebNetThingHandler {
-
- private final Logger logger = LoggerFactory.getLogger(OpenWebNetEnergyHandler.class);
-
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES;
- public static final int ENERGY_SUBSCRIPTION_PERIOD = 10; // minutes
- private @Nullable ScheduledFuture<?> notificationSchedule;
-
- public OpenWebNetEnergyHandler(Thing thing) {
- super(thing);
- }
-
- public Boolean isFirstSchedulerLaunch = true;
-
- @Override
- public void initialize() {
- super.initialize();
-
- // In order to get data from the probe we must send a command over the bus, this could be done only when the
- // bridge is online.
- // a) usual flow: binding is starting, the bridge isn't online (startup flow not yet completed) --> subscriber
- // can't be started here, it will be started inside the bridgeStatusChanged event.
- // b) thing's discovery: binding is up and running, the bridge is online --> subscriber must be started here
- // otherwise data will be read only after a reboot.
-
- OpenWebNetBridgeHandler h = bridgeHandler;
- if (h != null && h.isBusGateway()) {
- OpenGateway gw = h.gateway;
- if (gw != null && gw.isConnected()) {
- // bridge is online
- subscribeToActivePowerChanges();
- }
- }
- }
-
- @Override
- public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
- super.bridgeStatusChanged(bridgeStatusInfo);
-
- // subscribe the scheduler only after the bridge is online
- if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
- subscribeToActivePowerChanges();
- }
- }
-
- private void subscribeToActivePowerChanges() {
- notificationSchedule = scheduler.scheduleWithFixedDelay(() -> {
- if (isFirstSchedulerLaunch) {
- logger.debug(
- "subscribeToActivePowerChanges() For WHERE={} subscribing to active power changes notification for the next {}min",
- deviceWhere, ENERGY_SUBSCRIPTION_PERIOD);
- } else {
- logger.debug(
- "subscribeToActivePowerChanges() Refreshing subscription for the next {}min for WHERE={} to active power changes notification",
- ENERGY_SUBSCRIPTION_PERIOD, deviceWhere);
- }
-
- try {
- bridgeHandler.gateway.send(EnergyManagement.setActivePowerNotificationsTime(deviceWhere.value(),
- ENERGY_SUBSCRIPTION_PERIOD));
- isFirstSchedulerLaunch = false;
- } catch (Exception e) {
- if (isFirstSchedulerLaunch) {
- logger.warn(
- "subscribeToActivePowerChanges() For WHERE={} could not subscribe to active power changes notifications. Exception={}",
- deviceWhere, e.getMessage());
- } else {
- logger.warn(
- "subscribeToActivePowerChanges() Unable to refresh subscription to active power changes notifications for WHERE={}. Exception={}",
- deviceWhere, e.getMessage());
- }
- }
- }, 0, ENERGY_SUBSCRIPTION_PERIOD - 1, TimeUnit.MINUTES);
- }
-
- @Override
- public void dispose() {
- if (notificationSchedule != null) {
- logger.debug("dispose() scheduler stopped.");
-
- notificationSchedule.cancel(false);
- }
- super.dispose();
- }
-
- @Override
- protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
- return new WhereEnergyManagement(wStr);
- }
-
- @Override
- protected void requestChannelState(ChannelUID channel) {
- logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
- Where w = deviceWhere;
- if (w != null) {
- try {
- send(EnergyManagement.requestActivePower(w.value()));
- } catch (OWNException e) {
- logger.debug("Exception while requesting channel {} state: {}", channel, e.getMessage(), e);
- }
- } else {
- logger.warn("Could not requestChannelState(): deviceWhere is null");
- }
- }
-
- @Override
- protected void refreshDevice(boolean refreshAll) {
- requestChannelState(new ChannelUID("any:any:any:any"));
- }
-
- @Override
- protected void handleChannelCommand(ChannelUID channel, Command command) {
- logger.warn("handleChannelCommand() Read only channel, unsupported command {}", command);
- }
-
- @Override
- protected String ownIdPrefix() {
- return Who.ENERGY_MANAGEMENT.value().toString();
- }
-
- @Override
- protected void handleMessage(BaseOpenMessage msg) {
- super.handleMessage(msg);
-
- if (msg.isCommand()) {
- logger.warn("handleMessage() Ignoring unsupported command for thing {}. Frame={}", getThing().getUID(),
- msg);
- return;
- } else {
- // fix: check for correct DIM (ActivePower / 113)
- if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.ACTIVE_POWER)) {
- updateActivePower(msg);
- } else {
- logger.debug("handleMessage() Ignoring message {} because it's not related to active power value.",
- msg);
- }
- }
- }
-
- /**
- * Updates energy power state based on a EnergyManagement message received from the OWN network
- *
- * @param msg the EnergyManagement message received
- * @throws FrameException
- */
- private void updateActivePower(BaseOpenMessage msg) {
- Integer activePower;
- try {
- activePower = Integer.parseInt(msg.getDimValues()[0]);
- updateState(CHANNEL_POWER, new QuantityType<Power>(activePower, Units.WATT));
- } catch (FrameException e) {
- logger.warn("FrameException on frame {}: {}", msg, e.getMessage());
- updateState(CHANNEL_POWER, UnDefType.UNDEF);
- }
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openwebnet.handler;
-
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.types.Command;
-import org.openwebnet4j.message.BaseOpenMessage;
-import org.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereLightAutom;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link OpenWebNetGenericHandler} is responsible for handling Generic OpenWebNet
- * devices. It does not too much, but it is needed to avoid handler errors and to tell the user
- * that some device has been found by the gateway but it was not recognised.
- * It extends the abstract {@link OpenWebNetThingHandler}.
- *
- * @author Massimo Valla - Initial contribution
- */
-@NonNullByDefault
-public class OpenWebNetGenericHandler extends OpenWebNetThingHandler {
-
- private final Logger logger = LoggerFactory.getLogger(OpenWebNetGenericHandler.class);
-
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.GENERIC_SUPPORTED_THING_TYPES;
-
- public OpenWebNetGenericHandler(Thing thing) {
- super(thing);
- }
-
- @Override
- public void initialize() {
- super.initialize();
- }
-
- @Override
- protected void requestChannelState(ChannelUID channel) {
- // do nothing
- logger.warn("Generic: there are no channels");
- }
-
- @Override
- protected void refreshDevice(boolean refreshAll) {
- // do nothing
- logger.warn("Generic: nothing to refresh");
- }
-
- @Override
- protected void handleChannelCommand(ChannelUID channel, Command command) {
- // do nothing
- logger.warn("Generic: there are no channels");
- }
-
- @Override
- protected String ownIdPrefix() {
- return "G";
- }
-
- @Override
- protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
- return new WhereLightAutom(wStr);
- }
-
- @Override
- protected void handleMessage(BaseOpenMessage msg) {
- super.handleMessage(msg);
- // do nothing
- logger.warn("Generic: handleMessage() nothing to do!");
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openwebnet.handler;
-
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_BRIGHTNESS;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_SWITCH;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_SWITCH_01;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.CHANNEL_SWITCH_02;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.THING_TYPE_BUS_DIMMER;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.THING_TYPE_ZB_DIMMER;
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS;
-
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
-import org.openhab.core.library.types.IncreaseDecreaseType;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.library.types.PercentType;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.types.Command;
-import org.openwebnet4j.communication.OWNException;
-import org.openwebnet4j.communication.Response;
-import org.openwebnet4j.message.BaseOpenMessage;
-import org.openwebnet4j.message.FrameException;
-import org.openwebnet4j.message.Lighting;
-import org.openwebnet4j.message.What;
-import org.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereLightAutom;
-import org.openwebnet4j.message.WhereZigBee;
-import org.openwebnet4j.message.Who;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link OpenWebNetLightingHandler} is responsible for handling commands/messages for a Lighting OpenWebNet device.
- * It extends the abstract {@link OpenWebNetThingHandler}.
- *
- * @author Massimo Valla - Initial contribution
- */
-@NonNullByDefault
-public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
-
- private final Logger logger = LoggerFactory.getLogger(OpenWebNetLightingHandler.class);
-
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.LIGHTING_SUPPORTED_THING_TYPES;
-
- // interval to interpret ON as response to requestStatus
- private static final int BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC = 250;
-
- // time to wait before sending a statusRequest, to avoid repeated requests and ensure dimmer has reached its final
- // level
- private static final int BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC = 900;
-
- private static final int UNKNOWN_STATE = 1000;
-
- private static long lastAllDevicesRefreshTS = -1; // timestamp when the last request for all device refresh was sent
- // for this handler
-
- protected static final int ALL_DEVICES_REFRESH_INTERVAL_MSEC = 60000; // interval in msec before sending another all
- // devices refresh request
-
- private long lastBrightnessChangeSentTS = 0; // timestamp when last brightness change was sent to the device
-
- private long lastStatusRequestSentTS = 0; // timestamp when last status request was sent to the device
-
- private int brightness = UNKNOWN_STATE; // current brightness percent value for this device
-
- private int brightnessBeforeOff = UNKNOWN_STATE; // latest brightness before device was set to off
-
- private int sw[] = { UNKNOWN_STATE, UNKNOWN_STATE }; // current switch(es) state
-
- public OpenWebNetLightingHandler(Thing thing) {
- super(thing);
- }
-
- @Override
- protected void requestChannelState(ChannelUID channel) {
- logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
- requestStatus(channel.getId());
- }
-
- /** helper method to request light status based on channel */
- private void requestStatus(String channelId) {
- Where w = deviceWhere;
- if (w != null) {
- try {
- lastStatusRequestSentTS = System.currentTimeMillis();
- Response res = send(Lighting.requestStatus(toWhere(channelId)));
- if (res != null && res.isSuccess()) {
- // set thing online, if not already
- ThingStatus ts = getThing().getStatus();
- if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
- updateStatus(ThingStatus.ONLINE);
- }
- }
- } catch (OWNException e) {
- logger.warn("requestStatus() Exception while requesting light state: {}", e.getMessage());
- }
- } else {
- logger.warn("Could not requestStatus(): deviceWhere is null");
- }
- }
-
- @Override
- protected void refreshDevice(boolean refreshAll) {
- OpenWebNetBridgeHandler brH = bridgeHandler;
- if (brH != null) {
- if (brH.isBusGateway() && refreshAll) {
- long now = System.currentTimeMillis();
- if (now - lastAllDevicesRefreshTS > ALL_DEVICES_REFRESH_INTERVAL_MSEC) {
- try {
- send(Lighting.requestStatus(WhereLightAutom.GENERAL.value()));
- lastAllDevicesRefreshTS = now;
- } catch (OWNException e) {
- logger.warn("Excpetion while requesting all devices refresh: {}", e.getMessage());
- }
- } else {
- logger.debug("Refresh all devices just sent...");
- }
- } else { // USB or BUS-single device
- ThingTypeUID thingType = thing.getThingTypeUID();
- if (THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS.equals(thingType)) {
- // Unfortunately using USB Gateway OpenWebNet both switch endpoints cannot be requested at the same
- // time using UNIT 00 because USB stick returns NACK, so we need to send a request status for both
- // endpoints
- requestStatus(CHANNEL_SWITCH_02);
- }
- requestStatus(""); // channel here does not make any difference, see {@link #toWhere()}
- }
- }
- }
-
- @Override
- protected void handleChannelCommand(ChannelUID channel, Command command) {
- switch (channel.getId()) {
- case CHANNEL_BRIGHTNESS:
- handleBrightnessCommand(command);
- break;
- case CHANNEL_SWITCH:
- case CHANNEL_SWITCH_01:
- case CHANNEL_SWITCH_02:
- handleSwitchCommand(channel, command);
- break;
- default: {
- logger.warn("Unsupported ChannelUID {}", channel);
- }
- }
- }
-
- /**
- * Handles Lighting switch command for a channel
- *
- * @param channel the channel
- * @param command the command
- */
- private void handleSwitchCommand(ChannelUID channel, Command command) {
- logger.debug("handleSwitchCommand() (command={} - channel={})", command, channel);
- if (command instanceof OnOffType) {
- try {
- if (OnOffType.ON.equals(command)) {
- send(Lighting.requestTurnOn(toWhere(channel.getId())));
- } else if (OnOffType.OFF.equals(command)) {
- send(Lighting.requestTurnOff(toWhere(channel.getId())));
- }
- } catch (OWNException e) {
- logger.warn("Exception while processing command {}: {}", command, e.getMessage());
- }
- } else {
- logger.warn("Unsupported command: {}", command);
- }
- }
-
- /**
- * Handles Lighting brightness command (xx%, INCREASE, DECREASE, ON, OFF)
- *
- * @param command the command
- */
- private void handleBrightnessCommand(Command command) {
- logger.debug("handleBrightnessCommand() command={}", command);
- if (command instanceof PercentType) {
- dimLightTo(((PercentType) command).intValue(), command);
- } else if (command instanceof IncreaseDecreaseType) {
- if (IncreaseDecreaseType.INCREASE.equals(command)) {
- dimLightTo(brightness + 10, command);
- } else { // DECREASE
- dimLightTo(brightness - 10, command);
- }
- } else if (command instanceof OnOffType) {
- if (OnOffType.ON.equals(command)) {
- dimLightTo(brightnessBeforeOff, command);
- } else { // OFF
- dimLightTo(0, command);
- }
- } else {
- logger.warn("Cannot handle command {} for thing {}", command, getThing().getUID());
- }
- }
-
- /**
- * Helper method to dim light to given percent
- */
- private void dimLightTo(int percent, Command command) {
- logger.debug(" DIM dimLightTo({}) bri={} briBeforeOff={}", percent, brightness, brightnessBeforeOff);
- int newBrightness = percent;
- if (newBrightness == UNKNOWN_STATE) {
- // we do not know last brightness -> set dimmer to 100%
- newBrightness = 100;
- } else if (newBrightness <= 0) {
- newBrightness = 0;
- brightnessBeforeOff = brightness;
- logger.debug(" DIM saved bri before sending bri=0 command to device");
- } else if (newBrightness > 100) {
- newBrightness = 100;
- }
- What newBrightnessWhat = Lighting.percentToWhat(newBrightness);
- logger.debug(" DIM newBrightness={} newBrightnessWhat={}", newBrightness, newBrightnessWhat);
- @Nullable
- What brightnessWhat = null;
- if (brightness != UNKNOWN_STATE) {
- brightnessWhat = Lighting.percentToWhat(brightness);
- }
- if (brightnessWhat == null || !newBrightnessWhat.value().equals(brightnessWhat.value())) {
- logger.debug(" DIM brightnessWhat {} --> {} WHAT level change needed", brightnessWhat,
- newBrightnessWhat);
- Where w = deviceWhere;
- if (w != null) {
- try {
- lastBrightnessChangeSentTS = System.currentTimeMillis();
- send(Lighting.requestDimTo(w.value(), newBrightnessWhat));
- } catch (OWNException e) {
- logger.warn("Exception while sending dimTo request for command {}: {}", command, e.getMessage());
- }
- }
- } else {
- logger.debug(" DIM brightnessWhat {} --> {} NO WHAT level change needed", brightnessWhat,
- newBrightnessWhat);
- }
- brightness = newBrightness;
- updateState(CHANNEL_BRIGHTNESS, new PercentType(brightness));
- logger.debug(" DIM---END bri={} briBeforeOff={}", brightness, brightnessBeforeOff);
- }
-
- @Override
- protected String ownIdPrefix() {
- return Who.LIGHTING.value().toString();
- }
-
- @Override
- protected void handleMessage(BaseOpenMessage msg) {
- logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
- super.handleMessage(msg);
- ThingTypeUID thingType = thing.getThingTypeUID();
- if (THING_TYPE_ZB_DIMMER.equals(thingType) || THING_TYPE_BUS_DIMMER.equals(thingType)) {
- updateBrightness((Lighting) msg);
- } else {
- updateOnOffState((Lighting) msg);
- }
- }
-
- /**
- * Updates brightness based on OWN Lighting message received
- *
- * @param msg the Lighting message received
- */
- private synchronized void updateBrightness(Lighting msg) {
- logger.debug(" $BRI updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
- brightnessBeforeOff);
- long now = System.currentTimeMillis();
- long delta = now - lastBrightnessChangeSentTS;
- boolean belowThresh = delta < BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC;
- logger.debug(" $BRI delta={}ms {}", delta, (belowThresh ? "< DELAY" : ""));
- if (belowThresh) {
- // we just sent a command from OH, so we can ignore this message from network
- logger.debug(" $BRI a command was sent {} < {} ms --> no action needed", delta,
- BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC);
- } else {
- if (msg.isOn()) {
- // if we have not just sent a requestStatus, on ON event we send requestStatus to know current level
- long deltaStatusReq = now - lastStatusRequestSentTS;
- if (deltaStatusReq > BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC) {
- logger.debug(" $BRI 'ON' is new notification from network, scheduling requestStatus...");
- // we must wait BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC to be sure dimmer has reached its final level
- scheduler.schedule(() -> {
- requestStatus(CHANNEL_BRIGHTNESS);
- }, BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC, TimeUnit.MILLISECONDS);
- return;
- } else {
- // otherwise we interpret this ON event as the requestStatus response event with level=1
- // so we proceed to call updateBrightnessState()
- logger.debug(" $BRI 'ON' is the requestStatus response level");
- }
- }
- logger.debug(" $BRI update from network");
- if (msg.getWhat() != null) {
- updateBrightnessState(msg);
- } else { // dimension notification
- if (msg.getDim() == Lighting.DimLighting.DIMMER_LEVEL_100) {
- int newBrightness;
- try {
- newBrightness = msg.parseDimmerLevel100();
- } catch (FrameException fe) {
- logger.warn("updateBrightness() Wrong value for dimmerLevel100 in message: {}", msg);
- return;
- }
- logger.debug(" $BRI DIMMER_LEVEL_100 newBrightness={}", newBrightness);
- updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
- if (newBrightness == 0) {
- brightnessBeforeOff = brightness;
- }
- brightness = newBrightness;
- } else {
- logger.warn("updateBrightness() Cannot handle message {} for thing {}", msg, getThing().getUID());
- return;
- }
- }
- }
- logger.debug(" $BRI---END updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
- brightnessBeforeOff);
- }
-
- /**
- * Updates light brightness state based on a OWN Lighting message
- *
- * @param msg the Lighting message received
- */
- private void updateBrightnessState(Lighting msg) {
- What w = msg.getWhat();
- if (w != null) {
- if (Lighting.WhatLighting.ON.equals(w)) {
- w = Lighting.WhatLighting.DIMMER_LEVEL_2; // levels start at 2
- }
- int newBrightnessWhat = w.value();
- int brightnessWhat = UNKNOWN_STATE;
- if (brightness != UNKNOWN_STATE) {
- brightnessWhat = Lighting.percentToWhat(brightness).value();
- }
- logger.debug(" $BRI brightnessWhat {} --> {}", brightnessWhat, newBrightnessWhat);
- if (brightnessWhat != newBrightnessWhat) {
- int newBrightness = Lighting.levelToPercent(newBrightnessWhat);
- updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
- if (msg.isOff()) {
- brightnessBeforeOff = brightness;
- }
- brightness = newBrightness;
- logger.debug(" $BRI brightness CHANGED to {}", brightness);
- } else {
- logger.debug(" $BRI no change");
- }
- }
- }
-
- /**
- * Updates light on/off state based on a OWN Lighting event message received
- *
- * @param msg the Lighting message received
- */
- private void updateOnOffState(Lighting msg) {
- OpenWebNetBridgeHandler brH = bridgeHandler;
- if (brH != null) {
- if (msg.isOn() || msg.isOff()) {
- String channelId;
- int switchId = 0;
- if (brH.isBusGateway()) {
- channelId = CHANNEL_SWITCH;
- } else {
- WhereZigBee w = (WhereZigBee) (msg.getWhere());
- if (WhereZigBee.UNIT_02.equals(w.getUnit())) {
- channelId = CHANNEL_SWITCH_02;
- switchId = 1;
- } else {
- channelId = CHANNEL_SWITCH_01;
- }
- }
- int currentSt = sw[switchId];
- int newSt = (msg.isOn() ? 1 : 0);
- if (newSt != currentSt) {
- updateState(channelId, (newSt == 1 ? OnOffType.ON : OnOffType.OFF));
- sw[switchId] = newSt;
- logger.debug(" {} ONOFF CHANGED to {}", ownId, newSt);
- } else {
- logger.debug(" {} ONOFF no change", ownId);
- }
- } else {
- logger.debug("updateOnOffState() Ignoring unsupported WHAT for thing {}. Frame={}", getThing().getUID(),
- msg.getFrameValue());
- return;
- }
- }
- }
-
- @Override
- protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
- return new WhereLightAutom(wStr);
- }
-
- /**
- * Returns a WHERE address string based on channelId string
- *
- * @param channelId the channelId string
- **/
- @Nullable
- private String toWhere(String channelId) {
- Where w = deviceWhere;
- if (w != null) {
- OpenWebNetBridgeHandler brH = bridgeHandler;
- if (brH != null) {
- if (brH.isBusGateway()) {
- return w.value();
- } else if (channelId.equals(CHANNEL_SWITCH_02)) {
- return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_02);
- } else { // CHANNEL_SWITCH_01 or other channels
- return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_01);
- }
- }
- }
- return null;
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openwebnet.handler;
-
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
-
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.openwebnet.OpenWebNetBindingConstants;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.library.unit.SIUnits;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusInfo;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.UnDefType;
-import org.openwebnet4j.communication.OWNException;
-import org.openwebnet4j.message.BaseOpenMessage;
-import org.openwebnet4j.message.FrameException;
-import org.openwebnet4j.message.MalformedFrameException;
-import org.openwebnet4j.message.Thermoregulation;
-import org.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereThermo;
-import org.openwebnet4j.message.Who;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link OpenWebNetThermoregulationHandler} is responsible for handling commands/messages for Thermoregulation
- * Things. It extends the abstract {@link OpenWebNetThingHandler}.
- *
- * @author Massimo Valla - Initial contribution
- * @author Andrea Conte - Thermoregulation
- * @author Gilberto Cocchi - Thermoregulation
- */
-@NonNullByDefault
-public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
-
- private final Logger logger = LoggerFactory.getLogger(OpenWebNetThermoregulationHandler.class);
-
- public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.THERMOREGULATION_SUPPORTED_THING_TYPES;
-
- private boolean isTempSensor = false; // is the thing a sensor ?
-
- private double currentSetPointTemp = 11.5d; // 11.5 is the default setTemp used in MyHomeUP mobile app
-
- private Thermoregulation.Function currentFunction = Thermoregulation.Function.GENERIC;
-
- public OpenWebNetThermoregulationHandler(Thing thing) {
- super(thing);
- }
-
- @Override
- public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
- super.bridgeStatusChanged(bridgeStatusInfo);
- // when the bridge is ONLINE request for thing states (temp, setTemp, fanSpeed...)
- if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
- refreshDevice(false);
- }
- }
-
- @Override
- protected void handleChannelCommand(ChannelUID channel, Command command) {
- switch (channel.getId()) {
- case CHANNEL_TEMP_SETPOINT:
- handleSetpoint(command);
- break;
- case CHANNEL_FUNCTION:
- handleFunction(command);
- break;
- case CHANNEL_MODE:
- handleMode(command);
- break;
- case CHANNEL_FAN_SPEED:
- handleSetFanSpeed(command);
- break;
- default: {
- logger.warn("handleChannelCommand() Unsupported ChannelUID {}", channel.getId());
- }
- }
- }
-
- @Override
- protected void requestChannelState(ChannelUID channel) {
- refreshDevice(false);
- }
-
- @Override
- protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
- WhereThermo wt = new WhereThermo(wStr);
- if (wt.isProbe()) {
- isTempSensor = true;
- }
- return wt;
- }
-
- @Override
- protected String ownIdPrefix() {
- return Who.THERMOREGULATION.value().toString();
- }
-
- private void handleSetFanSpeed(Command command) {
- if (command instanceof StringType) {
- Where w = deviceWhere;
- if (w != null) {
- try {
- Thermoregulation.FanCoilSpeed speed = Thermoregulation.FanCoilSpeed.valueOf(command.toString());
- send(Thermoregulation.requestWriteFanCoilSpeed(w.value(), speed));
- } catch (OWNException e) {
- logger.warn("handleSetFanSpeed() {}", e.getMessage());
- } catch (IllegalArgumentException e) {
- logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command,
- getThing().getUID());
- return;
- }
- }
- } else {
- logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command, getThing().getUID());
- }
- }
-
- private void handleSetpoint(Command command) {
- if (command instanceof QuantityType || command instanceof DecimalType) {
- Where w = deviceWhere;
- if (w != null) {
- double newTemp = 0;
- if (command instanceof QuantityType) {
- QuantityType<?> tempCelsius = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
- if (tempCelsius != null) {
- newTemp = tempCelsius.doubleValue();
- }
- } else {
- newTemp = ((DecimalType) command).doubleValue();
- }
- try {
- send(Thermoregulation.requestWriteSetpointTemperature(w.value(), newTemp, currentFunction));
- } catch (MalformedFrameException | OWNException e) {
- logger.warn("handleSetpoint() {}", e.getMessage());
- }
- }
- } else {
- logger.warn("handleSetpoint() Unsupported command {} for thing {}", command, getThing().getUID());
- }
- }
-
- private void handleMode(Command command) {
- if (command instanceof StringType) {
- Where w = deviceWhere;
- if (w != null) {
- try {
- Thermoregulation.OperationMode mode = Thermoregulation.OperationMode.valueOf(command.toString());
- send(Thermoregulation.requestWriteMode(w.value(), mode, currentFunction, currentSetPointTemp));
- } catch (OWNException e) {
- logger.warn("handleMode() {}", e.getMessage());
- } catch (IllegalArgumentException e) {
- logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
- return;
- }
- }
- } else {
- logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
- }
- }
-
- private void handleFunction(Command command) {
- if (command instanceof StringType) {
- Where w = deviceWhere;
- if (w != null) {
- try {
- Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
- send(Thermoregulation.requestWriteFunction(w.value(), function));
- } catch (OWNException e) {
- logger.warn("handleFunction() {}", e.getMessage());
- } catch (IllegalArgumentException e) {
- logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
- return;
- }
- }
- } else {
- logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
- }
- }
-
- @Override
- protected void handleMessage(BaseOpenMessage msg) {
- super.handleMessage(msg);
- if (msg.isCommand()) {
- updateModeAndFunction((Thermoregulation) msg);
- } else {
- if (msg.getDim() == null) {
- return;
- }
- if (msg.getDim() == Thermoregulation.DimThermo.TEMPERATURE
- || msg.getDim() == Thermoregulation.DimThermo.PROBE_TEMPERATURE) {
- updateTemperature((Thermoregulation) msg);
- } else if (msg.getDim() == Thermoregulation.DimThermo.TEMP_SETPOINT
- || msg.getDim() == Thermoregulation.DimThermo.COMPLETE_PROBE_STATUS) {
- updateSetpoint((Thermoregulation) msg);
- } else if (msg.getDim() == Thermoregulation.DimThermo.VALVES_STATUS) {
- updateValveStatus((Thermoregulation) msg);
- } else if (msg.getDim() == Thermoregulation.DimThermo.ACTUATOR_STATUS) {
- updateActuatorStatus((Thermoregulation) msg);
- } else if (msg.getDim() == Thermoregulation.DimThermo.FAN_COIL_SPEED) {
- updateFanCoilSpeed((Thermoregulation) msg);
- } else {
- logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", msg.getDim(),
- getThing().getUID(), msg);
- }
- }
- }
-
- private void updateModeAndFunction(Thermoregulation tmsg) {
- if (tmsg.getWhat() == null) {
- logger.debug("updateModeAndFunction() Could not parse Mode or Function from {} (what is null)",
- tmsg.getFrameValue());
- return;
- }
-
- Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
-
- if (w.mode() == null) {
- logger.debug("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
- return;
- }
- if (w.function() == null) {
- logger.debug("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
- return;
- }
-
- Thermoregulation.OperationMode mode = w.mode();
- Thermoregulation.Function function = w.function();
-
- if (w == Thermoregulation.WhatThermo.HEATING) {
- function = Thermoregulation.Function.HEATING;
- } else if (w == Thermoregulation.WhatThermo.CONDITIONING) {
- function = Thermoregulation.Function.COOLING;
- }
-
- updateState(CHANNEL_MODE, new StringType(mode.toString()));
- updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
-
- // store current function
- currentFunction = function;
- }
-
- private void updateTemperature(Thermoregulation tmsg) {
- try {
- double temp = Thermoregulation.parseTemperature(tmsg);
- updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
- } catch (FrameException e) {
- logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
- updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
- }
- }
-
- private void updateSetpoint(Thermoregulation tmsg) {
- try {
- double temp = Thermoregulation.parseTemperature(tmsg);
- updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
- currentSetPointTemp = temp;
- } catch (FrameException e) {
- logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
- updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
- }
- }
-
- private void updateFanCoilSpeed(Thermoregulation tmsg) {
- try {
- Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
- updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
- } catch (FrameException e) {
- logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
- updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
- }
- }
-
- private void updateValveStatus(Thermoregulation tmsg) {
- try {
- Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
- Thermoregulation.WhatThermo.CONDITIONING);
- updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
-
- Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
- Thermoregulation.WhatThermo.HEATING);
- updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
- } catch (FrameException e) {
- logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
- updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
- updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
- }
- }
-
- private void updateActuatorStatus(Thermoregulation tmsg) {
- try {
- Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
- updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
- } catch (FrameException e) {
- logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
- updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
- }
- }
-
- @Override
- protected void refreshDevice(boolean refreshAll) {
- if (deviceWhere != null) {
- String w = deviceWhere.value();
- try {
- send(Thermoregulation.requestTemperature(w));
- if (!this.isTempSensor) {
- // for bus_thermo_zone request also other single channels updates
- send(Thermoregulation.requestSetPointTemperature(w));
- send(Thermoregulation.requestFanCoilSpeed(w));
- send(Thermoregulation.requestMode(w));
- send(Thermoregulation.requestValvesStatus(w));
- send(Thermoregulation.requestActuatorsStatus(w));
- }
- } catch (OWNException e) {
- logger.warn("refreshDevice() where='{}' returned OWNException {}", w, e.getMessage());
- }
- }
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.openwebnet.handler;
-
-import static org.openhab.binding.openwebnet.OpenWebNetBindingConstants.*;
-
-import java.util.Map;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import javax.measure.Quantity;
-import javax.measure.Unit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingStatus;
-import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.binding.BaseThingHandler;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.openwebnet4j.OpenGateway;
-import org.openwebnet4j.communication.OWNException;
-import org.openwebnet4j.communication.Response;
-import org.openwebnet4j.message.BaseOpenMessage;
-import org.openwebnet4j.message.OpenMessage;
-import org.openwebnet4j.message.Where;
-import org.openwebnet4j.message.WhereZigBee;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link OpenWebNetThingHandler} is responsible for handling commands for a OpenWebNet device.
- * It's the abstract class for all OpenWebNet things. It should be extended by each specific OpenWebNet category of
- * device (WHO).
- *
- * @author Massimo Valla - Initial contribution
- */
-@NonNullByDefault
-public abstract class OpenWebNetThingHandler extends BaseThingHandler {
-
- private final Logger logger = LoggerFactory.getLogger(OpenWebNetThingHandler.class);
-
- protected @Nullable OpenWebNetBridgeHandler bridgeHandler;
- protected @Nullable String ownId; // OpenWebNet identifier for this device: WHO.WHERE
- protected @Nullable Where deviceWhere; // this device Where address
-
- protected @Nullable ScheduledFuture<?> refreshTimeout;
-
- public OpenWebNetThingHandler(Thing thing) {
- super(thing);
- }
-
- @Override
- public void initialize() {
- Bridge bridge = getBridge();
- if (bridge != null) {
- OpenWebNetBridgeHandler brH = (OpenWebNetBridgeHandler) bridge.getHandler();
- if (brH != null) {
- bridgeHandler = brH;
-
- final String configDeviceWhere = (String) getConfig().get(CONFIG_PROPERTY_WHERE);
- if (configDeviceWhere == null) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "@text/offline.conf-error-where");
- } else {
- Where w;
- try {
- if (brH.isBusGateway()) {
- w = buildBusWhere(configDeviceWhere);
- } else {
- w = new WhereZigBee(configDeviceWhere);
- }
- } catch (IllegalArgumentException ia) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "@text/offline.conf-error-where");
- return;
- }
- deviceWhere = w;
- final String oid = brH.ownIdFromDeviceWhere(w, this);
- ownId = oid;
- Map<String, String> properties = editProperties();
- properties.put(PROPERTY_OWNID, oid);
- updateProperties(properties);
- brH.registerDevice(oid, this);
- logger.debug("associated thing to bridge with ownId={}", ownId);
- updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/unknown.waiting-state");
- }
- }
- } else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "@text/offline.conf-error-no-bridge");
- }
- }
-
- @Override
- public void handleCommand(ChannelUID channel, Command command) {
- logger.debug("handleCommand() (command={} - channel={})", command, channel);
- OpenWebNetBridgeHandler handler = bridgeHandler;
- if (handler != null) {
- OpenGateway gw = handler.gateway;
- if (gw != null && !gw.isConnected()) {
- logger.info("Cannot handle {} command for {}: gateway is not connected", command, getThing().getUID());
- return;
- }
- if (deviceWhere == null) {
- logger.info("Cannot handle {} command for {}: 'where' parameter is not configured or is invalid",
- command, getThing().getUID());
- return;
- }
- if (command instanceof RefreshType) {
- requestChannelState(channel);
- // set a schedule to put device OFFLINE if no answer is received after THING_STATE_REQ_TIMEOUT_SEC
- refreshTimeout = scheduler.schedule(() -> {
- if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- "Could not get channel state (timer expired)");
- }
- }, THING_STATE_REQ_TIMEOUT_SEC, TimeUnit.SECONDS);
- } else {
- handleChannelCommand(channel, command);
- }
- } else {
- logger.debug("Thing {} is not associated to any gateway, skipping command", getThing().getUID());
- }
- }
-
- /**
- * Handles a command for the specific channel for this thing.
- * It must be implemented by each specific OpenWebNet category of device (WHO), based on channel
- *
- * @param channel specific ChannleUID
- * @param command the Command to be executed
- */
- protected abstract void handleChannelCommand(ChannelUID channel, Command command);
-
- /**
- * Handle incoming message from OWN network via bridge Thing, directed to this device. It should be further
- * implemented by each specific device handler.
- *
- * @param msg the message to handle
- */
- protected void handleMessage(BaseOpenMessage msg) {
- ThingStatus ts = getThing().getStatus();
- if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
- updateStatus(ThingStatus.ONLINE);
- }
- }
-
- /**
- * Helper method to send OWN messages from ThingHandlers
- */
- protected @Nullable Response send(OpenMessage msg) throws OWNException {
- OpenWebNetBridgeHandler bh = bridgeHandler;
- if (bh != null) {
- OpenGateway gw = bh.gateway;
- if (gw != null) {
- return gw.send(msg);
- }
- }
- logger.warn("Couldn't send message {}: handler or gateway is null", msg);
- return null;
- }
-
- /**
- * Helper method to send with high priority OWN messages from ThingsHandlers
- */
- protected @Nullable Response sendHighPriority(OpenMessage msg) throws OWNException {
- OpenWebNetBridgeHandler handler = bridgeHandler;
- if (handler != null) {
- OpenGateway gw = handler.gateway;
- if (gw != null) {
- return gw.sendHighPriority(msg);
- }
- }
- return null;
- }
-
- /**
- * Request the state for the specified channel
- *
- * @param channel the {@link ChannelUID} to request the state for
- */
- protected abstract void requestChannelState(ChannelUID channel);
-
- /**
- * Refresh the device
- *
- * @param refreshAll set true if all devices of the binding should be refreshed with one command, if possible
- */
- protected abstract void refreshDevice(boolean refreshAll);
-
- /**
- * Abstract builder for device Where address, to be implemented by each subclass to choose the right Where subclass
- * (the method is used only if the Thing is associated to a BUS gateway).
- *
- * @param wStr the WHERE string
- */
- protected abstract Where buildBusWhere(String wStr) throws IllegalArgumentException;
-
- @Override
- public void dispose() {
- OpenWebNetBridgeHandler bh = bridgeHandler;
- String oid = ownId;
- if (bh != null && oid != null) {
- bh.unregisterDevice(oid);
- }
- ScheduledFuture<?> sc = refreshTimeout;
- if (sc != null) {
- sc.cancel(true);
- }
- super.dispose();
- }
-
- /**
- * Helper method to return a Quantity from a Number value or UnDefType.NULL if value is null
- *
- * @param value to be used
- * @param unit to be used
- * @return Quantity
- */
- protected <U extends Quantity<U>> State getAsQuantityTypeOrNull(@Nullable Number value, Unit<U> unit) {
- return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
- }
-
- /**
- * Returns a prefix String for ownId specific for each handler. To be implemented by sub-classes.
- *
- * @return
- */
- protected abstract String ownIdPrefix();
-}
+++ /dev/null
-/**
- * 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;
- }
-}
+++ /dev/null
-/**
- * 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;
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openwebnet.internal;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link OpenWebNetBindingConstants} class defines common constants, which are used across the whole binding.
+ *
+ * @author Massimo Valla - Initial contribution
+ * @author Andrea Conte - Energy management, Thermoregulation
+ * @author Gilberto Cocchi - Thermoregulation
+ */
+
+@NonNullByDefault
+public class OpenWebNetBindingConstants {
+
+ public static final String BINDING_ID = "openwebnet";
+
+ public static final int THING_STATE_REQ_TIMEOUT_SEC = 5;
+
+ // #LIST OF Thing Type UIDs
+ // generic device (used for not identified devices)
+ public static final ThingTypeUID THING_TYPE_GENERIC_DEVICE = new ThingTypeUID(BINDING_ID, "generic_device");
+ public static final String THING_LABEL_GENERIC_DEVICE = "GENERIC Device";
+ // bridges
+ public static final ThingTypeUID THING_TYPE_ZB_GATEWAY = new ThingTypeUID(BINDING_ID, "zb_gateway");
+ public static final String THING_LABEL_ZB_GATEWAY = "ZigBee USB Gateway";
+ public static final ThingTypeUID THING_TYPE_BUS_GATEWAY = new ThingTypeUID(BINDING_ID, "bus_gateway");
+ public static final String THING_LABEL_BUS_GATEWAY = "BUS Gateway";
+ // other thing types
+ // BUS
+ public static final ThingTypeUID THING_TYPE_BUS_ON_OFF_SWITCH = new ThingTypeUID(BINDING_ID, "bus_on_off_switch");
+ public static final String THING_LABEL_BUS_ON_OFF_SWITCH = "Switch";
+ public static final ThingTypeUID THING_TYPE_BUS_DIMMER = new ThingTypeUID(BINDING_ID, "bus_dimmer");
+ public static final String THING_LABEL_BUS_DIMMER = "Dimmer";
+ public static final ThingTypeUID THING_TYPE_BUS_AUTOMATION = new ThingTypeUID(BINDING_ID, "bus_automation");
+ public static final String THING_LABEL_BUS_AUTOMATION = "Automation";
+ public static final ThingTypeUID THING_TYPE_BUS_ENERGY_METER = new ThingTypeUID(BINDING_ID, "bus_energy_meter");
+ public static final String THING_LABEL_BUS_ENERGY_METER = "Energy Meter";
+ public static final ThingTypeUID THING_TYPE_BUS_THERMO_SENSOR = new ThingTypeUID(BINDING_ID, "bus_thermo_sensor");
+ public static final String THING_LABEL_BUS_THERMO_SENSOR = "Thermo Sensor";
+ public static final ThingTypeUID THING_TYPE_BUS_THERMO_ZONE = new ThingTypeUID(BINDING_ID, "bus_thermo_zone");
+ public static final String THING_LABEL_BUS_THERMO_ZONE = "Thermo Zone";
+
+ // ZIGBEE
+ public static final ThingTypeUID THING_TYPE_ZB_ON_OFF_SWITCH = new ThingTypeUID(BINDING_ID, "zb_on_off_switch");
+ public static final String THING_LABEL_ZB_ON_OFF_SWITCH = "ZigBee Switch";
+ public static final ThingTypeUID THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS = new ThingTypeUID(BINDING_ID,
+ "zb_on_off_switch2u");
+ public static final String THING_LABEL_ZB_ON_OFF_SWITCH_2UNITS = "ZigBee 2-units Switch";
+ public static final ThingTypeUID THING_TYPE_ZB_DIMMER = new ThingTypeUID(BINDING_ID, "zb_dimmer");
+ public static final String THING_LABEL_ZB_DIMMER = "ZigBee Dimmer";
+ public static final ThingTypeUID THING_TYPE_ZB_AUTOMATION = new ThingTypeUID(BINDING_ID, "zb_automation");
+ public static final String THING_LABEL_ZB_AUTOMATION = "ZigBee Automation";
+
+ // #SUPPORTED THINGS SETS
+ // ## Generic
+ public static final Set<ThingTypeUID> GENERIC_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GENERIC_DEVICE);
+ // ## Lighting
+ public static final Set<ThingTypeUID> LIGHTING_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_ON_OFF_SWITCH,
+ THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS, THING_TYPE_ZB_DIMMER, THING_TYPE_BUS_ON_OFF_SWITCH,
+ THING_TYPE_BUS_DIMMER);
+ // ## Automation
+ public static final Set<ThingTypeUID> AUTOMATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_AUTOMATION,
+ THING_TYPE_BUS_AUTOMATION);
+
+ // ## Thermoregulation
+ public static final Set<ThingTypeUID> THERMOREGULATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_THERMO_ZONE,
+ THING_TYPE_BUS_THERMO_SENSOR);
+
+ // ## Energy Management
+ public static final Set<ThingTypeUID> ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_ENERGY_METER);
+
+ // ## Groups
+ public static final Set<ThingTypeUID> DEVICE_SUPPORTED_THING_TYPES = Stream
+ .of(LIGHTING_SUPPORTED_THING_TYPES, AUTOMATION_SUPPORTED_THING_TYPES,
+ THERMOREGULATION_SUPPORTED_THING_TYPES, ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES,
+ GENERIC_SUPPORTED_THING_TYPES)
+ .flatMap(Collection::stream).collect(Collectors.toCollection(HashSet::new));
+
+ public static final Set<ThingTypeUID> BRIDGE_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_GATEWAY,
+ THING_TYPE_BUS_GATEWAY);
+
+ public static final Set<ThingTypeUID> ALL_SUPPORTED_THING_TYPES = Stream
+ .of(DEVICE_SUPPORTED_THING_TYPES, BRIDGE_SUPPORTED_THING_TYPES).flatMap(Collection::stream)
+ .collect(Collectors.toCollection(HashSet::new));
+
+ // LIST OF ALL CHANNEL IDs
+ // lighting
+ public static final String CHANNEL_SWITCH = "switch";
+ public static final String CHANNEL_SWITCH_01 = "switch_01";
+ public static final String CHANNEL_SWITCH_02 = "switch_02";
+ public static final String CHANNEL_BRIGHTNESS = "brightness";
+
+ // automation
+ public static final String CHANNEL_SHUTTER = "shutter";
+
+ // thermo
+ public static final String CHANNEL_TEMPERATURE = "temperature";
+ public static final String CHANNEL_FUNCTION = "function";
+ public static final String CHANNEL_TEMP_SETPOINT = "setpointTemperature";
+ public static final String CHANNEL_MODE = "mode";
+ public static final String CHANNEL_FAN_SPEED = "speedFanCoil";
+ public static final String CHANNEL_CONDITIONING_VALVES = "conditioningValves";
+ public static final String CHANNEL_HEATING_VALVES = "heatingValves";
+ public static final String CHANNEL_ACTUATORS = "actuators";
+
+ // energy management
+ public static final String CHANNEL_POWER = "power";
+
+ // devices config properties
+ public static final String CONFIG_PROPERTY_WHERE = "where";
+ public static final String CONFIG_PROPERTY_SHUTTER_RUN = "shutterRun";
+
+ // BUS gw config properties
+ public static final String CONFIG_PROPERTY_HOST = "host";
+ public static final String CONFIG_PROPERTY_SERIAL_PORT = "serialPort";
+
+ // properties
+ public static final String PROPERTY_OWNID = "ownId";
+ public static final String PROPERTY_ZIGBEEID = "zigbeeid";
+ public static final String PROPERTY_FIRMWARE_VERSION = "firmwareVersion";
+ public static final String PROPERTY_MODEL = "model";
+ public static final String PROPERTY_SERIAL_NO = "serialNumber";
+}
*/
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;
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;
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;
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;
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openwebnet.internal.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_SHUTTER;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.StopMoveType;
+import org.openhab.core.library.types.UpDownType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.UnDefType;
+import org.openwebnet4j.OpenGateway;
+import org.openwebnet4j.communication.OWNException;
+import org.openwebnet4j.message.Automation;
+import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.FrameException;
+import org.openwebnet4j.message.GatewayMgmt;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereLightAutom;
+import org.openwebnet4j.message.Who;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetAutomationHandler} is responsible for handling commands/messages for an Automation OpenWebNet
+ * device. It extends the abstract {@link OpenWebNetThingHandler}.
+ *
+ * @author Massimo Valla - Initial contribution
+ */
+@NonNullByDefault
+public class OpenWebNetAutomationHandler extends OpenWebNetThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(OpenWebNetAutomationHandler.class);
+
+ private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("ss.SSS");
+
+ private static long lastAllDevicesRefreshTS = -1; // timestamp when the last request for all device refresh was sent
+ protected static final int ALL_DEVICES_REFRESH_INTERVAL_MSEC = 60000; // interval in msec before sending another all
+ // devices refresh request
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.AUTOMATION_SUPPORTED_THING_TYPES;
+
+ // moving states
+ public static final int MOVING_STATE_STOPPED = 0;
+ public static final int MOVING_STATE_MOVING_UP = 1;
+ public static final int MOVING_STATE_MOVING_DOWN = 2;
+ public static final int MOVING_STATE_UNKNOWN = -1;
+
+ // calibration states
+ public static final int CALIBRATION_INACTIVE = -1;
+ public static final int CALIBRATION_ACTIVATED = 0;
+ public static final int CALIBRATION_GOING_UP = 1;
+ public static final int CALIBRATION_GOING_DOWN = 2;
+
+ // positions
+ public static final int POSITION_MAX_STEPS = 100;
+ public static final int POSITION_DOWN = 100;
+ public static final int POSITION_UP = 0;
+ public static final int POSITION_UNKNOWN = -1;
+
+ public static final int SHUTTER_RUN_UNDEFINED = -1;
+ private int shutterRun = SHUTTER_RUN_UNDEFINED;
+ private static final String AUTO_CALIBRATION = "AUTO";
+
+ private long startedMovingAtTS = -1; // timestamp when device started moving UP/DOWN
+ private int movingState = MOVING_STATE_UNKNOWN;
+ private int positionEstimation = POSITION_UNKNOWN;
+ private @Nullable ScheduledFuture<?> moveSchedule;
+ private int positionRequested = POSITION_UNKNOWN;
+ private int calibrating = CALIBRATION_INACTIVE;
+ private static final int MIN_STEP_TIME_MSEC = 50;
+ private @Nullable Command commandRequestedWhileMoving = null;
+
+ public OpenWebNetAutomationHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ Object shutterRunConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN);
+ try {
+ if (shutterRunConfig == null) {
+ shutterRunConfig = AUTO_CALIBRATION;
+ logger.debug("shutterRun null --> default to AUTO");
+ } else if (shutterRunConfig instanceof String) {
+ if (AUTO_CALIBRATION.equalsIgnoreCase(((String) shutterRunConfig))) {
+ logger.debug("shutterRun set to AUTO via configuration");
+ shutterRun = SHUTTER_RUN_UNDEFINED; // reset shutterRun
+ } else { // try to parse int>=1000
+ int shutterRunInt = Integer.parseInt((String) shutterRunConfig);
+ if (shutterRunInt < 1000) {
+ throw new NumberFormatException();
+ }
+ shutterRun = shutterRunInt;
+ logger.debug("shutterRun set to {} via configuration", shutterRun);
+ }
+ } else {
+ throw new NumberFormatException();
+ }
+ } catch (NumberFormatException e) {
+ logger.debug("Wrong configuration: {} setting must be {} or an integer >= 1000",
+ OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN, AUTO_CALIBRATION);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
+ "@text/offline.wrong-configuration");
+ shutterRun = SHUTTER_RUN_UNDEFINED;
+ }
+ updateState(CHANNEL_SHUTTER, UnDefType.UNDEF);
+ positionEstimation = POSITION_UNKNOWN;
+ }
+
+ @Override
+ protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
+ return new WhereLightAutom(wStr);
+ }
+
+ @Override
+ protected void requestChannelState(ChannelUID channel) {
+ logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
+ Where w = deviceWhere;
+ if (w != null) {
+ try {
+ send(Automation.requestStatus(w.value()));
+ } catch (OWNException e) {
+ logger.debug("Exception while requesting channel {} state: {}", channel, e.getMessage(), e);
+ }
+ } else {
+ logger.warn("Could not requestChannelState(): deviceWhere is null");
+ }
+ }
+
+ @Override
+ protected void refreshDevice(boolean refreshAll) {
+ OpenWebNetBridgeHandler brH = bridgeHandler;
+ if (brH != null) {
+ if (brH.isBusGateway() && refreshAll) {
+ long now = System.currentTimeMillis();
+ if (now - lastAllDevicesRefreshTS > ALL_DEVICES_REFRESH_INTERVAL_MSEC) {
+ try {
+ send(Automation.requestStatus(WhereLightAutom.GENERAL.value()));
+ lastAllDevicesRefreshTS = now;
+ } catch (OWNException e) {
+ logger.warn("Excpetion while requesting all devices refresh: {}", e.getMessage());
+ }
+ } else {
+ logger.debug("Refresh all devices just sent...");
+ }
+ } else {
+ requestChannelState(new ChannelUID("any")); // channel here does not make any difference
+ }
+ }
+ }
+
+ @Override
+ protected void handleChannelCommand(ChannelUID channel, Command command) {
+ switch (channel.getId()) {
+ case CHANNEL_SHUTTER:
+ handleShutterCommand(command);
+ break;
+ default: {
+ logger.info("Unsupported channel UID {}", channel);
+ }
+ }
+ }
+
+ /**
+ * Handles Automation Roller shutter command (UP/DOWN, STOP/MOVE, PERCENT xx%)
+ */
+ private void handleShutterCommand(Command command) {
+ Where w = deviceWhere;
+ if (w != null) {
+ calibrating = CALIBRATION_INACTIVE; // cancel calibration if we receive a command
+ commandRequestedWhileMoving = null;
+ try {
+ if (StopMoveType.STOP.equals(command)) {
+ send(Automation.requestStop(w.value()));
+ } else if (command instanceof UpDownType || command instanceof PercentType) {
+ if (movingState == MOVING_STATE_MOVING_UP || movingState == MOVING_STATE_MOVING_DOWN) { // already
+ // moving
+ logger.debug("# {} # already moving, STOP then defer command", deviceWhere);
+ commandRequestedWhileMoving = command;
+ sendHighPriority(Automation.requestStop(w.value()));
+ return;
+ } else {
+ if (command instanceof UpDownType) {
+ if (UpDownType.UP.equals(command)) {
+ send(Automation.requestMoveUp(w.value()));
+ } else {
+ send(Automation.requestMoveDown(w.value()));
+ }
+ } else if (command instanceof PercentType) {
+ handlePercentCommand((PercentType) command, w.value());
+ }
+ }
+ } else {
+ logger.debug("Unsupported command {} for thing {}", command, thing.getUID());
+ }
+ } catch (OWNException e) {
+ logger.debug("Exception while sending request for command {}: {}", command, e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * Handles Automation PERCENT xx% command
+ */
+ private void handlePercentCommand(PercentType command, String w) {
+ int percent = command.intValue();
+ if (percent == positionEstimation) {
+ logger.debug("# {} # handlePercentCommand() Command {}% == positionEstimation -> nothing to do", w,
+ percent);
+ return;
+ }
+ try {
+ if (percent == POSITION_DOWN) { // GO TO 100%
+ send(Automation.requestMoveDown(w));
+ } else if (percent == POSITION_UP) { // GO TO 0%
+ send(Automation.requestMoveUp(w));
+ } else { // GO TO XX%
+ logger.debug("# {} # {}% requested", deviceWhere, percent);
+ if (shutterRun == SHUTTER_RUN_UNDEFINED) {
+ logger.debug("& {} & CALIBRATION - shutterRun not configured, starting CALIBRATION...",
+ deviceWhere);
+ calibrating = CALIBRATION_ACTIVATED;
+ send(Automation.requestMoveUp(w));
+ positionRequested = percent;
+ } else if (shutterRun >= 1000 && positionEstimation != POSITION_UNKNOWN) {
+ // these two must be known to calculate moveTime.
+ // Calculate how much time we have to move and set a deadline to stop after that time
+ int moveTime = Math
+ .round(((float) Math.abs(percent - positionEstimation) / POSITION_MAX_STEPS * shutterRun));
+ logger.debug("# {} # target moveTime={}", deviceWhere, moveTime);
+ if (moveTime > MIN_STEP_TIME_MSEC) {
+ ScheduledFuture<?> mSch = moveSchedule;
+ if (mSch != null && !mSch.isDone()) {
+ // a moveSchedule was already scheduled and is not done... let's cancel the schedule
+ mSch.cancel(false);
+ logger.debug("# {} # new XX% requested, old moveSchedule cancelled", deviceWhere);
+ }
+ // send a requestFirmwareVersion message to BUS gateways to wake up the CMD connection before
+ // sending further cmds
+ OpenWebNetBridgeHandler h = bridgeHandler;
+ if (h != null && h.isBusGateway()) {
+ OpenGateway gw = h.gateway;
+ if (gw != null) {
+ if (!gw.isCmdConnectionReady()) {
+ logger.debug("# {} # waking-up CMD connection...", deviceWhere);
+ send(GatewayMgmt.requestFirmwareVersion());
+ }
+ }
+ }
+ // REMINDER: start the schedule BEFORE sending the command, because the synch command waits for
+ // ACK and can take some 300ms
+ logger.debug("# {} # Starting schedule...", deviceWhere);
+ moveSchedule = scheduler.schedule(() -> {
+ logger.debug("# {} # moveSchedule expired, sending STOP...", deviceWhere);
+ try {
+ sendHighPriority(Automation.requestStop(w));
+ } catch (OWNException ex) {
+ logger.debug("Exception while sending request for command {}: {}", command,
+ ex.getMessage(), ex);
+ }
+ }, moveTime, TimeUnit.MILLISECONDS);
+ logger.debug("# {} # ...schedule started, now sending highPriority command...", deviceWhere);
+ if (percent < positionEstimation) {
+ sendHighPriority(Automation.requestMoveUp(w));
+ } else {
+ sendHighPriority(Automation.requestMoveDown(w));
+ }
+ logger.debug("# {} # ...gateway.sendHighPriority() returned", deviceWhere);
+ } else {
+ logger.debug("# {} # moveTime <= MIN_STEP_TIME_MSEC ---> do nothing", deviceWhere);
+ }
+ } else {
+ logger.info(
+ "Command {} cannot be executed: unknown position or shutterRun configuration params not/wrongly set (thing={})",
+ command, thing.getUID());
+ }
+ }
+ } catch (OWNException e) {
+ logger.debug("Exception while sending request for command {}: {}", command, e.getMessage(), e);
+ }
+ }
+
+ @Override
+ protected String ownIdPrefix() {
+ return Who.AUTOMATION.value().toString();
+ }
+
+ @Override
+ protected void handleMessage(BaseOpenMessage msg) {
+ logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
+ updateAutomationState((Automation) msg);
+ // REMINDER: update automation state, and only after update thing status using the super method, to avoid delays
+ super.handleMessage(msg);
+ }
+
+ /**
+ * Updates automation device state based on the Automation message received from OWN network
+ *
+ * @param msg the Automation message
+ */
+ private void updateAutomationState(Automation msg) {
+ logger.debug("updateAutomationState() - msg={} what={}", msg, msg.getWhat());
+ try {
+ if (msg.isCommandTranslation()) {
+ logger.debug("msg is command translation, ignoring it");
+ return;
+ }
+ } catch (FrameException fe) {
+ logger.warn("Exception while checking WHERE command translation for frame {}: {}, ignoring it", msg,
+ fe.getMessage());
+ }
+ if (msg.isUp()) {
+ updateMovingState(MOVING_STATE_MOVING_UP);
+ if (calibrating == CALIBRATION_ACTIVATED) {
+ calibrating = CALIBRATION_GOING_UP;
+ logger.debug("& {} & CALIBRATION - started going ALL UP...", deviceWhere);
+ }
+ } else if (msg.isDown()) {
+ updateMovingState(MOVING_STATE_MOVING_DOWN);
+ if (calibrating == CALIBRATION_ACTIVATED) {
+ calibrating = CALIBRATION_GOING_DOWN;
+ logger.debug("& {} & CALIBRATION - started going ALL DOWN...", deviceWhere);
+ }
+ } else if (msg.isStop()) {
+ long stoppedAt = System.currentTimeMillis();
+ if (calibrating == CALIBRATION_GOING_DOWN && shutterRun == SHUTTER_RUN_UNDEFINED) {
+ shutterRun = (int) (stoppedAt - startedMovingAtTS);
+ logger.debug("& {} & CALIBRATION - reached DOWN ---> shutterRun={}", deviceWhere, shutterRun);
+ updateMovingState(MOVING_STATE_STOPPED);
+ logger.debug("& {} & CALIBRATION - COMPLETED, now going to {}%", deviceWhere, positionRequested);
+ handleShutterCommand(new PercentType(positionRequested));
+ Configuration configuration = editConfiguration();
+ configuration.put(OpenWebNetBindingConstants.CONFIG_PROPERTY_SHUTTER_RUN, Integer.toString(shutterRun));
+ updateConfiguration(configuration);
+ logger.debug("& {} & CALIBRATION - configuration updated: shutterRun = {}ms", deviceWhere, shutterRun);
+ } else if (calibrating == CALIBRATION_GOING_UP) {
+ updateMovingState(MOVING_STATE_STOPPED);
+ logger.debug("& {} & CALIBRATION - reached UP, now sending DOWN command...", deviceWhere);
+ calibrating = CALIBRATION_ACTIVATED;
+ Where dw = deviceWhere;
+ if (dw != null) {
+ String w = dw.value();
+ try {
+ send(Automation.requestMoveDown(w));
+ } catch (OWNException e) {
+ logger.debug("Exception while sending DOWN command during calibration: {}", e.getMessage(), e);
+ calibrating = CALIBRATION_INACTIVE;
+ }
+ }
+ } else {
+ updateMovingState(MOVING_STATE_STOPPED);
+ // do deferred command, if present
+ Command cmd = commandRequestedWhileMoving;
+ if (cmd != null) {
+ handleShutterCommand(cmd);
+ }
+ }
+ } else {
+ logger.debug("Frame {} not supported for thing {}, ignoring it.", msg, thing.getUID());
+ }
+ }
+
+ /**
+ * Updates movingState to newState
+ */
+ private void updateMovingState(int newState) {
+ if (movingState == MOVING_STATE_STOPPED) {
+ if (newState != MOVING_STATE_STOPPED) { // moving after stop
+ startedMovingAtTS = System.currentTimeMillis();
+ synchronized (DATE_FORMATTER) {
+ logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAtTS,
+ DATE_FORMATTER.format(new Date(startedMovingAtTS)));
+ }
+ }
+ } else { // we were moving
+ updatePosition();
+ if (newState != MOVING_STATE_STOPPED) { // moving after moving, take new timestamp
+ startedMovingAtTS = System.currentTimeMillis();
+ synchronized (DATE_FORMATTER) {
+ logger.debug("# {} # MOVING {} - startedMovingAt={} - {}", deviceWhere, newState, startedMovingAtTS,
+ DATE_FORMATTER.format(new Date(startedMovingAtTS)));
+ }
+ }
+ // cancel the schedule
+ ScheduledFuture<?> mSc = moveSchedule;
+ if (mSc != null && !mSc.isDone()) {
+ mSc.cancel(false);
+ }
+ }
+ movingState = newState;
+ logger.debug("# {} # movingState={} positionEstimation={} - calibrating={} shutterRun={}", deviceWhere,
+ movingState, positionEstimation, calibrating, shutterRun);
+ }
+
+ /**
+ * Updates positionEstimation and then channel state based on movedTime and current movingState
+ */
+ private void updatePosition() {
+ int newPos = POSITION_UNKNOWN;
+ if (shutterRun > 0 && startedMovingAtTS != -1) {// we have shutterRun and startedMovingAtTS defined, let's
+ // calculate new positionEstimation
+ long movedTime = System.currentTimeMillis() - startedMovingAtTS;
+ logger.debug("# {} # current positionEstimation={} movedTime={}", deviceWhere, positionEstimation,
+ movedTime);
+ int movedSteps = Math.round((float) movedTime / shutterRun * POSITION_MAX_STEPS);
+ logger.debug("# {} # movedSteps: {} {}", deviceWhere, movedSteps,
+ (movingState == MOVING_STATE_MOVING_DOWN) ? "DOWN(+)" : "UP(-)");
+ if (positionEstimation == POSITION_UNKNOWN && movedSteps >= POSITION_MAX_STEPS) { // we did a full run
+ newPos = (movingState == MOVING_STATE_MOVING_DOWN) ? POSITION_DOWN : POSITION_UP;
+ } else if (positionEstimation != POSITION_UNKNOWN) {
+ newPos = positionEstimation
+ + ((movingState == MOVING_STATE_MOVING_DOWN) ? movedSteps : -1 * movedSteps);
+ logger.debug("# {} # {} {} {} = {}", deviceWhere, positionEstimation,
+ (movingState == MOVING_STATE_MOVING_DOWN) ? "+" : "-", movedSteps, newPos);
+ if (newPos > POSITION_DOWN) {
+ newPos = POSITION_DOWN;
+ } else if (newPos < POSITION_UP) {
+ newPos = POSITION_UP;
+ }
+ }
+ }
+ if (newPos != POSITION_UNKNOWN) {
+ if (newPos != positionEstimation) {
+ updateState(CHANNEL_SHUTTER, new PercentType(newPos));
+ }
+ } else {
+ updateState(CHANNEL_SHUTTER, UnDefType.UNDEF);
+ }
+ positionEstimation = newPos;
+ }
+
+ @Override
+ public void dispose() {
+ ScheduledFuture<?> mSc = moveSchedule;
+ if (mSc != null) {
+ mSc.cancel(true);
+ }
+ super.dispose();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openwebnet.internal.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.binding.openwebnet.internal.discovery.OpenWebNetDeviceDiscoveryService;
+import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetBusBridgeConfig;
+import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetZigBeeBridgeConfig;
+import org.openhab.core.config.core.status.ConfigStatusMessage;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openwebnet4j.BUSGateway;
+import org.openwebnet4j.GatewayListener;
+import org.openwebnet4j.OpenDeviceType;
+import org.openwebnet4j.OpenGateway;
+import org.openwebnet4j.USBGateway;
+import org.openwebnet4j.communication.OWNAuthException;
+import org.openwebnet4j.communication.OWNException;
+import org.openwebnet4j.message.Automation;
+import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.EnergyManagement;
+import org.openwebnet4j.message.FrameException;
+import org.openwebnet4j.message.GatewayMgmt;
+import org.openwebnet4j.message.Lighting;
+import org.openwebnet4j.message.OpenMessage;
+import org.openwebnet4j.message.Thermoregulation;
+import org.openwebnet4j.message.What;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereZigBee;
+import org.openwebnet4j.message.Who;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetBridgeHandler} is responsible for handling communication with gateways and handling events.
+ *
+ * @author Massimo Valla - Initial contribution
+ * @author Andrea Conte - Energy management, Thermoregulation
+ * @author Gilberto Cocchi - Thermoregulation
+ */
+@NonNullByDefault
+public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implements GatewayListener {
+
+ private final Logger logger = LoggerFactory.getLogger(OpenWebNetBridgeHandler.class);
+
+ private static final int GATEWAY_ONLINE_TIMEOUT_SEC = 20; // Time to wait for the gateway to become connected
+
+ private static final int REFRESH_ALL_DEVICES_DELAY_MSEC = 500; // Delay to wait before sending all devices refresh
+ // request after a connect/reconnect
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.BRIDGE_SUPPORTED_THING_TYPES;
+
+ // ConcurrentHashMap of devices registered to this BridgeHandler
+ // association is: ownId (String) -> OpenWebNetThingHandler, with ownId = WHO.WHERE
+ private Map<String, @Nullable OpenWebNetThingHandler> registeredDevices = new ConcurrentHashMap<>();
+ private Map<String, Long> discoveringDevices = new ConcurrentHashMap<>();
+
+ protected @Nullable OpenGateway gateway;
+ private boolean isBusGateway = false;
+
+ private boolean isGatewayConnected = false;
+
+ public @Nullable OpenWebNetDeviceDiscoveryService deviceDiscoveryService;
+ private boolean reconnecting = false; // we are trying to reconnect to gateway
+ private @Nullable ScheduledFuture<?> refreshSchedule;
+
+ private boolean scanIsActive = false; // a device scan has been activated by OpenWebNetDeviceDiscoveryService;
+ private boolean discoveryByActivation;
+
+ public OpenWebNetBridgeHandler(Bridge bridge) {
+ super(bridge);
+ }
+
+ public boolean isBusGateway() {
+ return isBusGateway;
+ }
+
+ @Override
+ public void initialize() {
+ ThingTypeUID thingType = getThing().getThingTypeUID();
+ OpenGateway gw;
+ if (thingType.equals(THING_TYPE_ZB_GATEWAY)) {
+ gw = initZigBeeGateway();
+ } else {
+ gw = initBusGateway();
+ isBusGateway = true;
+ }
+ if (gw != null) {
+ gateway = gw;
+ gw.subscribe(this);
+ if (gw.isConnected()) { // gateway is already connected, device can go ONLINE
+ isGatewayConnected = true;
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ updateStatus(ThingStatus.UNKNOWN);
+ logger.debug("Trying to connect gateway {}... ", gw);
+ try {
+ gw.connect();
+ scheduler.schedule(() -> {
+ // if status is still UNKNOWN after timer ends, set the device as OFFLINE
+ if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
+ logger.info("status still UNKNOWN. Setting device={} to OFFLINE", thing.getUID());
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "@text/offline.comm-error-timeout");
+ }
+ }, GATEWAY_ONLINE_TIMEOUT_SEC, TimeUnit.SECONDS);
+ logger.debug("bridge {} initialization completed", thing.getUID());
+ } catch (OWNException e) {
+ logger.debug("gw.connect() returned OWNException: {}", e.getMessage());
+ // status is updated by callback onConnectionError()
+ }
+ }
+ }
+ }
+
+ /**
+ * Init a ZigBee gateway based on config
+ */
+ private @Nullable OpenGateway initZigBeeGateway() {
+ logger.debug("Initializing ZigBee USB Gateway");
+ OpenWebNetZigBeeBridgeConfig zbBridgeConfig = getConfigAs(OpenWebNetZigBeeBridgeConfig.class);
+ String serialPort = zbBridgeConfig.getSerialPort();
+ if (serialPort == null || serialPort.isEmpty()) {
+ logger.warn("Cannot connect ZigBee USB Gateway. No serial port has been provided in Bridge configuration.");
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/offline.conf-error-no-serial-port");
+ return null;
+ } else {
+ return new USBGateway(serialPort);
+ }
+ }
+
+ /**
+ * Init a BUS gateway based on config
+ */
+ private @Nullable OpenGateway initBusGateway() {
+ logger.debug("Initializing BUS gateway");
+ OpenWebNetBusBridgeConfig busBridgeConfig = getConfigAs(OpenWebNetBusBridgeConfig.class);
+ String host = busBridgeConfig.getHost();
+ if (host == null || host.isEmpty()) {
+ logger.warn("Cannot connect to BUS Gateway. No host/IP has been provided in Bridge configuration.");
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/offline.conf-error-no-ip-address");
+ return null;
+ } else {
+ int port = busBridgeConfig.getPort().intValue();
+ String passwd = busBridgeConfig.getPasswd();
+ String passwdMasked;
+ if (passwd.length() >= 4) {
+ passwdMasked = "******" + passwd.substring(passwd.length() - 3, passwd.length());
+ } else {
+ passwdMasked = "******";
+ }
+ discoveryByActivation = busBridgeConfig.getDiscoveryByActivation();
+ logger.debug("Creating new BUS gateway with config properties: {}:{}, pwd={}, discoveryByActivation={}",
+ host, port, passwdMasked, discoveryByActivation);
+ return new BUSGateway(host, port, passwd);
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand (command={} - channel={})", command, channelUID);
+ OpenGateway gw = gateway;
+ if (gw == null || !gw.isConnected()) {
+ logger.warn("Gateway is NOT connected, skipping command");
+ return;
+ } else {
+ if (command instanceof RefreshType) {
+ refreshAllDevices();
+ } else {
+ logger.warn("Command or channel not supported: channel={} command={}", channelUID, command);
+ }
+ }
+ }
+
+ @Override
+ public Collection<ConfigStatusMessage> getConfigStatus() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void handleRemoval() {
+ disconnectGateway();
+ super.handleRemoval();
+ }
+
+ @Override
+ public void dispose() {
+ ScheduledFuture<?> rSc = refreshSchedule;
+ if (rSc != null) {
+ rSc.cancel(true);
+ }
+ disconnectGateway();
+ super.dispose();
+ }
+
+ private void disconnectGateway() {
+ OpenGateway gw = gateway;
+ if (gw != null) {
+ gw.closeConnection();
+ gw.unsubscribe(this);
+ logger.debug("Gateway {} connection closed and unsubscribed", gw.toString());
+ gateway = null;
+ }
+ reconnecting = false;
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singleton(OpenWebNetDeviceDiscoveryService.class);
+ }
+
+ /**
+ * Search for devices connected to this bridge handler's gateway
+ *
+ * @param listener to receive device found notifications
+ */
+ public synchronized void searchDevices() {
+ scanIsActive = true;
+ logger.debug("------$$ scanIsActive={}", scanIsActive);
+ OpenGateway gw = gateway;
+ if (gw != null) {
+ if (!gw.isDiscovering()) {
+ if (!gw.isConnected()) {
+ logger.debug("------$$ Gateway '{}' is NOT connected, cannot search for devices", gw);
+ return;
+ }
+ logger.info("------$$ STARTED active SEARCH for devices on bridge '{}'", thing.getUID());
+ try {
+ gw.discoverDevices();
+ } catch (OWNException e) {
+ logger.warn("------$$ OWNException while discovering devices on bridge '{}': {}", thing.getUID(),
+ e.getMessage());
+ }
+ } else {
+ logger.debug("------$$ Searching devices on bridge '{}' already activated", thing.getUID());
+ return;
+ }
+ } else {
+ logger.warn("------$$ Cannot search devices: no gateway associated to this handler");
+ }
+ }
+
+ @Override
+ public void onNewDevice(@Nullable Where w, @Nullable OpenDeviceType deviceType, @Nullable BaseOpenMessage message) {
+ OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
+ if (discService != null) {
+ if (w != null && deviceType != null) {
+ discService.newDiscoveryResult(w, deviceType, message);
+ } else {
+ logger.warn("onNewDevice with null where/deviceType, msg={}", message);
+ }
+ } else {
+ logger.warn("onNewDevice but null deviceDiscoveryService");
+ }
+ }
+
+ @Override
+ public void onDiscoveryCompleted() {
+ logger.info("------$$ FINISHED active SEARCH for devices on bridge '{}'", thing.getUID());
+ }
+
+ /**
+ * Notifies that the scan has been stopped/aborted by OpenWebNetDeviceDiscoveryService
+ */
+ public void scanStopped() {
+ scanIsActive = false;
+ logger.debug("------$$ scanIsActive={}", scanIsActive);
+ }
+
+ private void discoverByActivation(BaseOpenMessage baseMsg) {
+ logger.debug("discoverByActivation: msg={}", baseMsg);
+ OpenWebNetDeviceDiscoveryService discService = deviceDiscoveryService;
+ if (discService == null) {
+ logger.warn("discoverByActivation: null OpenWebNetDeviceDiscoveryService, ignoring msg={}", baseMsg);
+ return;
+ }
+ // we support these types only
+ if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
+ || baseMsg instanceof Thermoregulation) {
+ BaseOpenMessage bmsg = baseMsg;
+ if (baseMsg instanceof Lighting) {
+ What what = baseMsg.getWhat();
+ if (Lighting.WhatLighting.OFF.equals(what)) { // skipping OFF msg: cannot distinguish dimmer/switch
+ logger.debug("discoverByActivation: skipping OFF msg: cannot distinguish dimmer/switch");
+ return;
+ }
+ if (Lighting.WhatLighting.ON.equals(what)) { // if not already done just now, request light status to
+ // distinguish dimmer from switch
+ if (discoveringDevices.containsKey(ownIdFromMessage(baseMsg))) {
+ logger.debug(
+ "discoverByActivation: we just requested status for this device and it's ON -> it's a switch");
+ } else {
+ OpenGateway gw = gateway;
+ if (gw != null) {
+ try {
+ discoveringDevices.put(ownIdFromMessage(baseMsg),
+ Long.valueOf(System.currentTimeMillis()));
+ gw.send(Lighting.requestStatus(baseMsg.getWhere().value()));
+ return;
+ } catch (OWNException e) {
+ logger.warn("discoverByActivation: Exception while requesting light state: {}",
+ e.getMessage());
+ return;
+ }
+ }
+ }
+ }
+ discoveringDevices.remove(ownIdFromMessage(baseMsg));
+ }
+ OpenDeviceType type = null;
+ try {
+ type = bmsg.detectDeviceType();
+ } catch (FrameException e) {
+ logger.warn("Exception while detecting device type: {}", e.getMessage());
+ }
+ if (type != null) {
+ discService.newDiscoveryResult(bmsg.getWhere(), type, bmsg);
+ } else {
+ logger.debug("discoverByActivation: no device type detected from msg: {}", bmsg);
+ }
+ }
+ }
+
+ /**
+ * Register a device ThingHandler to this BridgHandler
+ *
+ * @param ownId the device OpenWebNet id
+ * @param thingHandler the thing handler to be registered
+ */
+ protected void registerDevice(String ownId, OpenWebNetThingHandler thingHandler) {
+ if (registeredDevices.containsKey(ownId)) {
+ logger.warn("registering device with an existing ownId={}", ownId);
+ }
+ registeredDevices.put(ownId, thingHandler);
+ logger.debug("registered device ownId={}, thing={}", ownId, thingHandler.getThing().getUID());
+ }
+
+ /**
+ * Un-register a device from this bridge handler
+ *
+ * @param ownId the device OpenWebNet id
+ */
+ protected void unregisterDevice(String ownId) {
+ if (registeredDevices.remove(ownId) != null) {
+ logger.debug("un-registered device ownId={}", ownId);
+ } else {
+ logger.warn("could not un-register ownId={} (not found)", ownId);
+ }
+ }
+
+ /**
+ * Get an already registered device on this bridge handler
+ *
+ * @param ownId the device OpenWebNet id
+ * @return the registered device Thing handler or null if the id cannot be found
+ */
+ public @Nullable OpenWebNetThingHandler getRegisteredDevice(String ownId) {
+ return registeredDevices.get(ownId);
+ }
+
+ private void refreshAllDevices() {
+ logger.debug("Refreshing all devices for bridge {}", thing.getUID());
+ for (Thing ownThing : getThing().getThings()) {
+ OpenWebNetThingHandler hndlr = (OpenWebNetThingHandler) ownThing.getHandler();
+ if (hndlr != null) {
+ hndlr.refreshDevice(true);
+ }
+ }
+ }
+
+ @Override
+ public void onEventMessage(@Nullable OpenMessage msg) {
+ logger.trace("RECEIVED <<<<< {}", msg);
+ if (msg == null) {
+ logger.warn("received event msg is null");
+ return;
+ }
+ if (msg.isACK() || msg.isNACK()) {
+ return; // we ignore ACKS/NACKS
+ }
+ // GATEWAY MANAGEMENT
+ if (msg instanceof GatewayMgmt) {
+ // noop
+ return;
+ }
+
+ BaseOpenMessage baseMsg = (BaseOpenMessage) msg;
+ // let's try to get the Thing associated with this message...
+ if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
+ || baseMsg instanceof Thermoregulation) {
+ String ownId = ownIdFromMessage(baseMsg);
+ logger.debug("ownIdFromMessage({}) --> {}", baseMsg, ownId);
+ OpenWebNetThingHandler deviceHandler = registeredDevices.get(ownId);
+ if (deviceHandler == null) {
+ OpenGateway gw = gateway;
+ if (isBusGateway && ((gw != null && !gw.isDiscovering() && scanIsActive)
+ || (discoveryByActivation && !scanIsActive))) {
+ discoverByActivation(baseMsg);
+ } else {
+ logger.debug("ownId={} has NO DEVICE associated, ignoring it", ownId);
+ }
+ } else {
+ deviceHandler.handleMessage(baseMsg);
+ }
+ } else {
+ logger.debug("BridgeHandler ignoring frame {}. WHO={} is not supported by this binding", baseMsg,
+ baseMsg.getWho());
+ }
+ }
+
+ @Override
+ public void onConnected() {
+ isGatewayConnected = true;
+ Map<String, String> properties = editProperties();
+ boolean propertiesChanged = false;
+ OpenGateway gw = gateway;
+ if (gw == null) {
+ logger.warn("received onConnected() but gateway is null");
+ return;
+ }
+ if (gw instanceof USBGateway) {
+ logger.info("---- CONNECTED to ZigBee USB gateway bridge '{}' (serialPort: {})", thing.getUID(),
+ ((USBGateway) gw).getSerialPortName());
+ } else {
+ logger.info("---- CONNECTED to BUS gateway bridge '{}' ({}:{})", thing.getUID(),
+ ((BUSGateway) gw).getHost(), ((BUSGateway) gw).getPort());
+ // update serial number property (with MAC address)
+ if (properties.get(PROPERTY_SERIAL_NO) != gw.getMACAddr().toUpperCase()) {
+ properties.put(PROPERTY_SERIAL_NO, gw.getMACAddr().toUpperCase());
+ propertiesChanged = true;
+ logger.debug("updated property gw serialNumber: {}", properties.get(PROPERTY_SERIAL_NO));
+ }
+ }
+ if (properties.get(PROPERTY_FIRMWARE_VERSION) != gw.getFirmwareVersion()) {
+ properties.put(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
+ propertiesChanged = true;
+ logger.debug("updated property gw firmware version: {}", properties.get(PROPERTY_FIRMWARE_VERSION));
+ }
+ if (propertiesChanged) {
+ updateProperties(properties);
+ logger.info("properties updated for bridge '{}'", thing.getUID());
+ }
+ updateStatus(ThingStatus.ONLINE);
+ // schedule a refresh for all devices
+ refreshSchedule = scheduler.schedule(this::refreshAllDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
+ TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onConnectionError(@Nullable OWNException error) {
+ String errMsg;
+ if (error == null) {
+ errMsg = "unknown error";
+ } else {
+ errMsg = error.getMessage();
+ }
+ logger.info("---- ON CONNECTION ERROR for gateway {}: {}", gateway, errMsg);
+ isGatewayConnected = false;
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "@text/offline.comm-error-connection" + " (onConnectionError - " + errMsg + ")");
+ tryReconnectGateway();
+ }
+
+ @Override
+ public void onConnectionClosed() {
+ isGatewayConnected = false;
+ logger.debug("onConnectionClosed() - isGatewayConnected={}", isGatewayConnected);
+ // NOTE: cannot change to OFFLINE here because we are already in REMOVING state
+ }
+
+ @Override
+ public void onDisconnected(@Nullable OWNException e) {
+ isGatewayConnected = false;
+ String errMsg;
+ if (e == null) {
+ errMsg = "unknown error";
+ } else {
+ errMsg = e.getMessage();
+ }
+ logger.info("---- DISCONNECTED from gateway {}. OWNException: {}", gateway, errMsg);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "@text/offline.comm-error-disconnected" + " (onDisconnected - " + errMsg + ")");
+ tryReconnectGateway();
+ }
+
+ private void tryReconnectGateway() {
+ OpenGateway gw = gateway;
+ if (gw != null) {
+ if (!reconnecting) {
+ reconnecting = true;
+ logger.info("---- Starting RECONNECT cycle to gateway {}", gw);
+ try {
+ gw.reconnect();
+ } catch (OWNAuthException e) {
+ logger.info("---- AUTH error from gateway. Stopping re-connect");
+ reconnecting = false;
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
+ "@text/offline.conf-error-auth" + " (" + e + ")");
+ }
+ } else {
+ logger.debug("---- reconnecting=true");
+ }
+ } else {
+ logger.warn("---- cannot start RECONNECT, gateway is null");
+ }
+ }
+
+ @Override
+ public void onReconnected() {
+ reconnecting = false;
+ OpenGateway gw = gateway;
+ logger.info("---- RE-CONNECTED to bridge {}", thing.getUID());
+ if (gw != null) {
+ updateStatus(ThingStatus.ONLINE);
+ if (gw.getFirmwareVersion() != null) {
+ this.updateProperty(PROPERTY_FIRMWARE_VERSION, gw.getFirmwareVersion());
+ logger.debug("gw firmware version: {}", gw.getFirmwareVersion());
+ }
+
+ // schedule a refresh for all devices
+ refreshSchedule = scheduler.schedule(this::refreshAllDevices, REFRESH_ALL_DEVICES_DELAY_MSEC,
+ TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /**
+ * Return a ownId string (=WHO.WHERE) from the device Where address and handler
+ *
+ * @param where the Where address (to be normalized)
+ * @param handler the device handler
+ * @return the ownId String
+ */
+ protected String ownIdFromDeviceWhere(Where where, OpenWebNetThingHandler handler) {
+ return handler.ownIdPrefix() + "." + normalizeWhere(where);
+ }
+
+ /**
+ * Returns a ownId string (=WHO.WHERE) from a Who and Where address
+ *
+ * @param who the Who
+ * @param where the Where address (to be normalized)
+ * @return the ownId String
+ */
+ public String ownIdFromWhoWhere(Who who, Where where) {
+ return who.value() + "." + normalizeWhere(where);
+ }
+
+ /**
+ * Return a ownId string (=WHO.WHERE) from a BaseOpenMessage
+ *
+ * @param baseMsg the BaseOpenMessage
+ * @return the ownId String
+ */
+ public String ownIdFromMessage(BaseOpenMessage baseMsg) {
+ return baseMsg.getWho().value() + "." + normalizeWhere(baseMsg.getWhere());
+ }
+
+ /**
+ * Transform a Where address into a Thing id string
+ *
+ * @param where the Where address
+ * @return the thing Id string
+ */
+ public String thingIdFromWhere(Where where) {
+ return normalizeWhere(where); // '#' cannot be used in ThingUID;
+ }
+
+ /**
+ * Normalize a Where address
+ *
+ * @param where the Where address
+ * @return the normalized address as String
+ */
+ public String normalizeWhere(Where where) {
+ String str = where.value();
+ if (where instanceof WhereZigBee) {
+ str = ((WhereZigBee) where).valueWithUnit(WhereZigBee.UNIT_ALL); // 76543210X#9 --> 765432100#9
+ } else {
+ if (str.indexOf("#4#") == -1) { // skip APL#4#bus case
+ if (str.indexOf('#') == 0) { // Thermo central unit (#0) or zone via central unit (#Z, Z=[1-99]) --> Z
+ str = str.substring(1);
+ } else if (str.indexOf('#') > 0) { // Thermo zone Z and actuator N (Z#N, Z=[1-99], N=[1-9]) --> Z
+ str = str.substring(0, str.indexOf('#'));
+ }
+ }
+ }
+ return str.replace('#', 'h');
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openwebnet.internal.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_POWER;
+
+import java.util.Set;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.measure.quantity.Power;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.UnDefType;
+import org.openwebnet4j.OpenGateway;
+import org.openwebnet4j.communication.OWNException;
+import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.EnergyManagement;
+import org.openwebnet4j.message.FrameException;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereEnergyManagement;
+import org.openwebnet4j.message.Who;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetEnergyHandler} is responsible for handling commands/messages for a Energy Management OpenWebNet
+ * device. It extends the abstract {@link OpenWebNetThingHandler}.
+ *
+ * @author Massimo Valla - Initial contribution
+ * @author Andrea Conte - Energy management
+ */
+@NonNullByDefault
+public class OpenWebNetEnergyHandler extends OpenWebNetThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(OpenWebNetEnergyHandler.class);
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES;
+ public static final int ENERGY_SUBSCRIPTION_PERIOD = 10; // minutes
+ private @Nullable ScheduledFuture<?> notificationSchedule;
+
+ public OpenWebNetEnergyHandler(Thing thing) {
+ super(thing);
+ }
+
+ public Boolean isFirstSchedulerLaunch = true;
+
+ @Override
+ public void initialize() {
+ super.initialize();
+
+ // In order to get data from the probe we must send a command over the bus, this could be done only when the
+ // bridge is online.
+ // a) usual flow: binding is starting, the bridge isn't online (startup flow not yet completed) --> subscriber
+ // can't be started here, it will be started inside the bridgeStatusChanged event.
+ // b) thing's discovery: binding is up and running, the bridge is online --> subscriber must be started here
+ // otherwise data will be read only after a reboot.
+
+ OpenWebNetBridgeHandler h = bridgeHandler;
+ if (h != null && h.isBusGateway()) {
+ OpenGateway gw = h.gateway;
+ if (gw != null && gw.isConnected()) {
+ // bridge is online
+ subscribeToActivePowerChanges();
+ }
+ }
+ }
+
+ @Override
+ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+ super.bridgeStatusChanged(bridgeStatusInfo);
+
+ // subscribe the scheduler only after the bridge is online
+ if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
+ subscribeToActivePowerChanges();
+ }
+ }
+
+ private void subscribeToActivePowerChanges() {
+ notificationSchedule = scheduler.scheduleWithFixedDelay(() -> {
+ if (isFirstSchedulerLaunch) {
+ logger.debug(
+ "subscribeToActivePowerChanges() For WHERE={} subscribing to active power changes notification for the next {}min",
+ deviceWhere, ENERGY_SUBSCRIPTION_PERIOD);
+ } else {
+ logger.debug(
+ "subscribeToActivePowerChanges() Refreshing subscription for the next {}min for WHERE={} to active power changes notification",
+ ENERGY_SUBSCRIPTION_PERIOD, deviceWhere);
+ }
+
+ try {
+ bridgeHandler.gateway.send(EnergyManagement.setActivePowerNotificationsTime(deviceWhere.value(),
+ ENERGY_SUBSCRIPTION_PERIOD));
+ isFirstSchedulerLaunch = false;
+ } catch (Exception e) {
+ if (isFirstSchedulerLaunch) {
+ logger.warn(
+ "subscribeToActivePowerChanges() For WHERE={} could not subscribe to active power changes notifications. Exception={}",
+ deviceWhere, e.getMessage());
+ } else {
+ logger.warn(
+ "subscribeToActivePowerChanges() Unable to refresh subscription to active power changes notifications for WHERE={}. Exception={}",
+ deviceWhere, e.getMessage());
+ }
+ }
+ }, 0, ENERGY_SUBSCRIPTION_PERIOD - 1, TimeUnit.MINUTES);
+ }
+
+ @Override
+ public void dispose() {
+ if (notificationSchedule != null) {
+ logger.debug("dispose() scheduler stopped.");
+
+ notificationSchedule.cancel(false);
+ }
+ super.dispose();
+ }
+
+ @Override
+ protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
+ return new WhereEnergyManagement(wStr);
+ }
+
+ @Override
+ protected void requestChannelState(ChannelUID channel) {
+ logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
+ Where w = deviceWhere;
+ if (w != null) {
+ try {
+ send(EnergyManagement.requestActivePower(w.value()));
+ } catch (OWNException e) {
+ logger.debug("Exception while requesting channel {} state: {}", channel, e.getMessage(), e);
+ }
+ } else {
+ logger.warn("Could not requestChannelState(): deviceWhere is null");
+ }
+ }
+
+ @Override
+ protected void refreshDevice(boolean refreshAll) {
+ requestChannelState(new ChannelUID("any:any:any:any"));
+ }
+
+ @Override
+ protected void handleChannelCommand(ChannelUID channel, Command command) {
+ logger.warn("handleChannelCommand() Read only channel, unsupported command {}", command);
+ }
+
+ @Override
+ protected String ownIdPrefix() {
+ return Who.ENERGY_MANAGEMENT.value().toString();
+ }
+
+ @Override
+ protected void handleMessage(BaseOpenMessage msg) {
+ super.handleMessage(msg);
+
+ if (msg.isCommand()) {
+ logger.warn("handleMessage() Ignoring unsupported command for thing {}. Frame={}", getThing().getUID(),
+ msg);
+ return;
+ } else {
+ // fix: check for correct DIM (ActivePower / 113)
+ if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.ACTIVE_POWER)) {
+ updateActivePower(msg);
+ } else {
+ logger.debug("handleMessage() Ignoring message {} because it's not related to active power value.",
+ msg);
+ }
+ }
+ }
+
+ /**
+ * Updates energy power state based on a EnergyManagement message received from the OWN network
+ *
+ * @param msg the EnergyManagement message received
+ * @throws FrameException
+ */
+ private void updateActivePower(BaseOpenMessage msg) {
+ Integer activePower;
+ try {
+ activePower = Integer.parseInt(msg.getDimValues()[0]);
+ updateState(CHANNEL_POWER, new QuantityType<Power>(activePower, Units.WATT));
+ } catch (FrameException e) {
+ logger.warn("FrameException on frame {}: {}", msg, e.getMessage());
+ updateState(CHANNEL_POWER, UnDefType.UNDEF);
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openwebnet.internal.handler;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.types.Command;
+import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereLightAutom;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetGenericHandler} is responsible for handling Generic OpenWebNet
+ * devices. It does not too much, but it is needed to avoid handler errors and to tell the user
+ * that some device has been found by the gateway but it was not recognised.
+ * It extends the abstract {@link OpenWebNetThingHandler}.
+ *
+ * @author Massimo Valla - Initial contribution
+ */
+@NonNullByDefault
+public class OpenWebNetGenericHandler extends OpenWebNetThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(OpenWebNetGenericHandler.class);
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.GENERIC_SUPPORTED_THING_TYPES;
+
+ public OpenWebNetGenericHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ }
+
+ @Override
+ protected void requestChannelState(ChannelUID channel) {
+ // do nothing
+ logger.warn("Generic: there are no channels");
+ }
+
+ @Override
+ protected void refreshDevice(boolean refreshAll) {
+ // do nothing
+ logger.warn("Generic: nothing to refresh");
+ }
+
+ @Override
+ protected void handleChannelCommand(ChannelUID channel, Command command) {
+ // do nothing
+ logger.warn("Generic: there are no channels");
+ }
+
+ @Override
+ protected String ownIdPrefix() {
+ return "G";
+ }
+
+ @Override
+ protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
+ return new WhereLightAutom(wStr);
+ }
+
+ @Override
+ protected void handleMessage(BaseOpenMessage msg) {
+ super.handleMessage(msg);
+ // do nothing
+ logger.warn("Generic: handleMessage() nothing to do!");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openwebnet.internal.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.core.library.types.IncreaseDecreaseType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.types.Command;
+import org.openwebnet4j.communication.OWNException;
+import org.openwebnet4j.communication.Response;
+import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.FrameException;
+import org.openwebnet4j.message.Lighting;
+import org.openwebnet4j.message.What;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereLightAutom;
+import org.openwebnet4j.message.WhereZigBee;
+import org.openwebnet4j.message.Who;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetLightingHandler} is responsible for handling commands/messages for a Lighting OpenWebNet device.
+ * It extends the abstract {@link OpenWebNetThingHandler}.
+ *
+ * @author Massimo Valla - Initial contribution
+ */
+@NonNullByDefault
+public class OpenWebNetLightingHandler extends OpenWebNetThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(OpenWebNetLightingHandler.class);
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.LIGHTING_SUPPORTED_THING_TYPES;
+
+ // interval to interpret ON as response to requestStatus
+ private static final int BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC = 250;
+
+ // time to wait before sending a statusRequest, to avoid repeated requests and ensure dimmer has reached its final
+ // level
+ private static final int BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC = 900;
+
+ private static final int UNKNOWN_STATE = 1000;
+
+ private static long lastAllDevicesRefreshTS = -1; // timestamp when the last request for all device refresh was sent
+ // for this handler
+
+ protected static final int ALL_DEVICES_REFRESH_INTERVAL_MSEC = 60000; // interval in msec before sending another all
+ // devices refresh request
+
+ private long lastBrightnessChangeSentTS = 0; // timestamp when last brightness change was sent to the device
+
+ private long lastStatusRequestSentTS = 0; // timestamp when last status request was sent to the device
+
+ private int brightness = UNKNOWN_STATE; // current brightness percent value for this device
+
+ private int brightnessBeforeOff = UNKNOWN_STATE; // latest brightness before device was set to off
+
+ private int sw[] = { UNKNOWN_STATE, UNKNOWN_STATE }; // current switch(es) state
+
+ public OpenWebNetLightingHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ protected void requestChannelState(ChannelUID channel) {
+ logger.debug("requestChannelState() thingUID={} channel={}", thing.getUID(), channel.getId());
+ requestStatus(channel.getId());
+ }
+
+ /** helper method to request light status based on channel */
+ private void requestStatus(String channelId) {
+ Where w = deviceWhere;
+ if (w != null) {
+ try {
+ lastStatusRequestSentTS = System.currentTimeMillis();
+ Response res = send(Lighting.requestStatus(toWhere(channelId)));
+ if (res != null && res.isSuccess()) {
+ // set thing online, if not already
+ ThingStatus ts = getThing().getStatus();
+ if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
+ updateStatus(ThingStatus.ONLINE);
+ }
+ }
+ } catch (OWNException e) {
+ logger.warn("requestStatus() Exception while requesting light state: {}", e.getMessage());
+ }
+ } else {
+ logger.warn("Could not requestStatus(): deviceWhere is null");
+ }
+ }
+
+ @Override
+ protected void refreshDevice(boolean refreshAll) {
+ OpenWebNetBridgeHandler brH = bridgeHandler;
+ if (brH != null) {
+ if (brH.isBusGateway() && refreshAll) {
+ long now = System.currentTimeMillis();
+ if (now - lastAllDevicesRefreshTS > ALL_DEVICES_REFRESH_INTERVAL_MSEC) {
+ try {
+ send(Lighting.requestStatus(WhereLightAutom.GENERAL.value()));
+ lastAllDevicesRefreshTS = now;
+ } catch (OWNException e) {
+ logger.warn("Excpetion while requesting all devices refresh: {}", e.getMessage());
+ }
+ } else {
+ logger.debug("Refresh all devices just sent...");
+ }
+ } else { // USB or BUS-single device
+ ThingTypeUID thingType = thing.getThingTypeUID();
+ if (THING_TYPE_ZB_ON_OFF_SWITCH_2UNITS.equals(thingType)) {
+ // Unfortunately using USB Gateway OpenWebNet both switch endpoints cannot be requested at the same
+ // time using UNIT 00 because USB stick returns NACK, so we need to send a request status for both
+ // endpoints
+ requestStatus(CHANNEL_SWITCH_02);
+ }
+ requestStatus(""); // channel here does not make any difference, see {@link #toWhere()}
+ }
+ }
+ }
+
+ @Override
+ protected void handleChannelCommand(ChannelUID channel, Command command) {
+ switch (channel.getId()) {
+ case CHANNEL_BRIGHTNESS:
+ handleBrightnessCommand(command);
+ break;
+ case CHANNEL_SWITCH:
+ case CHANNEL_SWITCH_01:
+ case CHANNEL_SWITCH_02:
+ handleSwitchCommand(channel, command);
+ break;
+ default: {
+ logger.warn("Unsupported ChannelUID {}", channel);
+ }
+ }
+ }
+
+ /**
+ * Handles Lighting switch command for a channel
+ *
+ * @param channel the channel
+ * @param command the command
+ */
+ private void handleSwitchCommand(ChannelUID channel, Command command) {
+ logger.debug("handleSwitchCommand() (command={} - channel={})", command, channel);
+ if (command instanceof OnOffType) {
+ try {
+ if (OnOffType.ON.equals(command)) {
+ send(Lighting.requestTurnOn(toWhere(channel.getId())));
+ } else if (OnOffType.OFF.equals(command)) {
+ send(Lighting.requestTurnOff(toWhere(channel.getId())));
+ }
+ } catch (OWNException e) {
+ logger.warn("Exception while processing command {}: {}", command, e.getMessage());
+ }
+ } else {
+ logger.warn("Unsupported command: {}", command);
+ }
+ }
+
+ /**
+ * Handles Lighting brightness command (xx%, INCREASE, DECREASE, ON, OFF)
+ *
+ * @param command the command
+ */
+ private void handleBrightnessCommand(Command command) {
+ logger.debug("handleBrightnessCommand() command={}", command);
+ if (command instanceof PercentType) {
+ dimLightTo(((PercentType) command).intValue(), command);
+ } else if (command instanceof IncreaseDecreaseType) {
+ if (IncreaseDecreaseType.INCREASE.equals(command)) {
+ dimLightTo(brightness + 10, command);
+ } else { // DECREASE
+ dimLightTo(brightness - 10, command);
+ }
+ } else if (command instanceof OnOffType) {
+ if (OnOffType.ON.equals(command)) {
+ dimLightTo(brightnessBeforeOff, command);
+ } else { // OFF
+ dimLightTo(0, command);
+ }
+ } else {
+ logger.warn("Cannot handle command {} for thing {}", command, getThing().getUID());
+ }
+ }
+
+ /**
+ * Helper method to dim light to given percent
+ */
+ private void dimLightTo(int percent, Command command) {
+ logger.debug(" DIM dimLightTo({}) bri={} briBeforeOff={}", percent, brightness, brightnessBeforeOff);
+ int newBrightness = percent;
+ if (newBrightness == UNKNOWN_STATE) {
+ // we do not know last brightness -> set dimmer to 100%
+ newBrightness = 100;
+ } else if (newBrightness <= 0) {
+ newBrightness = 0;
+ brightnessBeforeOff = brightness;
+ logger.debug(" DIM saved bri before sending bri=0 command to device");
+ } else if (newBrightness > 100) {
+ newBrightness = 100;
+ }
+ What newBrightnessWhat = Lighting.percentToWhat(newBrightness);
+ logger.debug(" DIM newBrightness={} newBrightnessWhat={}", newBrightness, newBrightnessWhat);
+ @Nullable
+ What brightnessWhat = null;
+ if (brightness != UNKNOWN_STATE) {
+ brightnessWhat = Lighting.percentToWhat(brightness);
+ }
+ if (brightnessWhat == null || !newBrightnessWhat.value().equals(brightnessWhat.value())) {
+ logger.debug(" DIM brightnessWhat {} --> {} WHAT level change needed", brightnessWhat,
+ newBrightnessWhat);
+ Where w = deviceWhere;
+ if (w != null) {
+ try {
+ lastBrightnessChangeSentTS = System.currentTimeMillis();
+ send(Lighting.requestDimTo(w.value(), newBrightnessWhat));
+ } catch (OWNException e) {
+ logger.warn("Exception while sending dimTo request for command {}: {}", command, e.getMessage());
+ }
+ }
+ } else {
+ logger.debug(" DIM brightnessWhat {} --> {} NO WHAT level change needed", brightnessWhat,
+ newBrightnessWhat);
+ }
+ brightness = newBrightness;
+ updateState(CHANNEL_BRIGHTNESS, new PercentType(brightness));
+ logger.debug(" DIM---END bri={} briBeforeOff={}", brightness, brightnessBeforeOff);
+ }
+
+ @Override
+ protected String ownIdPrefix() {
+ return Who.LIGHTING.value().toString();
+ }
+
+ @Override
+ protected void handleMessage(BaseOpenMessage msg) {
+ logger.debug("handleMessage({}) for thing: {}", msg, thing.getUID());
+ super.handleMessage(msg);
+ ThingTypeUID thingType = thing.getThingTypeUID();
+ if (THING_TYPE_ZB_DIMMER.equals(thingType) || THING_TYPE_BUS_DIMMER.equals(thingType)) {
+ updateBrightness((Lighting) msg);
+ } else {
+ updateOnOffState((Lighting) msg);
+ }
+ }
+
+ /**
+ * Updates brightness based on OWN Lighting message received
+ *
+ * @param msg the Lighting message received
+ */
+ private synchronized void updateBrightness(Lighting msg) {
+ logger.debug(" $BRI updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
+ brightnessBeforeOff);
+ long now = System.currentTimeMillis();
+ long delta = now - lastBrightnessChangeSentTS;
+ boolean belowThresh = delta < BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC;
+ logger.debug(" $BRI delta={}ms {}", delta, (belowThresh ? "< DELAY" : ""));
+ if (belowThresh) {
+ // we just sent a command from OH, so we can ignore this message from network
+ logger.debug(" $BRI a command was sent {} < {} ms --> no action needed", delta,
+ BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC);
+ } else {
+ if (msg.isOn()) {
+ // if we have not just sent a requestStatus, on ON event we send requestStatus to know current level
+ long deltaStatusReq = now - lastStatusRequestSentTS;
+ if (deltaStatusReq > BRIGHTNESS_STATUS_REQUEST_INTERVAL_MSEC) {
+ logger.debug(" $BRI 'ON' is new notification from network, scheduling requestStatus...");
+ // we must wait BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC to be sure dimmer has reached its final level
+ scheduler.schedule(() -> {
+ requestStatus(CHANNEL_BRIGHTNESS);
+ }, BRIGHTNESS_STATUS_REQUEST_DELAY_MSEC, TimeUnit.MILLISECONDS);
+ return;
+ } else {
+ // otherwise we interpret this ON event as the requestStatus response event with level=1
+ // so we proceed to call updateBrightnessState()
+ logger.debug(" $BRI 'ON' is the requestStatus response level");
+ }
+ }
+ logger.debug(" $BRI update from network");
+ if (msg.getWhat() != null) {
+ updateBrightnessState(msg);
+ } else { // dimension notification
+ if (msg.getDim() == Lighting.DimLighting.DIMMER_LEVEL_100) {
+ int newBrightness;
+ try {
+ newBrightness = msg.parseDimmerLevel100();
+ } catch (FrameException fe) {
+ logger.warn("updateBrightness() Wrong value for dimmerLevel100 in message: {}", msg);
+ return;
+ }
+ logger.debug(" $BRI DIMMER_LEVEL_100 newBrightness={}", newBrightness);
+ updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
+ if (newBrightness == 0) {
+ brightnessBeforeOff = brightness;
+ }
+ brightness = newBrightness;
+ } else {
+ logger.warn("updateBrightness() Cannot handle message {} for thing {}", msg, getThing().getUID());
+ return;
+ }
+ }
+ }
+ logger.debug(" $BRI---END updateBrightness({}) || bri={} briBeforeOff={}", msg, brightness,
+ brightnessBeforeOff);
+ }
+
+ /**
+ * Updates light brightness state based on a OWN Lighting message
+ *
+ * @param msg the Lighting message received
+ */
+ private void updateBrightnessState(Lighting msg) {
+ What w = msg.getWhat();
+ if (w != null) {
+ if (Lighting.WhatLighting.ON.equals(w)) {
+ w = Lighting.WhatLighting.DIMMER_LEVEL_2; // levels start at 2
+ }
+ int newBrightnessWhat = w.value();
+ int brightnessWhat = UNKNOWN_STATE;
+ if (brightness != UNKNOWN_STATE) {
+ brightnessWhat = Lighting.percentToWhat(brightness).value();
+ }
+ logger.debug(" $BRI brightnessWhat {} --> {}", brightnessWhat, newBrightnessWhat);
+ if (brightnessWhat != newBrightnessWhat) {
+ int newBrightness = Lighting.levelToPercent(newBrightnessWhat);
+ updateState(CHANNEL_BRIGHTNESS, new PercentType(newBrightness));
+ if (msg.isOff()) {
+ brightnessBeforeOff = brightness;
+ }
+ brightness = newBrightness;
+ logger.debug(" $BRI brightness CHANGED to {}", brightness);
+ } else {
+ logger.debug(" $BRI no change");
+ }
+ }
+ }
+
+ /**
+ * Updates light on/off state based on a OWN Lighting event message received
+ *
+ * @param msg the Lighting message received
+ */
+ private void updateOnOffState(Lighting msg) {
+ OpenWebNetBridgeHandler brH = bridgeHandler;
+ if (brH != null) {
+ if (msg.isOn() || msg.isOff()) {
+ String channelId;
+ int switchId = 0;
+ if (brH.isBusGateway()) {
+ channelId = CHANNEL_SWITCH;
+ } else {
+ WhereZigBee w = (WhereZigBee) (msg.getWhere());
+ if (WhereZigBee.UNIT_02.equals(w.getUnit())) {
+ channelId = CHANNEL_SWITCH_02;
+ switchId = 1;
+ } else {
+ channelId = CHANNEL_SWITCH_01;
+ }
+ }
+ int currentSt = sw[switchId];
+ int newSt = (msg.isOn() ? 1 : 0);
+ if (newSt != currentSt) {
+ updateState(channelId, (newSt == 1 ? OnOffType.ON : OnOffType.OFF));
+ sw[switchId] = newSt;
+ logger.debug(" {} ONOFF CHANGED to {}", ownId, newSt);
+ } else {
+ logger.debug(" {} ONOFF no change", ownId);
+ }
+ } else {
+ logger.debug("updateOnOffState() Ignoring unsupported WHAT for thing {}. Frame={}", getThing().getUID(),
+ msg.getFrameValue());
+ return;
+ }
+ }
+ }
+
+ @Override
+ protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
+ return new WhereLightAutom(wStr);
+ }
+
+ /**
+ * Returns a WHERE address string based on channelId string
+ *
+ * @param channelId the channelId string
+ **/
+ @Nullable
+ private String toWhere(String channelId) {
+ Where w = deviceWhere;
+ if (w != null) {
+ OpenWebNetBridgeHandler brH = bridgeHandler;
+ if (brH != null) {
+ if (brH.isBusGateway()) {
+ return w.value();
+ } else if (channelId.equals(CHANNEL_SWITCH_02)) {
+ return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_02);
+ } else { // CHANNEL_SWITCH_01 or other channels
+ return ((WhereZigBee) w).valueWithUnit(WhereZigBee.UNIT_01);
+ }
+ }
+ }
+ return null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openwebnet.internal.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusInfo;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.UnDefType;
+import org.openwebnet4j.communication.OWNException;
+import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.FrameException;
+import org.openwebnet4j.message.MalformedFrameException;
+import org.openwebnet4j.message.Thermoregulation;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereThermo;
+import org.openwebnet4j.message.Who;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetThermoregulationHandler} is responsible for handling commands/messages for Thermoregulation
+ * Things. It extends the abstract {@link OpenWebNetThingHandler}.
+ *
+ * @author Massimo Valla - Initial contribution
+ * @author Andrea Conte - Thermoregulation
+ * @author Gilberto Cocchi - Thermoregulation
+ */
+@NonNullByDefault
+public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(OpenWebNetThermoregulationHandler.class);
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.THERMOREGULATION_SUPPORTED_THING_TYPES;
+
+ private boolean isTempSensor = false; // is the thing a sensor ?
+
+ private double currentSetPointTemp = 11.5d; // 11.5 is the default setTemp used in MyHomeUP mobile app
+
+ private Thermoregulation.Function currentFunction = Thermoregulation.Function.GENERIC;
+
+ public OpenWebNetThermoregulationHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+ super.bridgeStatusChanged(bridgeStatusInfo);
+ // when the bridge is ONLINE request for thing states (temp, setTemp, fanSpeed...)
+ if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
+ refreshDevice(false);
+ }
+ }
+
+ @Override
+ protected void handleChannelCommand(ChannelUID channel, Command command) {
+ switch (channel.getId()) {
+ case CHANNEL_TEMP_SETPOINT:
+ handleSetpoint(command);
+ break;
+ case CHANNEL_FUNCTION:
+ handleFunction(command);
+ break;
+ case CHANNEL_MODE:
+ handleMode(command);
+ break;
+ case CHANNEL_FAN_SPEED:
+ handleSetFanSpeed(command);
+ break;
+ default: {
+ logger.warn("handleChannelCommand() Unsupported ChannelUID {}", channel.getId());
+ }
+ }
+ }
+
+ @Override
+ protected void requestChannelState(ChannelUID channel) {
+ refreshDevice(false);
+ }
+
+ @Override
+ protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
+ WhereThermo wt = new WhereThermo(wStr);
+ if (wt.isProbe()) {
+ isTempSensor = true;
+ }
+ return wt;
+ }
+
+ @Override
+ protected String ownIdPrefix() {
+ return Who.THERMOREGULATION.value().toString();
+ }
+
+ private void handleSetFanSpeed(Command command) {
+ if (command instanceof StringType) {
+ Where w = deviceWhere;
+ if (w != null) {
+ try {
+ Thermoregulation.FanCoilSpeed speed = Thermoregulation.FanCoilSpeed.valueOf(command.toString());
+ send(Thermoregulation.requestWriteFanCoilSpeed(w.value(), speed));
+ } catch (OWNException e) {
+ logger.warn("handleSetFanSpeed() {}", e.getMessage());
+ } catch (IllegalArgumentException e) {
+ logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command,
+ getThing().getUID());
+ return;
+ }
+ }
+ } else {
+ logger.warn("handleSetFanSpeed() Unsupported command {} for thing {}", command, getThing().getUID());
+ }
+ }
+
+ private void handleSetpoint(Command command) {
+ if (command instanceof QuantityType || command instanceof DecimalType) {
+ Where w = deviceWhere;
+ if (w != null) {
+ double newTemp = 0;
+ if (command instanceof QuantityType) {
+ QuantityType<?> tempCelsius = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
+ if (tempCelsius != null) {
+ newTemp = tempCelsius.doubleValue();
+ }
+ } else {
+ newTemp = ((DecimalType) command).doubleValue();
+ }
+ try {
+ send(Thermoregulation.requestWriteSetpointTemperature(w.value(), newTemp, currentFunction));
+ } catch (MalformedFrameException | OWNException e) {
+ logger.warn("handleSetpoint() {}", e.getMessage());
+ }
+ }
+ } else {
+ logger.warn("handleSetpoint() Unsupported command {} for thing {}", command, getThing().getUID());
+ }
+ }
+
+ private void handleMode(Command command) {
+ if (command instanceof StringType) {
+ Where w = deviceWhere;
+ if (w != null) {
+ try {
+ Thermoregulation.OperationMode mode = Thermoregulation.OperationMode.valueOf(command.toString());
+ send(Thermoregulation.requestWriteMode(w.value(), mode, currentFunction, currentSetPointTemp));
+ } catch (OWNException e) {
+ logger.warn("handleMode() {}", e.getMessage());
+ } catch (IllegalArgumentException e) {
+ logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
+ return;
+ }
+ }
+ } else {
+ logger.warn("handleMode() Unsupported command {} for thing {}", command, getThing().getUID());
+ }
+ }
+
+ private void handleFunction(Command command) {
+ if (command instanceof StringType) {
+ Where w = deviceWhere;
+ if (w != null) {
+ try {
+ Thermoregulation.Function function = Thermoregulation.Function.valueOf(command.toString());
+ send(Thermoregulation.requestWriteFunction(w.value(), function));
+ } catch (OWNException e) {
+ logger.warn("handleFunction() {}", e.getMessage());
+ } catch (IllegalArgumentException e) {
+ logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
+ return;
+ }
+ }
+ } else {
+ logger.warn("handleFunction() Unsupported command {} for thing {}", command, getThing().getUID());
+ }
+ }
+
+ @Override
+ protected void handleMessage(BaseOpenMessage msg) {
+ super.handleMessage(msg);
+ if (msg.isCommand()) {
+ updateModeAndFunction((Thermoregulation) msg);
+ } else {
+ if (msg.getDim() == null) {
+ return;
+ }
+ if (msg.getDim() == Thermoregulation.DimThermo.TEMPERATURE
+ || msg.getDim() == Thermoregulation.DimThermo.PROBE_TEMPERATURE) {
+ updateTemperature((Thermoregulation) msg);
+ } else if (msg.getDim() == Thermoregulation.DimThermo.TEMP_SETPOINT
+ || msg.getDim() == Thermoregulation.DimThermo.COMPLETE_PROBE_STATUS) {
+ updateSetpoint((Thermoregulation) msg);
+ } else if (msg.getDim() == Thermoregulation.DimThermo.VALVES_STATUS) {
+ updateValveStatus((Thermoregulation) msg);
+ } else if (msg.getDim() == Thermoregulation.DimThermo.ACTUATOR_STATUS) {
+ updateActuatorStatus((Thermoregulation) msg);
+ } else if (msg.getDim() == Thermoregulation.DimThermo.FAN_COIL_SPEED) {
+ updateFanCoilSpeed((Thermoregulation) msg);
+ } else {
+ logger.debug("handleMessage() Ignoring unsupported DIM {} for thing {}. Frame={}", msg.getDim(),
+ getThing().getUID(), msg);
+ }
+ }
+ }
+
+ private void updateModeAndFunction(Thermoregulation tmsg) {
+ if (tmsg.getWhat() == null) {
+ logger.debug("updateModeAndFunction() Could not parse Mode or Function from {} (what is null)",
+ tmsg.getFrameValue());
+ return;
+ }
+
+ Thermoregulation.WhatThermo w = Thermoregulation.WhatThermo.fromValue(tmsg.getWhat().value());
+
+ if (w.mode() == null) {
+ logger.debug("updateModeAndFunction() Could not parse Mode from: {}", tmsg.getFrameValue());
+ return;
+ }
+ if (w.function() == null) {
+ logger.debug("updateModeAndFunction() Could not parse Function from: {}", tmsg.getFrameValue());
+ return;
+ }
+
+ Thermoregulation.OperationMode mode = w.mode();
+ Thermoregulation.Function function = w.function();
+
+ if (w == Thermoregulation.WhatThermo.HEATING) {
+ function = Thermoregulation.Function.HEATING;
+ } else if (w == Thermoregulation.WhatThermo.CONDITIONING) {
+ function = Thermoregulation.Function.COOLING;
+ }
+
+ updateState(CHANNEL_MODE, new StringType(mode.toString()));
+ updateState(CHANNEL_FUNCTION, new StringType(function.toString()));
+
+ // store current function
+ currentFunction = function;
+ }
+
+ private void updateTemperature(Thermoregulation tmsg) {
+ try {
+ double temp = Thermoregulation.parseTemperature(tmsg);
+ updateState(CHANNEL_TEMPERATURE, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
+ } catch (FrameException e) {
+ logger.warn("updateTemperature() FrameException on frame {}: {}", tmsg, e.getMessage());
+ updateState(CHANNEL_TEMPERATURE, UnDefType.UNDEF);
+ }
+ }
+
+ private void updateSetpoint(Thermoregulation tmsg) {
+ try {
+ double temp = Thermoregulation.parseTemperature(tmsg);
+ updateState(CHANNEL_TEMP_SETPOINT, getAsQuantityTypeOrNull(temp, SIUnits.CELSIUS));
+ currentSetPointTemp = temp;
+ } catch (FrameException e) {
+ logger.warn("updateSetpoint() FrameException on frame {}: {}", tmsg, e.getMessage());
+ updateState(CHANNEL_TEMP_SETPOINT, UnDefType.UNDEF);
+ }
+ }
+
+ private void updateFanCoilSpeed(Thermoregulation tmsg) {
+ try {
+ Thermoregulation.FanCoilSpeed speed = Thermoregulation.parseFanCoilSpeed(tmsg);
+ updateState(CHANNEL_FAN_SPEED, new StringType(speed.toString()));
+ } catch (FrameException e) {
+ logger.warn("updateFanCoilSpeed() FrameException on frame {}: {}", tmsg, e.getMessage());
+ updateState(CHANNEL_FAN_SPEED, UnDefType.UNDEF);
+ }
+ }
+
+ private void updateValveStatus(Thermoregulation tmsg) {
+ try {
+ Thermoregulation.ValveOrActuatorStatus cv = Thermoregulation.parseValveStatus(tmsg,
+ Thermoregulation.WhatThermo.CONDITIONING);
+ updateState(CHANNEL_CONDITIONING_VALVES, new StringType(cv.toString()));
+
+ Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseValveStatus(tmsg,
+ Thermoregulation.WhatThermo.HEATING);
+ updateState(CHANNEL_HEATING_VALVES, new StringType(hv.toString()));
+ } catch (FrameException e) {
+ logger.warn("updateValveStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
+ updateState(CHANNEL_CONDITIONING_VALVES, UnDefType.UNDEF);
+ updateState(CHANNEL_HEATING_VALVES, UnDefType.UNDEF);
+ }
+ }
+
+ private void updateActuatorStatus(Thermoregulation tmsg) {
+ try {
+ Thermoregulation.ValveOrActuatorStatus hv = Thermoregulation.parseActuatorStatus(tmsg);
+ updateState(CHANNEL_ACTUATORS, new StringType(hv.toString()));
+ } catch (FrameException e) {
+ logger.warn("updateActuatorStatus() FrameException on frame {}: {}", tmsg, e.getMessage());
+ updateState(CHANNEL_ACTUATORS, UnDefType.UNDEF);
+ }
+ }
+
+ @Override
+ protected void refreshDevice(boolean refreshAll) {
+ if (deviceWhere != null) {
+ String w = deviceWhere.value();
+ try {
+ send(Thermoregulation.requestTemperature(w));
+ if (!this.isTempSensor) {
+ // for bus_thermo_zone request also other single channels updates
+ send(Thermoregulation.requestSetPointTemperature(w));
+ send(Thermoregulation.requestFanCoilSpeed(w));
+ send(Thermoregulation.requestMode(w));
+ send(Thermoregulation.requestValvesStatus(w));
+ send(Thermoregulation.requestActuatorsStatus(w));
+ }
+ } catch (OWNException e) {
+ logger.warn("refreshDevice() where='{}' returned OWNException {}", w, e.getMessage());
+ }
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.openwebnet.internal.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
+
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.measure.Quantity;
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.openwebnet4j.OpenGateway;
+import org.openwebnet4j.communication.OWNException;
+import org.openwebnet4j.communication.Response;
+import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.OpenMessage;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereZigBee;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetThingHandler} is responsible for handling commands for a OpenWebNet device.
+ * It's the abstract class for all OpenWebNet things. It should be extended by each specific OpenWebNet category of
+ * device (WHO).
+ *
+ * @author Massimo Valla - Initial contribution
+ */
+@NonNullByDefault
+public abstract class OpenWebNetThingHandler extends BaseThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(OpenWebNetThingHandler.class);
+
+ protected @Nullable OpenWebNetBridgeHandler bridgeHandler;
+ protected @Nullable String ownId; // OpenWebNet identifier for this device: WHO.WHERE
+ protected @Nullable Where deviceWhere; // this device Where address
+
+ protected @Nullable ScheduledFuture<?> refreshTimeout;
+
+ public OpenWebNetThingHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ Bridge bridge = getBridge();
+ if (bridge != null) {
+ OpenWebNetBridgeHandler brH = (OpenWebNetBridgeHandler) bridge.getHandler();
+ if (brH != null) {
+ bridgeHandler = brH;
+
+ final String configDeviceWhere = (String) getConfig().get(CONFIG_PROPERTY_WHERE);
+ if (configDeviceWhere == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/offline.conf-error-where");
+ } else {
+ Where w;
+ try {
+ if (brH.isBusGateway()) {
+ w = buildBusWhere(configDeviceWhere);
+ } else {
+ w = new WhereZigBee(configDeviceWhere);
+ }
+ } catch (IllegalArgumentException ia) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/offline.conf-error-where");
+ return;
+ }
+ deviceWhere = w;
+ final String oid = brH.ownIdFromDeviceWhere(w, this);
+ ownId = oid;
+ Map<String, String> properties = editProperties();
+ properties.put(PROPERTY_OWNID, oid);
+ updateProperties(properties);
+ brH.registerDevice(oid, this);
+ logger.debug("associated thing to bridge with ownId={}", ownId);
+ updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/unknown.waiting-state");
+ }
+ }
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/offline.conf-error-no-bridge");
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channel, Command command) {
+ logger.debug("handleCommand() (command={} - channel={})", command, channel);
+ OpenWebNetBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ OpenGateway gw = handler.gateway;
+ if (gw != null && !gw.isConnected()) {
+ logger.info("Cannot handle {} command for {}: gateway is not connected", command, getThing().getUID());
+ return;
+ }
+ if (deviceWhere == null) {
+ logger.info("Cannot handle {} command for {}: 'where' parameter is not configured or is invalid",
+ command, getThing().getUID());
+ return;
+ }
+ if (command instanceof RefreshType) {
+ requestChannelState(channel);
+ // set a schedule to put device OFFLINE if no answer is received after THING_STATE_REQ_TIMEOUT_SEC
+ refreshTimeout = scheduler.schedule(() -> {
+ if (thing.getStatus().equals(ThingStatus.UNKNOWN)) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Could not get channel state (timer expired)");
+ }
+ }, THING_STATE_REQ_TIMEOUT_SEC, TimeUnit.SECONDS);
+ } else {
+ handleChannelCommand(channel, command);
+ }
+ } else {
+ logger.debug("Thing {} is not associated to any gateway, skipping command", getThing().getUID());
+ }
+ }
+
+ /**
+ * Handles a command for the specific channel for this thing.
+ * It must be implemented by each specific OpenWebNet category of device (WHO), based on channel
+ *
+ * @param channel specific ChannleUID
+ * @param command the Command to be executed
+ */
+ protected abstract void handleChannelCommand(ChannelUID channel, Command command);
+
+ /**
+ * Handle incoming message from OWN network via bridge Thing, directed to this device. It should be further
+ * implemented by each specific device handler.
+ *
+ * @param msg the message to handle
+ */
+ protected void handleMessage(BaseOpenMessage msg) {
+ ThingStatus ts = getThing().getStatus();
+ if (ThingStatus.ONLINE != ts && ThingStatus.REMOVING != ts && ThingStatus.REMOVED != ts) {
+ updateStatus(ThingStatus.ONLINE);
+ }
+ }
+
+ /**
+ * Helper method to send OWN messages from ThingHandlers
+ */
+ protected @Nullable Response send(OpenMessage msg) throws OWNException {
+ OpenWebNetBridgeHandler bh = bridgeHandler;
+ if (bh != null) {
+ OpenGateway gw = bh.gateway;
+ if (gw != null) {
+ return gw.send(msg);
+ }
+ }
+ logger.warn("Couldn't send message {}: handler or gateway is null", msg);
+ return null;
+ }
+
+ /**
+ * Helper method to send with high priority OWN messages from ThingsHandlers
+ */
+ protected @Nullable Response sendHighPriority(OpenMessage msg) throws OWNException {
+ OpenWebNetBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ OpenGateway gw = handler.gateway;
+ if (gw != null) {
+ return gw.sendHighPriority(msg);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Request the state for the specified channel
+ *
+ * @param channel the {@link ChannelUID} to request the state for
+ */
+ protected abstract void requestChannelState(ChannelUID channel);
+
+ /**
+ * Refresh the device
+ *
+ * @param refreshAll set true if all devices of the binding should be refreshed with one command, if possible
+ */
+ protected abstract void refreshDevice(boolean refreshAll);
+
+ /**
+ * Abstract builder for device Where address, to be implemented by each subclass to choose the right Where subclass
+ * (the method is used only if the Thing is associated to a BUS gateway).
+ *
+ * @param wStr the WHERE string
+ */
+ protected abstract Where buildBusWhere(String wStr) throws IllegalArgumentException;
+
+ @Override
+ public void dispose() {
+ OpenWebNetBridgeHandler bh = bridgeHandler;
+ String oid = ownId;
+ if (bh != null && oid != null) {
+ bh.unregisterDevice(oid);
+ }
+ ScheduledFuture<?> sc = refreshTimeout;
+ if (sc != null) {
+ sc.cancel(true);
+ }
+ super.dispose();
+ }
+
+ /**
+ * Helper method to return a Quantity from a Number value or UnDefType.NULL if value is null
+ *
+ * @param value to be used
+ * @param unit to be used
+ * @return Quantity
+ */
+ protected <U extends Quantity<U>> State getAsQuantityTypeOrNull(@Nullable Number value, Unit<U> unit) {
+ return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
+ }
+
+ /**
+ * Returns a prefix String for ownId specific for each handler. To be implemented by sub-classes.
+ *
+ * @return
+ */
+ protected abstract String ownIdPrefix();
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
+++ /dev/null
-/**
- * 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));
- }
- }
-}
--- /dev/null
+/**
+ * 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));
+ }
+ }
+}