diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/SwitchbotBindingConstants.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/SwitchbotBindingConstants.java index f5eb305ba814..1103808c4f9d 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/SwitchbotBindingConstants.java +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/SwitchbotBindingConstants.java @@ -29,13 +29,19 @@ public class SwitchbotBindingConstants { // List of all Thing Type UIDs public static final ThingTypeUID BRIDGE_TYPE_SWITCHBOT_ACCOUNT = new ThingTypeUID(BINDING_ID, "switchbotAccount"); public static final ThingTypeUID THING_TYPE_CURTAIN = new ThingTypeUID(BINDING_ID, "curtain"); - public static final ThingTypeUID THING_TYPE_HUB_MINI = new ThingTypeUID(BINDING_ID, "hubmini"); + public static final ThingTypeUID THING_TYPE_HUB = new ThingTypeUID(BINDING_ID, "hub"); + public static final ThingTypeUID THING_TYPE_BOT = new ThingTypeUID(BINDING_ID, "bot"); + public static final ThingTypeUID THING_TYPE_PLUG = new ThingTypeUID(BINDING_ID, "plug"); + public static final ThingTypeUID THING_TYPE_METER = new ThingTypeUID(BINDING_ID, "meter"); + public static final ThingTypeUID THING_TYPE_HUMIDIFIER = new ThingTypeUID(BINDING_ID, "humidifier"); + public static final ThingTypeUID THING_TYPE_SMARTFAN = new ThingTypeUID(BINDING_ID, "smartfan"); public static final String COMMAND = "command"; public static final String COMMAND_TURN_ON = "turnOn"; public static final String COMMAND_TURN_OFF = "turnOff"; public static final String COMMAND_OPEN = "open"; public static final String COMMAND_CLOSE = "close"; + public static final String COMMAND_PRESS = "press"; public static final String CONFIG_DEVICE_ID = "deviceId"; public static final String CONFIG_GROUP = "group"; @@ -45,4 +51,7 @@ public class SwitchbotBindingConstants { public static final String CHANNEL_MOVING = "moving"; public static final String CHANNEL_GROUP = "group"; public static final String CHANNEL_SLIDE_POSITION = "slide-position"; + + // bot channels + public static final String CHANNEL_POWER = "power"; } diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/SwitchbotHandlerFactory.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/SwitchbotHandlerFactory.java index 19d924a2517c..9ff7edb46736 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/SwitchbotHandlerFactory.java +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/SwitchbotHandlerFactory.java @@ -25,8 +25,13 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.switchbot.internal.discovery.SwitchbotAccountDiscoveryService; +import org.openhab.binding.switchbot.internal.handler.BotHandler; import org.openhab.binding.switchbot.internal.handler.CurtainHandler; -import org.openhab.binding.switchbot.internal.handler.HubMiniHandler; +import org.openhab.binding.switchbot.internal.handler.HubHandler; +import org.openhab.binding.switchbot.internal.handler.HumidifierHandler; +import org.openhab.binding.switchbot.internal.handler.MeterHandler; +import org.openhab.binding.switchbot.internal.handler.PlugHandler; +import org.openhab.binding.switchbot.internal.handler.SmartfanHandler; import org.openhab.binding.switchbot.internal.handler.SwitchbotAccountHandler; import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.thing.Bridge; @@ -49,11 +54,15 @@ @Component(configurationPid = "binding.switchbot", service = ThingHandlerFactory.class) public class SwitchbotHandlerFactory extends BaseThingHandlerFactory { - public static final Set SUPPORTED_THING_TYPE_UIDS = Collections.unmodifiableSet(Stream - .of(BRIDGE_TYPE_SWITCHBOT_ACCOUNT, THING_TYPE_CURTAIN, THING_TYPE_HUB_MINI).collect(Collectors.toSet())); + public static final Set SUPPORTED_THING_TYPE_UIDS = Collections + .unmodifiableSet(Stream + .of(BRIDGE_TYPE_SWITCHBOT_ACCOUNT, THING_TYPE_CURTAIN, THING_TYPE_HUB, THING_TYPE_BOT, + THING_TYPE_PLUG, THING_TYPE_METER, THING_TYPE_HUMIDIFIER, THING_TYPE_SMARTFAN) + .collect(Collectors.toSet())); public static final Set DISCOVERABLE_THING_TYPE_UIDS = Collections - .unmodifiableSet(Stream.of(THING_TYPE_CURTAIN, THING_TYPE_HUB_MINI).collect(Collectors.toSet())); + .unmodifiableSet(Stream.of(THING_TYPE_CURTAIN, THING_TYPE_HUB, THING_TYPE_BOT, THING_TYPE_PLUG, + THING_TYPE_METER, THING_TYPE_HUMIDIFIER, THING_TYPE_SMARTFAN).collect(Collectors.toSet())); private Map> discoveryServiceRegistrations = new HashMap<>(); @@ -69,8 +78,18 @@ protected ThingHandler createHandler(Thing thing) { if (thingTypeUID.equals(THING_TYPE_CURTAIN)) { return new CurtainHandler(thing); - } else if (thingTypeUID.equals(THING_TYPE_HUB_MINI)) { - return new HubMiniHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_HUB)) { + return new HubHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_BOT)) { + return new BotHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_PLUG)) { + return new PlugHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_METER)) { + return new MeterHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_HUMIDIFIER)) { + return new HumidifierHandler(thing); + } else if (thingTypeUID.equals(THING_TYPE_SMARTFAN)) { + return new SmartfanHandler(thing); } else if (thingTypeUID.equals(BRIDGE_TYPE_SWITCHBOT_ACCOUNT)) { SwitchbotAccountHandler handler = new SwitchbotAccountHandler((Bridge) thing); registerAccountDiscoveryService(handler); diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/BotConfig.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/BotConfig.java new file mode 100644 index 000000000000..776e3c65e7d6 --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/BotConfig.java @@ -0,0 +1,27 @@ +package org.openhab.binding.switchbot.internal.config; + +public class BotConfig { + private int refreshInterval; + private String deviceId; + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public int getRefreshInterval() { + return refreshInterval; + } + + public void setRefreshInterval(int refreshInterval) { + this.refreshInterval = refreshInterval; + } + + @Override + public String toString() { + return "BotConfig [refreshInterval=" + refreshInterval + ", deviceId=" + deviceId + "]"; + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/HubMiniConfig.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/HubConfig.java similarity index 76% rename from bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/HubMiniConfig.java rename to bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/HubConfig.java index 74648b60f633..23eed3bee6e4 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/HubMiniConfig.java +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/HubConfig.java @@ -1,6 +1,6 @@ package org.openhab.binding.switchbot.internal.config; -public class HubMiniConfig { +public class HubConfig { private String deviceId; public String getDeviceId() { @@ -13,6 +13,6 @@ public void setDeviceId(String deviceId) { @Override public String toString() { - return "HubMiniConfig [deviceId=" + deviceId + "]"; + return "HubConfig [deviceId=" + deviceId + "]"; } } diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/HumidifierConfig.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/HumidifierConfig.java new file mode 100644 index 000000000000..9e63223c5c7a --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/HumidifierConfig.java @@ -0,0 +1,27 @@ +package org.openhab.binding.switchbot.internal.config; + +public class HumidifierConfig { + private String deviceId; + private int refreshInterval; + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public int getRefreshInterval() { + return refreshInterval; + } + + public void setRefreshInterval(int refreshInterval) { + this.refreshInterval = refreshInterval; + } + + @Override + public String toString() { + return "HumidifierConfig [deviceId=" + deviceId + ", refreshInterval=" + refreshInterval + "]"; + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/MeterConfig.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/MeterConfig.java new file mode 100644 index 000000000000..8cf024d4f140 --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/MeterConfig.java @@ -0,0 +1,27 @@ +package org.openhab.binding.switchbot.internal.config; + +public class MeterConfig { + private String deviceId; + private int refreshInterval; + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public int getRefreshInterval() { + return refreshInterval; + } + + public void setRefreshInterval(int refreshInterval) { + this.refreshInterval = refreshInterval; + } + + @Override + public String toString() { + return "MeterConfig [deviceId=" + deviceId + ", refreshInterval=" + refreshInterval + "]"; + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/PlugConfig.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/PlugConfig.java new file mode 100644 index 000000000000..4a0b2a473983 --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/PlugConfig.java @@ -0,0 +1,27 @@ +package org.openhab.binding.switchbot.internal.config; + +public class PlugConfig { + private String deviceId; + private int refreshInterval; + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public int getRefreshInterval() { + return refreshInterval; + } + + public void setRefreshInterval(int refreshInterval) { + this.refreshInterval = refreshInterval; + } + + @Override + public String toString() { + return "MeterConfig [deviceId=" + deviceId + ", refreshInterval=" + refreshInterval + "]"; + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/SmartfanConfig.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/SmartfanConfig.java new file mode 100644 index 000000000000..b1652653a382 --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/config/SmartfanConfig.java @@ -0,0 +1,27 @@ +package org.openhab.binding.switchbot.internal.config; + +public class SmartfanConfig { + private String deviceId; + private int refreshInterval; + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public int getRefreshInterval() { + return refreshInterval; + } + + public void setRefreshInterval(int refreshInterval) { + this.refreshInterval = refreshInterval; + } + + @Override + public String toString() { + return "MeterConfig [deviceId=" + deviceId + ", refreshInterval=" + refreshInterval + "]"; + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/BotDevice.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/BotDevice.java new file mode 100644 index 000000000000..b7b49db71333 --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/BotDevice.java @@ -0,0 +1,13 @@ +package org.openhab.binding.switchbot.internal.discovery; + +/** + * Represents a discovered Bot device. + * + * @author Arjan Lamers - Initial contribution + */ +public class BotDevice extends SwitchbotDevice { + + public BotDevice(String name, String deviceId) { + super(name, deviceId, DeviceType.BOT); + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/HubMiniDevice.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/HubDevice.java similarity index 51% rename from bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/HubMiniDevice.java rename to bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/HubDevice.java index f05ea88d904b..db636f02dbad 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/HubMiniDevice.java +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/HubDevice.java @@ -5,9 +5,9 @@ * * @author Arjan Lamers - Initial contribution */ -public class HubMiniDevice extends SwitchbotDevice { +public class HubDevice extends SwitchbotDevice { - public HubMiniDevice(String name, String deviceId) { - super(name, deviceId, DeviceType.HUB_MINI); + public HubDevice(String name, String deviceId) { + super(name, deviceId, DeviceType.HUB); } } diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/SwitchbotDevice.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/SwitchbotDevice.java index 9ab0996e41d7..f4541dc4abee 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/SwitchbotDevice.java +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/SwitchbotDevice.java @@ -13,7 +13,12 @@ public class SwitchbotDevice { public enum DeviceType { CURTAIN(THING_TYPE_CURTAIN), - HUB_MINI(THING_TYPE_HUB_MINI); + BOT(THING_TYPE_BOT), + PLUG(THING_TYPE_PLUG), + METER(THING_TYPE_METER), + HUMIDIFIER(THING_TYPE_HUMIDIFIER), + SMARTFAN(THING_TYPE_SMARTFAN), + HUB(THING_TYPE_HUB); private ThingTypeUID thingType; diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/BotHandler.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/BotHandler.java new file mode 100644 index 000000000000..962301301123 --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/BotHandler.java @@ -0,0 +1,81 @@ +/** + * 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.switchbot.internal.handler; + +import static org.openhab.binding.switchbot.internal.SwitchbotBindingConstants.CHANNEL_POWER; + +import org.openhab.binding.switchbot.internal.config.BotConfig; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link BotHandler} is responsible for handling commands, which are + * sent to one of the channels. It maps the OpenHAB world to the Switchbot world. + * + * @author Arjan Lamers - Initial contribution + */ +public class BotHandler extends SwitchbotHandler { + + private Logger logger = LoggerFactory.getLogger(BotHandler.class); + + public BotHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + logger.debug("Will boot up Switchbot Bot binding"); + + BotConfig config = getThing().getConfiguration().as(BotConfig.class); + + logger.debug("Bot Config: {}", config); + + refreshTime = config.getRefreshInterval(); + if (refreshTime < 30) { + logger.warn( + "Refresh time [{}] is not valid. Refresh time must be at least 30 seconds. Setting to minimum of 30 sec", + refreshTime); + config.setRefreshInterval(30); + } + + apiProxy = new SwitchbotApiProxy(config.getDeviceId(), authorizationOpenToken); + startAutomaticRefresh(); + } + + @Override + protected void updateState(SwitchbotApiStatusModel state) { + if (state != null) { + updateStatus(ThingStatus.ONLINE); + publishChannels(state); + } else { + logger.warn("Bot {} not cloud-enabled, check app settings", apiProxy.getDeviceId()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Bot not cloud-enabled, check app settings"); + } + } + + private void publishChannels(SwitchbotApiStatusModel state) { + if (state == null) { + updateState(CHANNEL_POWER, OnOffType.OFF); + return; + } + + boolean power = state.getBody().getPower() == null ? false : state.getBody().getPower().equalsIgnoreCase("on"); + updateState(CHANNEL_POWER, power ? OnOffType.ON : OnOffType.OFF); + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CommandModel.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CommandModel.java index 430955be78c5..da15e646266f 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CommandModel.java +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CommandModel.java @@ -8,6 +8,7 @@ public class CommandModel { public static CommandModel TURN_OFF = new CommandModel("turnOff", "default", "command"); public static CommandModel TURN_ON = new CommandModel("turnOn", "default", "command"); + public static CommandModel PRESS = new CommandModel("press", "default", "command"); public CommandModel(String command, String parameter, String commandType) { this.command = command; diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CurtainHandler.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CurtainHandler.java index d4ae3626f8d4..92ff3165b6c1 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CurtainHandler.java +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CurtainHandler.java @@ -14,22 +14,12 @@ import static org.openhab.binding.switchbot.internal.SwitchbotBindingConstants.*; -import java.io.IOException; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.switchbot.internal.SwitchbotBindingConstants; import org.openhab.binding.switchbot.internal.config.CurtainConfig; -import org.openhab.binding.switchbot.internal.handler.CurtainProxy.CurtainState; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; -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.types.Command; -import org.openhab.core.types.RefreshType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,42 +33,60 @@ public class CurtainHandler extends SwitchbotHandler { private Logger logger = LoggerFactory.getLogger(CurtainHandler.class); - private CurtainProxy curtainProxy; + /** internal representation of the curtain state. */ + public static class CurtainState { + boolean calibrate; + boolean group; + boolean moving; + int slidePosition; - private int refreshTime; - private ScheduledFuture refreshTask; + public boolean isCalibrate() { + return calibrate; + } - public CurtainHandler(Thing thing) { - super(thing); - } + public void setCalibrate(boolean calibrate) { + this.calibrate = calibrate; + } - @Override - public void handleCommand(@NonNull ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - refreshStateAndUpdate(); - } else if (channelUID.getId().equals(SwitchbotBindingConstants.COMMAND)) { - sendCommandToDevice(command); + public boolean isGroup() { + return group; } - } - private void sendCommandToDevice(Command command) { - logger.debug("Ok - will handle command '{}' for CHANNEL_COMMAND", command); + public void setGroup(boolean group) { + this.group = group; + } - try { - curtainProxy.sendCommand(command.toString()); - } catch (IOException e) { - logger.debug("Error while processing command from openHAB.", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + public boolean isMoving() { + return moving; } - this.refreshStateAndUpdate(); + + public void setMoving(boolean moving) { + this.moving = moving; + } + + public int getSlidePosition() { + return slidePosition; + } + + public void setSlidePosition(int slidePosition) { + this.slidePosition = slidePosition; + } + } + + public CurtainHandler(Thing thing) { + super(thing); } @Override - public void dispose() { - logger.debug("Running dispose()"); - if (this.refreshTask != null) { - this.refreshTask.cancel(true); - this.refreshTask = null; + protected void updateState(SwitchbotApiStatusModel status) { + CurtainState curtainState = toCurtainState(status); + if (curtainState != null) { + updateStatus(ThingStatus.ONLINE); + publishChannels(curtainState); + } else { + logger.warn("Curtain {} not cloud-enabled, check app settings", apiProxy.getDeviceId()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Curtain not cloud-enabled, check app settings"); } } @@ -99,37 +107,10 @@ public void initialize() { config.setRefreshInterval(30); } - curtainProxy = new CurtainProxy(config, authorizationOpenToken); + apiProxy = new SwitchbotApiProxy(getCommunicationDeviceId(config), authorizationOpenToken); startAutomaticRefresh(); } - public void refreshStateAndUpdate() { - if (curtainProxy != null) { - try { - CurtainState curtainState = curtainProxy.getDeviceStatus(); - if (curtainState != null) { - updateStatus(ThingStatus.ONLINE); - publishChannels(curtainState); - } else { - logger.warn("Curtain {} not cloud-enabled, check app settings", - curtainProxy.getConfig().getDeviceId()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "Curtain not cloud-enabled, check app settings"); - } - } catch (IOException e) { - logger.debug("Error when refreshing state, putting device offline.", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - } - } - - private void startAutomaticRefresh() { - Runnable refresher = () -> refreshStateAndUpdate(); - - this.refreshTask = scheduler.scheduleWithFixedDelay(refresher, 0, refreshTime, TimeUnit.SECONDS); - logger.debug("Start automatic refresh at {} seconds", refreshTime); - } - private void publishChannels(CurtainState curtainState) { if (curtainState == null) { updateState(CHANNEL_CALIBRATE, OnOffType.OFF); @@ -144,4 +125,36 @@ private void publishChannels(CurtainState curtainState) { updateState(CHANNEL_MOVING, curtainState.isMoving() ? OnOffType.ON : OnOffType.OFF); updateState(CHANNEL_SLIDE_POSITION, new DecimalType(curtainState.getSlidePosition())); } + + private CurtainState toCurtainState(SwitchbotApiStatusModel status) { + + if (status.getBody().getCalibrate() == null) { + // probably not cloud enabled so no real curtain state available + return null; + } + + CurtainState curtainState = new CurtainState(); + curtainState.setCalibrate(status.getBody().getCalibrate()); + curtainState.setGroup(status.getBody().getGroup()); + curtainState.setMoving(status.getBody().getMoving()); + curtainState.setSlidePosition(status.getBody().getSlidePosition()); + + return curtainState; + } + + /** + * Device id of a group is a concatenation of device ids. For communication, we need the master deviceId (the + * first). + * + * @param config + * + * @return the master device id (if a group) or the device id if not in a group + */ + private String getCommunicationDeviceId(CurtainConfig config) { + if (config.isGroup()) { + return config.getDeviceId().split("-")[0]; + } else { + return config.getDeviceId(); + } + } } diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CurtainProxy.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CurtainProxy.java deleted file mode 100644 index a7204951a55c..000000000000 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CurtainProxy.java +++ /dev/null @@ -1,156 +0,0 @@ -package org.openhab.binding.switchbot.internal.handler; - -import static org.openhab.binding.switchbot.internal.SwitchbotBindingConstants.*; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Properties; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.switchbot.internal.config.CurtainConfig; -import org.openhab.binding.switchbot.internal.discovery.CurtainStatusModel; -import org.openhab.core.io.net.http.HttpUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; - -/** - * Forms a proxy for a curtain device, a digital twin. - */ -public class CurtainProxy { - private Logger logger = LoggerFactory.getLogger(CurtainProxy.class); - - private CurtainConfig config; - - private String authorizationOpenToken; - - public CurtainConfig getConfig() { - return config; - } - - /** - * Device id of a group is a concatenation of device ids. For communication, we need the master deviceId (the - * first). - * - * @return the master device id (if a group) or the device id if not in a group - */ - private String getCommunicationDeviceId() { - if (config.isGroup()) { - return config.getDeviceId().split("-")[0]; - } else { - return config.getDeviceId(); - } - } - - /** internal representation of the curtain state. */ - public static class CurtainState { - boolean calibrate; - boolean group; - boolean moving; - int slidePosition; - - public boolean isCalibrate() { - return calibrate; - } - - public void setCalibrate(boolean calibrate) { - this.calibrate = calibrate; - } - - public boolean isGroup() { - return group; - } - - public void setGroup(boolean group) { - this.group = group; - } - - public boolean isMoving() { - return moving; - } - - public void setMoving(boolean moving) { - this.moving = moving; - } - - public int getSlidePosition() { - return slidePosition; - } - - public void setSlidePosition(int slidePosition) { - this.slidePosition = slidePosition; - } - } - - public CurtainProxy(CurtainConfig config, String authorizationOpenToken) { - this.config = config; - this.authorizationOpenToken = authorizationOpenToken; - } - - public void sendCommand(@NonNull String command) throws IOException { - Properties headers = new Properties(); - headers.setProperty("Authorization", authorizationOpenToken); - - CommandModel commandModel; - switch (command) { - case COMMAND_TURN_OFF: - case COMMAND_CLOSE: - commandModel = CommandModel.TURN_OFF; - break; - case COMMAND_TURN_ON: - case COMMAND_OPEN: - commandModel = CommandModel.TURN_ON; - break; - default: - throw new IllegalArgumentException("Unknown command: " + command); - } - Gson gson = new Gson(); - String commandJson = gson.toJson(commandModel); - InputStream stream = new ByteArrayInputStream(commandJson.getBytes(StandardCharsets.UTF_8)); - - String resultString = HttpUtil.executeUrl("POST", - "https://api.switch-bot.com/v1.0/devices/" + getCommunicationDeviceId() + "/commands", headers, stream, - "application/json", 20000); - - logger.debug("Result from WS call to get /v1.0/devices/{}/command: {}", getCommunicationDeviceId(), - resultString); - - return; - } - - public CurtainState getDeviceStatus() throws IOException { - Properties headers = new Properties(); - headers.setProperty("Authorization", authorizationOpenToken); - - String resultString = HttpUtil.executeUrl("GET", - "https://api.switch-bot.com/v1.0/devices/" + getCommunicationDeviceId() + "/status", headers, null, - "application/json", 20000); - - Gson gson = new Gson(); - CurtainStatusModel status = gson.fromJson(resultString, CurtainStatusModel.class); - - logger.debug("Result from WS call to get /v1.0/devices/{}/status: {}", getCommunicationDeviceId(), - resultString); - - return toCurtainState(status); - } - - private CurtainState toCurtainState(CurtainStatusModel status) { - - if (status.getBody().getCalibrate() == null) { - // probably not cloud enabled so no real curtain state available - return null; - } - - CurtainState curtainState = new CurtainState(); - curtainState.setCalibrate(status.getBody().getCalibrate()); - curtainState.setGroup(status.getBody().getGroup()); - curtainState.setMoving(status.getBody().getMoving()); - curtainState.setSlidePosition(status.getBody().getSlidePosition()); - - return curtainState; - } -} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/CurtainStatusModel.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CurtainStatusModel.java similarity index 97% rename from bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/CurtainStatusModel.java rename to bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CurtainStatusModel.java index f61b21dca790..f30447a2a2a8 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/discovery/CurtainStatusModel.java +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/CurtainStatusModel.java @@ -1,4 +1,4 @@ -package org.openhab.binding.switchbot.internal.discovery; +package org.openhab.binding.switchbot.internal.handler; /** * Represents the json model of the status call. diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HubHandler.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HubHandler.java new file mode 100644 index 000000000000..e985d06ec528 --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HubHandler.java @@ -0,0 +1,50 @@ +/** + * 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.switchbot.internal.handler; + +import org.openhab.binding.switchbot.internal.config.HubConfig; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link HubHandler} is responsible for handling commands for the Hub Mini / Hub Plus / Hub, which are + * sent to one of the channels. It maps the OpenHAB world to the Switchbot world. + * + * @author Arjan Lamers - Initial contribution + */ +public class HubHandler extends SwitchbotHandler { + + private Logger logger = LoggerFactory.getLogger(HubHandler.class); + + public HubHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + logger.debug("Will boot up Switchbot Hub binding"); + + HubConfig config = getThing().getConfiguration().as(HubConfig.class); + + logger.debug("Hub Config: {}", config); + + apiProxy = new SwitchbotApiProxy(config.getDeviceId(), authorizationOpenToken); + } + + @Override + protected void updateState(SwitchbotApiStatusModel status) { + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HubMiniHandler.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HubMiniHandler.java deleted file mode 100644 index ba5a3a5697d4..000000000000 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HubMiniHandler.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.switchbot.internal.handler; - -import java.io.IOException; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.switchbot.internal.SwitchbotBindingConstants; -import org.openhab.binding.switchbot.internal.config.HubMiniConfig; -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.types.Command; -import org.openhab.core.types.RefreshType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link HubMiniHandler} is responsible for handling commands for the Hub Mini, which are - * sent to one of the channels. It maps the OpenHAB world to the Switchbot world. - * - * @author Arjan Lamers - Initial contribution - */ -public class HubMiniHandler extends SwitchbotHandler { - - private Logger logger = LoggerFactory.getLogger(HubMiniHandler.class); - - private HubMiniProxy hubMiniProxy; - - public HubMiniHandler(Thing thing) { - super(thing); - } - - @Override - public void handleCommand(@NonNull ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - // the hub mini has no state to refresh - } else if (channelUID.getId().equals(SwitchbotBindingConstants.COMMAND)) { - sendCommandToDevice(command); - } - } - - private void sendCommandToDevice(Command command) { - logger.debug("Ok - will handle command for CHANNEL_COMMAND"); - - try { - hubMiniProxy.sendCommand(command.toString()); - } catch (IOException e) { - logger.debug("Error while processing command from openHAB.", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - } - - @Override - public void initialize() { - updateStatus(ThingStatus.UNKNOWN); - logger.debug("Will boot up Switchbot Hub binding"); - - HubMiniConfig config = getThing().getConfiguration().as(HubMiniConfig.class); - - logger.debug("Hub Mini Config: {}", config); - - hubMiniProxy = new HubMiniProxy(config, authorizationOpenToken); - } -} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HubMiniProxy.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HubMiniProxy.java deleted file mode 100644 index 4efe8b86355a..000000000000 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HubMiniProxy.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.openhab.binding.switchbot.internal.handler; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Properties; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.switchbot.internal.config.HubMiniConfig; -import org.openhab.core.io.net.http.HttpUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; - -/** - * Forms a proxy for a curtain device, a digital twin. - */ -public class HubMiniProxy { - private Logger logger = LoggerFactory.getLogger(HubMiniProxy.class); - - private HubMiniConfig config; - - private String authorizationOpenToken; - - public HubMiniConfig getConfig() { - return config; - } - - public HubMiniProxy(HubMiniConfig config, String authorizationOpenToken) { - this.config = config; - this.authorizationOpenToken = authorizationOpenToken; - } - - public void sendCommand(@NonNull String command) throws IOException { - Properties headers = new Properties(); - headers.setProperty("Authorization", authorizationOpenToken); - - CommandModel commandModel; - switch (command) { - case "turnOff": - commandModel = CommandModel.TURN_OFF; - break; - case "turnOn": - commandModel = CommandModel.TURN_ON; - break; - default: - throw new IllegalArgumentException("Unknown command: " + command); - } - Gson gson = new Gson(); - String commandJson = gson.toJson(commandModel); - InputStream stream = new ByteArrayInputStream(commandJson.getBytes(StandardCharsets.UTF_8)); - - String resultString = HttpUtil.executeUrl("POST", - "https://api.switch-bot.com/v1.0/devices/" + config.getDeviceId() + "/commands", headers, stream, - "application/json", 20000); - - logger.debug("Result from WS call to get /v1.0/devices/{}/command: {}", config.getDeviceId(), resultString); - - return; - } -} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HumidifierHandler.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HumidifierHandler.java new file mode 100644 index 000000000000..d279c5f6a6b9 --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/HumidifierHandler.java @@ -0,0 +1,59 @@ +/** + * 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.switchbot.internal.handler; + +import org.openhab.binding.switchbot.internal.config.HumidifierConfig; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link HumidifierHandler} is responsible for handling commands, which are + * sent to one of the channels. It maps the OpenHAB world to the Switchbot world. + * + * @author Arjan Lamers - Initial contribution + */ +public class HumidifierHandler extends SwitchbotHandler { + + private Logger logger = LoggerFactory.getLogger(HumidifierHandler.class); + + public HumidifierHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + logger.debug("Will boot up Switchbot Humidifier binding"); + + HumidifierConfig config = getThing().getConfiguration().as(HumidifierConfig.class); + + logger.debug("Humidifier Config: {}", config); + + refreshTime = config.getRefreshInterval(); + if (refreshTime < 30) { + logger.warn( + "Refresh time [{}] is not valid. Refresh time must be at least 30 seconds. Setting to minimum of 30 sec", + refreshTime); + config.setRefreshInterval(30); + } + + apiProxy = new SwitchbotApiProxy(config.getDeviceId(), authorizationOpenToken); + startAutomaticRefresh(); + } + + @Override + protected void updateState(SwitchbotApiStatusModel status) { + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/MeterHandler.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/MeterHandler.java new file mode 100644 index 000000000000..ea9790c9701d --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/MeterHandler.java @@ -0,0 +1,60 @@ +/** + * 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.switchbot.internal.handler; + +import org.openhab.binding.switchbot.internal.config.MeterConfig; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link MeterHandler} is responsible for handling commands, which are + * sent to one of the channels. It maps the OpenHAB world to the Switchbot world. + * + * @author Arjan Lamers - Initial contribution + */ +public class MeterHandler extends SwitchbotHandler { + + private Logger logger = LoggerFactory.getLogger(MeterHandler.class); + + public MeterHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + logger.debug("Will boot up Switchbot Curtain binding"); + + MeterConfig config = getThing().getConfiguration().as(MeterConfig.class); + + logger.debug("Curtain Config: {}", config); + + refreshTime = config.getRefreshInterval(); + if (refreshTime < 30) { + logger.warn( + "Refresh time [{}] is not valid. Refresh time must be at least 30 seconds. Setting to minimum of 30 sec", + refreshTime); + config.setRefreshInterval(30); + } + + apiProxy = new SwitchbotApiProxy(config.getDeviceId(), authorizationOpenToken); + startAutomaticRefresh(); + } + + @Override + protected void updateState(SwitchbotApiStatusModel status) { + // TODO Auto-generated method stub + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/PlugHandler.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/PlugHandler.java new file mode 100644 index 000000000000..4262bb9c9ddb --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/PlugHandler.java @@ -0,0 +1,59 @@ +/** + * 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.switchbot.internal.handler; + +import org.openhab.binding.switchbot.internal.config.PlugConfig; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PlugHandler} is responsible for handling commands, which are + * sent to one of the channels. It maps the OpenHAB world to the Switchbot world. + * + * @author Arjan Lamers - Initial contribution + */ +public class PlugHandler extends SwitchbotHandler { + + private Logger logger = LoggerFactory.getLogger(PlugHandler.class); + + public PlugHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + logger.debug("Will boot up Switchbot Curtain binding"); + + PlugConfig config = getThing().getConfiguration().as(PlugConfig.class); + + logger.debug("Curtain Config: {}", config); + + refreshTime = config.getRefreshInterval(); + if (refreshTime < 30) { + logger.warn( + "Refresh time [{}] is not valid. Refresh time must be at least 30 seconds. Setting to minimum of 30 sec", + refreshTime); + config.setRefreshInterval(30); + } + + apiProxy = new SwitchbotApiProxy(config.getDeviceId(), authorizationOpenToken); + startAutomaticRefresh(); + } + + @Override + protected void updateState(SwitchbotApiStatusModel status) { + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SmartfanHandler.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SmartfanHandler.java new file mode 100644 index 000000000000..b2f1c75e380f --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SmartfanHandler.java @@ -0,0 +1,59 @@ +/** + * 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.switchbot.internal.handler; + +import org.openhab.binding.switchbot.internal.config.SmartfanConfig; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link SmartfanHandler} is responsible for handling commands, which are + * sent to one of the channels. It maps the OpenHAB world to the Switchbot world. + * + * @author Arjan Lamers - Initial contribution + */ +public class SmartfanHandler extends SwitchbotHandler { + + private Logger logger = LoggerFactory.getLogger(SmartfanHandler.class); + + public SmartfanHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + logger.debug("Will boot up Switchbot Smartfan binding"); + + SmartfanConfig config = getThing().getConfiguration().as(SmartfanConfig.class); + + logger.debug("Curtain Config: {}", config); + + refreshTime = config.getRefreshInterval(); + if (refreshTime < 30) { + logger.warn( + "Refresh time [{}] is not valid. Refresh time must be at least 30 seconds. Setting to minimum of 30 sec", + refreshTime); + config.setRefreshInterval(30); + } + + apiProxy = new SwitchbotApiProxy(config.getDeviceId(), authorizationOpenToken); + startAutomaticRefresh(); + } + + @Override + protected void updateState(SwitchbotApiStatusModel status) { + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotAccountHandler.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotAccountHandler.java index 7b34be0fa9ff..4dd61937369a 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotAccountHandler.java +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotAccountHandler.java @@ -21,7 +21,7 @@ import org.openhab.binding.switchbot.internal.config.SwitchbotAccountConfig; import org.openhab.binding.switchbot.internal.discovery.AllDevicesModel; import org.openhab.binding.switchbot.internal.discovery.CurtainDevice; -import org.openhab.binding.switchbot.internal.discovery.HubMiniDevice; +import org.openhab.binding.switchbot.internal.discovery.HubDevice; import org.openhab.binding.switchbot.internal.discovery.SwitchbotDevice; import org.openhab.core.io.net.http.HttpUtil; import org.openhab.core.thing.Bridge; @@ -89,6 +89,8 @@ private List toSwitchbotDevices(AllDevicesModel allDevices) { // curtains can be grouped. Create individual curtain devices as well as an additional device for the // group. + // TODO decide if we want the master / slave actually discovered since it looks like you can't control + // them individually if grouped case "Curtain": if (device.isGroup()) { if (device.isMaster()) { @@ -105,8 +107,18 @@ private List toSwitchbotDevices(AllDevicesModel allDevices) { devices.add(new CurtainDevice(device.getDeviceName(), device.getDeviceId(), false)); } break; + case "Hub Plus": case "Hub Mini": - devices.add(new HubMiniDevice(device.getDeviceName(), device.getDeviceId())); + devices.add(new HubDevice(device.getDeviceName(), device.getDeviceId())); + break; + case "Bot": + case "Plug": + case "Meter": + case "Humidifier": + case "Smart Fan": + logger.warn( + "Known but unsupported device type discovered, will not be added to inbox: {} with deviceId {}", + device.getDeviceType(), device.getDeviceId()); break; default: logger.warn("Unknown device type discovered, will not be added to inbox: {} with deviceId {}", diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotApiProxy.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotApiProxy.java new file mode 100644 index 000000000000..9ae21f966d46 --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotApiProxy.java @@ -0,0 +1,82 @@ +package org.openhab.binding.switchbot.internal.handler; + +import static org.openhab.binding.switchbot.internal.SwitchbotBindingConstants.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Properties; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.core.io.net.http.HttpUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +/** + * Forms a proxy for the Switchbot API, for any device. + * Is stateful per device since it holds a device id. + */ +public class SwitchbotApiProxy { + private Logger logger = LoggerFactory.getLogger(SwitchbotApiProxy.class); + + private final String authorizationOpenToken; + private final String deviceId; + + public SwitchbotApiProxy(String deviceId, String authorizationOpenToken) { + this.authorizationOpenToken = authorizationOpenToken; + this.deviceId = deviceId; + } + + public String getDeviceId() { + return deviceId; + } + + public void sendCommand(@NonNull String command) throws IOException { + Properties headers = new Properties(); + headers.setProperty("Authorization", authorizationOpenToken); + + CommandModel commandModel; + switch (command) { + case COMMAND_TURN_OFF: + case COMMAND_CLOSE: + commandModel = CommandModel.TURN_OFF; + break; + case COMMAND_TURN_ON: + case COMMAND_OPEN: + commandModel = CommandModel.TURN_ON; + break; + case COMMAND_PRESS: + commandModel = CommandModel.PRESS; + break; + default: + throw new IllegalArgumentException("Unknown command: " + command); + } + Gson gson = new Gson(); + String commandJson = gson.toJson(commandModel); + InputStream stream = new ByteArrayInputStream(commandJson.getBytes(StandardCharsets.UTF_8)); + + String resultString = HttpUtil.executeUrl("POST", + "https://api.switch-bot.com/v1.0/devices/" + deviceId + "/commands", headers, stream, + "application/json", 20000); + + logger.debug("Result from WS call to get /v1.0/devices/{}/command: {}", deviceId, resultString); + + return; + } + + public SwitchbotApiStatusModel getDeviceStatus() throws IOException { + Properties headers = new Properties(); + headers.setProperty("Authorization", authorizationOpenToken); + + String resultString = HttpUtil.executeUrl("GET", + "https://api.switch-bot.com/v1.0/devices/" + deviceId + "/status", headers, null, "application/json", + 20000); + logger.debug("Result from WS call to get /v1.0/devices/{}/status: {}", deviceId, resultString); + + Gson gson = new Gson(); + return gson.fromJson(resultString, SwitchbotApiStatusModel.class); + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotApiStatusModel.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotApiStatusModel.java new file mode 100644 index 000000000000..8511f6f00b67 --- /dev/null +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotApiStatusModel.java @@ -0,0 +1,254 @@ +package org.openhab.binding.switchbot.internal.handler; + +/** + * Represents the json model of the combination of all possible status call responses. + * Documentation is verbatim from https://github.com/OpenWonderLabs/SwitchBotAPI#get-device-status + * + * @author Arjan Lamers - Initial contribution + */ +public class SwitchbotApiStatusModel { + + /* + * Example json body from https://github.com/OpenWonderLabs/SwitchBotAPI + * { + * "statusCode": 100, + * "body": { + * "deviceId": "E2F6032048AB", + * "deviceType": "Curtain", + * "hubDeviceId": "FA7310762361", + * "calibrate": true, + * "group": false, + * "moving": false, + * "slidePosition": 0 + * }, + * "message": "success" + * } + */ + + private int statusCode; + private String message; + private Body body; + + public static class Body { + /** device ID */ + private String deviceId; + /** device type */ + private String deviceType; + /** device's parent Hub ID */ + private String hubDeviceId; + /** only available for Bot/Plug/Humidifier devices. ON/OFF state */ + private String power; + /** only available for Meter/Humidifier devices. humidity percentage */ + private Integer humidity; + /** only available for Meter/Humidifier devices. temperature in celsius */ + private Float temperature; + /** only available for Humidifier devices. atomization efficiency */ + private Integer nebulizationEfficiency; + /** only available for Humidifier devices. determines if a Humidifier is in Auto Mode or not */ + private Boolean auto; + /** only available for Humidifier devices. determines if a Humidifier's safety lock is on or not */ + private Boolean childLock; + /** only available for Humidifier devices. determines if a Humidifier is muted or not */ + private Boolean sound; + /** only available for Curtain devices. determines if a Curtain has been calibrated or not */ + private Boolean calibrate; + /** + * only available for Curtain devices. determines if a Curtain is paired with or grouped with another Curtain or + * not + */ + private Boolean group; + /** only available for Curtain devices. determines if a Curtain is moving or not */ + private Boolean moving; + /** + * only available for Curtain devices. the percentage of the distance between the calibrated open position and + * close position that a Curtain has moved to + */ + private Integer slidePosition; + /** only available for Smart Fan devices. the fan mode */ + private Integer mode; + /** only available for Smart Fan devices. the fan speed */ + private Integer speed; + /** only available for Smart Fan devices. determines if the fan is swinging or not */ + private Boolean shaking; + /** only available for Smart Fan devices. the fan's swing direciton */ + private Integer shakeCenter; + /** only available for Smart Fan devices. the fan's swing range, 0~120° */ + private Integer shakeRange; + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceType() { + return deviceType; + } + + public void setDeviceType(String deviceType) { + this.deviceType = deviceType; + } + + public String getHubDeviceId() { + return hubDeviceId; + } + + public void setHubDeviceId(String hubDeviceId) { + this.hubDeviceId = hubDeviceId; + } + + public String getPower() { + return power; + } + + public void setPower(String power) { + this.power = power; + } + + public Integer getHumidity() { + return humidity; + } + + public void setHumidity(Integer humidity) { + this.humidity = humidity; + } + + public Float getTemperature() { + return temperature; + } + + public void setTemperature(Float temperature) { + this.temperature = temperature; + } + + public Integer getNebulizationEfficiency() { + return nebulizationEfficiency; + } + + public void setNebulizationEfficiency(Integer nebulizationEfficiency) { + this.nebulizationEfficiency = nebulizationEfficiency; + } + + public Boolean getAuto() { + return auto; + } + + public void setAuto(Boolean auto) { + this.auto = auto; + } + + public Boolean getChildLock() { + return childLock; + } + + public void setChildLock(Boolean childLock) { + this.childLock = childLock; + } + + public Boolean getSound() { + return sound; + } + + public void setSound(Boolean sound) { + this.sound = sound; + } + + public Boolean getCalibrate() { + return calibrate; + } + + public void setCalibrate(Boolean calibrate) { + this.calibrate = calibrate; + } + + public Boolean getGroup() { + return group; + } + + public void setGroup(Boolean group) { + this.group = group; + } + + public Boolean getMoving() { + return moving; + } + + public void setMoving(Boolean moving) { + this.moving = moving; + } + + public Integer getSlidePosition() { + return slidePosition; + } + + public void setSlidePosition(Integer slidePosition) { + this.slidePosition = slidePosition; + } + + public Integer getMode() { + return mode; + } + + public void setMode(Integer mode) { + this.mode = mode; + } + + public Integer getSpeed() { + return speed; + } + + public void setSpeed(Integer speed) { + this.speed = speed; + } + + public Boolean getShaking() { + return shaking; + } + + public void setShaking(Boolean shaking) { + this.shaking = shaking; + } + + public Integer getShakeCenter() { + return shakeCenter; + } + + public void setShakeCenter(Integer shakeCenter) { + this.shakeCenter = shakeCenter; + } + + public Integer getShakeRange() { + return shakeRange; + } + + public void setShakeRange(Integer shakeRange) { + this.shakeRange = shakeRange; + } + } + + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Body getBody() { + return body; + } + + public void setBody(Body body) { + this.body = body; + } +} diff --git a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotHandler.java b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotHandler.java index fc888b3cd72a..656998def335 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotHandler.java +++ b/bundles/org.openhab.binding.switchbot/src/main/java/org/openhab/binding/switchbot/internal/handler/SwitchbotHandler.java @@ -12,21 +12,42 @@ */ package org.openhab.binding.switchbot.internal.handler; +import java.io.IOException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.switchbot.internal.SwitchbotBindingConstants; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link SwitchbotHandler} is responsible for handling commands, which are * sent to one of the channels. It maps the OpenHAB world to the Switchbot world. * * Allows the account handler to place an authorization token for any derived class. + * Also provides basic refresh handling etc. + * * * @author Arjan Lamers - Initial contribution */ abstract class SwitchbotHandler extends BaseThingHandler { + private Logger logger = LoggerFactory.getLogger(SwitchbotHandler.class); protected String authorizationOpenToken; + protected SwitchbotApiProxy apiProxy; + + protected int refreshTime; + protected ScheduledFuture refreshTask; + public SwitchbotHandler(Thing thing) { super(thing); } @@ -34,4 +55,55 @@ public SwitchbotHandler(Thing thing) { public void setAuthorizationOpenToken(String authorizationOpenToken) { this.authorizationOpenToken = authorizationOpenToken; } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + refreshStateAndUpdate(); + } else if (channelUID.getId().equals(SwitchbotBindingConstants.COMMAND)) { + sendCommandToDevice(command); + } + } + + private void sendCommandToDevice(Command command) { + logger.debug("Ok - will handle command {} for CHANNEL_COMMAND", command); + + try { + apiProxy.sendCommand(command.toString()); + } catch (IOException e) { + logger.debug("Error while processing command from openHAB.", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + this.refreshStateAndUpdate(); + } + + @Override + public void dispose() { + logger.debug("Running dispose()"); + if (this.refreshTask != null) { + this.refreshTask.cancel(true); + this.refreshTask = null; + } + } + + protected abstract void updateState(SwitchbotApiStatusModel status); + + public void refreshStateAndUpdate() { + if (apiProxy != null) { + try { + SwitchbotApiStatusModel status = apiProxy.getDeviceStatus(); + updateState(status); + } catch (IOException e) { + logger.debug("Error when refreshing state, putting device offline.", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + } + + protected void startAutomaticRefresh() { + Runnable refresher = () -> refreshStateAndUpdate(); + + this.refreshTask = scheduler.scheduleWithFixedDelay(refresher, 0, refreshTime, TimeUnit.SECONDS); + logger.debug("Start automatic refresh at {} seconds", refreshTime); + } } diff --git a/bundles/org.openhab.binding.switchbot/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.switchbot/src/main/resources/OH-INF/thing/thing-types.xml index 55e6d52e8e2d..daeec9bca427 100644 --- a/bundles/org.openhab.binding.switchbot/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.switchbot/src/main/resources/OH-INF/thing/thing-types.xml @@ -32,7 +32,7 @@ - + @@ -52,13 +52,13 @@ - + - - Switchbot Hub Mini + + Switchbot Hub @@ -72,6 +72,140 @@ + + + + + + + Switchbot Bot + + + + + + + + + + The id of the device + + + + Interval the device is polled in sec (API allows 1000 calls per day per account). + 600 + + + + + + + + + + + Switchbot Plug + + + + + + + The id of the device + + + + Interval the device is polled in sec (API allows 1000 calls per day per account). + 600 + + + + + + + + + + + Switchbot Meter + + + + + + + The id of the device + + + + Interval the device is polled in sec (API allows 1000 calls per day per account). + 600 + + + + + + + + + + + Switchbot Humidifier + + + + + + + The id of the device + + + + Interval the device is polled in sec (API allows 1000 calls per day per account). + 600 + + + + + + + + + + + Switchbot Smart Fan + + + + + + + The id of the device + + + + Interval the device is polled in sec (API allows 1000 calls per day per account). + 600 + + + + Switch @@ -101,7 +235,14 @@ - + + Switch + + Indicates if this bot is on or off. + + + + String Send Commands to Switchbot Curtain. (turnOff is closed, turnOn is open) @@ -115,5 +256,18 @@ + + String + + Send Commands to Switchbot Bot. + + + + + + + + +