[orbitbhyve] Initial contribution (#10426)

* [orbitbhyve] initial contribution

Signed-off-by: Ondrej Pecta <opecta@gmail.com>

* [orbitbhyve] improvements based on code review

Signed-off-by: Ondrej Pecta <opecta@gmail.com>

* [orbitbhyve] next bunch of fixes related to code review

Signed-off-by: Ondrej Pecta <opecta@gmail.com>

* Update bundles/org.openhab.binding.orbitbhyve/src/main/java/org/openhab/binding/orbitbhyve/internal/OrbitBhyveHandlerFactory.java

Signed-off-by: Fabian Wolter <github@fabian-wolter.de>

Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
This commit is contained in:
Ondrej Pecta
2021-07-31 12:01:22 +02:00
committed by GitHub
parent 882374fbcc
commit 677804c485
25 changed files with 1882 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.orbitbhyve-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-orbitbhyve" description="Orbit B-hyve Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.orbitbhyve/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.orbitbhyve.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link OrbitBhyveBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveBindingConstants {
public static final String BINDING_ID = "orbitbhyve";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_SPRINKLER = new ThingTypeUID(BINDING_ID, "sprinkler");
// List of all Channel ids
public static final String CHANNEL_CONTROL = "control";
public static final String CHANNEL_MODE = "mode";
public static final String CHANNEL_SMART_WATERING = "smart_watering";
public static final String CHANNEL_NEXT_START = "next_start";
public static final String CHANNEL_RAIN_DELAY = "rain_delay";
public static final String CHANNEL_WATERING_TIME = "watering_time";
// Constants
public static final String AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36";
public static final String BHYVE_API = "https://api.orbitbhyve.com/v1/";
public static final String BHYVE_SESSION = BHYVE_API + "session";
public static final String BHYVE_DEVICES = BHYVE_API + "devices";
public static final String BHYVE_PROGRAMS = BHYVE_API + "sprinkler_timer_programs";
public static final String BHYVE_WS_URL = "wss://api.orbitbhyve.com/v1/events";
public static final int BHYVE_TIMEOUT = 5;
}

View File

@@ -0,0 +1,31 @@
/**
* 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.orbitbhyve.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link OrbitBhyveConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveConfiguration {
/**
* Sample configuration parameter. Replace with your own.
*/
public String email = "";
public String password = "";
public int refresh = 30;
}

View File

@@ -0,0 +1,85 @@
/**
* 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.orbitbhyve.internal;
import static org.openhab.binding.orbitbhyve.internal.OrbitBhyveBindingConstants.THING_TYPE_BRIDGE;
import static org.openhab.binding.orbitbhyve.internal.OrbitBhyveBindingConstants.THING_TYPE_SPRINKLER;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.binding.orbitbhyve.internal.handler.OrbitBhyveBridgeHandler;
import org.openhab.binding.orbitbhyve.internal.handler.OrbitBhyveSprinklerHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.io.net.http.WebSocketFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link OrbitBhyveHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.orbitbhyve", service = ThingHandlerFactory.class)
public class OrbitBhyveHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_SPRINKLER);
/**
* the shared http client
*/
private HttpClient httpClient;
/**
* the shared web socket client
*/
private WebSocketClient webSocketClient;
@Activate
public OrbitBhyveHandlerFactory(@Reference HttpClientFactory httpClientFactory,
@Reference WebSocketFactory webSocketFactory) {
this.httpClient = httpClientFactory.getCommonHttpClient();
this.webSocketClient = webSocketFactory.getCommonWebSocketClient();
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
return new OrbitBhyveBridgeHandler((Bridge) thing, httpClient, webSocketClient);
}
if (THING_TYPE_SPRINKLER.equals(thingTypeUID)) {
return new OrbitBhyveSprinklerHandler(thing);
}
return null;
}
}

View File

@@ -0,0 +1,150 @@
/**
* 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.orbitbhyve.internal.discovery;
import static org.openhab.binding.orbitbhyve.internal.OrbitBhyveBindingConstants.THING_TYPE_SPRINKLER;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.orbitbhyve.internal.handler.OrbitBhyveBridgeHandler;
import org.openhab.binding.orbitbhyve.internal.model.OrbitBhyveDevice;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OrbitBhyveDiscoveryService} discovers sprinklers
* associated with your Orbit B-Hyve cloud account.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(OrbitBhyveDiscoveryService.class);
private @Nullable OrbitBhyveBridgeHandler bridgeHandler;
private @Nullable ScheduledFuture<?> discoveryJob;
private static final int DISCOVERY_TIMEOUT_SEC = 10;
private static final int DISCOVERY_REFRESH_SEC = 1800;
public OrbitBhyveDiscoveryService() {
super(DISCOVERY_TIMEOUT_SEC);
logger.debug("Creating discovery service");
}
@Override
protected void startScan() {
runDiscovery();
}
@Override
public void activate() {
super.activate(null);
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
public void setThingHandler(@Nullable ThingHandler thingHandler) {
if (thingHandler instanceof OrbitBhyveBridgeHandler) {
bridgeHandler = (OrbitBhyveBridgeHandler) thingHandler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Starting Orbit B-Hyve background discovery");
ScheduledFuture<?> localDiscoveryJob = discoveryJob;
if (localDiscoveryJob == null || localDiscoveryJob.isCancelled()) {
discoveryJob = scheduler.scheduleWithFixedDelay(this::runDiscovery, 10, DISCOVERY_REFRESH_SEC,
TimeUnit.SECONDS);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stopping Orbit B-Hyve background discovery");
ScheduledFuture<?> localDiscoveryJob = discoveryJob;
if (localDiscoveryJob != null && !localDiscoveryJob.isCancelled()) {
localDiscoveryJob.cancel(true);
}
}
private synchronized void runDiscovery() {
OrbitBhyveBridgeHandler localBridgeHandler = bridgeHandler;
if (localBridgeHandler != null && ThingStatus.ONLINE == localBridgeHandler.getThing().getStatus()) {
List<OrbitBhyveDevice> devices = localBridgeHandler.getDevices();
logger.debug("Discovered total of {} devices", devices.size());
for (OrbitBhyveDevice device : devices) {
sprinklerDiscovered(device);
}
}
}
private void sprinklerDiscovered(OrbitBhyveDevice device) {
OrbitBhyveBridgeHandler localBridgeHandler = bridgeHandler;
if (localBridgeHandler != null) {
Map<String, Object> properties = new HashMap<>();
properties.put("id", device.getId());
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFwVersion());
properties.put(Thing.PROPERTY_HARDWARE_VERSION, device.getHwVersion());
properties.put(Thing.PROPERTY_MAC_ADDRESS, device.getMacAddress());
properties.put(Thing.PROPERTY_MODEL_ID, device.getType());
properties.put("Zones", device.getNumStations());
properties.put("Active zones", device.getZones().size());
ThingUID thingUID = new ThingUID(THING_TYPE_SPRINKLER, localBridgeHandler.getThing().getUID(),
device.getId());
logger.debug("Detected a/an {} - label: {} id: {}", THING_TYPE_SPRINKLER.getId(), device.getName(),
device.getId());
thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_SPRINKLER)
.withProperties(properties).withRepresentationProperty("id").withLabel(device.getName())
.withBridge(localBridgeHandler.getThing().getUID()).build());
}
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return Collections.singleton(THING_TYPE_SPRINKLER);
}
}

View File

@@ -0,0 +1,588 @@
/**
* 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.orbitbhyve.internal.handler;
import static org.openhab.binding.orbitbhyve.internal.OrbitBhyveBindingConstants.*;
import java.io.IOException;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.binding.orbitbhyve.internal.OrbitBhyveConfiguration;
import org.openhab.binding.orbitbhyve.internal.discovery.OrbitBhyveDiscoveryService;
import org.openhab.binding.orbitbhyve.internal.model.OrbitBhyveDevice;
import org.openhab.binding.orbitbhyve.internal.model.OrbitBhyveProgram;
import org.openhab.binding.orbitbhyve.internal.model.OrbitBhyveSessionResponse;
import org.openhab.binding.orbitbhyve.internal.model.OrbitBhyveSocketEvent;
import org.openhab.binding.orbitbhyve.internal.net.OrbitBhyveSocket;
import org.openhab.core.config.core.status.ConfigStatusMessage;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
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.ConfigStatusBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link OrbitBhyveBridgeHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveBridgeHandler extends ConfigStatusBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(OrbitBhyveBridgeHandler.class);
private final HttpClient httpClient;
private final WebSocketClient webSocketClient;
private @Nullable ScheduledFuture<?> future = null;
private @Nullable Session session;
private @Nullable String sessionToken = null;
private OrbitBhyveConfiguration config = new OrbitBhyveConfiguration();
private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
// Gson & parser
private final Gson gson = new Gson();
public OrbitBhyveBridgeHandler(Bridge thing, HttpClient httpClient, WebSocketClient webSocketClient) {
super(thing);
this.httpClient = httpClient;
this.webSocketClient = webSocketClient;
}
@Override
public Collection<ConfigStatusMessage> getConfigStatus() {
return Collections.emptyList();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(OrbitBhyveDiscoveryService.class);
}
@Override
public void initialize() {
config = getConfigAs(OrbitBhyveConfiguration.class);
httpClient.setFollowRedirects(false);
scheduler.execute(() -> {
login();
future = scheduler.scheduleWithFixedDelay(this::ping, 0, config.refresh, TimeUnit.SECONDS);
});
logger.debug("Finished initializing!");
}
@Override
public void dispose() {
ScheduledFuture<?> localFuture = future;
if (localFuture != null) {
localFuture.cancel(true);
}
closeSession();
super.dispose();
}
private boolean login() {
try {
String urlParameters = "{\"session\":{\"email\":\"" + config.email + "\",\"password\":\"" + config.password
+ "\"}}";
ContentResponse response = httpClient.newRequest(BHYVE_SESSION).method(HttpMethod.POST).agent(AGENT)
.content(new StringContentProvider(urlParameters), "application/json; charset=utf-8")
.timeout(BHYVE_TIMEOUT, TimeUnit.SECONDS).send();
if (response.getStatus() == 200) {
if (logger.isTraceEnabled()) {
logger.trace("response: {}", response.getContentAsString());
}
OrbitBhyveSessionResponse session = gson.fromJson(response.getContentAsString(),
OrbitBhyveSessionResponse.class);
sessionToken = session.getOrbitSessionToken();
logger.debug("token: {}", sessionToken);
initializeWebSocketSession();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Login response status:" + response.getStatus());
return false;
}
} catch (TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Exception during login");
return false;
} catch (InterruptedException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Exception during login");
Thread.currentThread().interrupt();
return false;
}
updateStatus(ThingStatus.ONLINE);
return true;
}
private synchronized void ping() {
if (ThingStatus.OFFLINE == thing.getStatus()) {
login();
}
if (ThingStatus.ONLINE == thing.getStatus()) {
Session localSession = session;
if (localSession == null || !localSession.isOpen()) {
initializeWebSocketSession();
}
localSession = session;
if (localSession != null && localSession.isOpen() && localSession.getRemote() != null) {
try {
logger.debug("Sending ping");
localSession.getRemote().sendString("{\"event\":\"ping\"}");
updateAllStatuses();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error sending ping to a web socket");
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Web socket creation error");
}
}
}
public List<OrbitBhyveDevice> getDevices() {
try {
ContentResponse response = sendRequestBuilder(BHYVE_DEVICES, HttpMethod.GET).send();
if (response.getStatus() == 200) {
if (logger.isTraceEnabled()) {
logger.trace("Devices response: {}", response.getContentAsString());
}
OrbitBhyveDevice[] devices = gson.fromJson(response.getContentAsString(), OrbitBhyveDevice[].class);
return Arrays.asList(devices);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Get devices returned response status: " + response.getStatus());
}
} catch (TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during getting devices");
} catch (InterruptedException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during getting devices");
Thread.currentThread().interrupt();
}
return new ArrayList<>();
}
Request sendRequestBuilder(String uri, HttpMethod method) {
return httpClient.newRequest(uri).method(method).agent(AGENT).header("Orbit-Session-Token", sessionToken)
.timeout(BHYVE_TIMEOUT, TimeUnit.SECONDS);
}
public @Nullable OrbitBhyveDevice getDevice(String deviceId) {
try {
ContentResponse response = sendRequestBuilder(BHYVE_DEVICES + "/" + deviceId, HttpMethod.GET).send();
if (response.getStatus() == 200) {
if (logger.isTraceEnabled()) {
logger.trace("Device response: {}", response.getContentAsString());
}
OrbitBhyveDevice device = gson.fromJson(response.getContentAsString(), OrbitBhyveDevice.class);
return device;
} else {
logger.debug("Returned status: {}", response.getStatus());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Returned status: " + response.getStatus());
}
} catch (TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error during getting device info: " + deviceId);
} catch (InterruptedException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error during getting device info: " + deviceId);
Thread.currentThread().interrupt();
}
return null;
}
public synchronized void processStatusResponse(String content) {
updateStatus(ThingStatus.ONLINE);
logger.trace("Got message: {}", content);
OrbitBhyveSocketEvent event = gson.fromJson(content, OrbitBhyveSocketEvent.class);
if (event != null) {
processEvent(event);
}
}
private void processEvent(OrbitBhyveSocketEvent event) {
switch (event.getEvent()) {
case "watering_in_progress_notification":
disableZones(event.getDeviceId());
Channel channel = getThingChannel(event.getDeviceId(), event.getStation());
if (channel != null) {
logger.debug("Watering zone: {}", event.getStation());
updateState(channel.getUID(), OnOffType.ON);
String program = event.getProgram().getAsString();
if (!program.isEmpty() && !"manual".equals(program)) {
channel = getThingChannel(event.getDeviceId(), "program_" + program);
if (channel != null) {
updateState(channel.getUID(), OnOffType.ON);
}
}
}
break;
case "watering_complete":
logger.debug("Watering complete");
disableZones(event.getDeviceId());
disablePrograms(event.getDeviceId());
updateDeviceStatus(event.getDeviceId());
break;
case "change_mode":
logger.debug("Updating mode to: {}", event.getMode());
Channel ch = getThingChannel(event.getDeviceId(), CHANNEL_MODE);
if (ch != null) {
updateState(ch.getUID(), new StringType(event.getMode()));
}
ch = getThingChannel(event.getDeviceId(), CHANNEL_CONTROL);
if (ch != null) {
updateState(ch.getUID(), "off".equals(event.getMode()) ? OnOffType.OFF : OnOffType.ON);
}
updateDeviceStatus(event.getDeviceId());
break;
case "rain_delay":
updateDeviceStatus(event.getDeviceId());
break;
case "skip_active_station":
disableZones(event.getDeviceId());
break;
case "program_changed":
OrbitBhyveProgram program = gson.fromJson(event.getProgram(), OrbitBhyveProgram.class);
if (program != null) {
updateDeviceProgramStatus(program);
updateDeviceStatus(program.getDeviceId());
}
break;
default:
logger.debug("Received event: {}", event.getEvent());
}
}
private void updateAllStatuses() {
List<OrbitBhyveDevice> devices = getDevices();
for (Thing th : getThing().getThings()) {
String deviceId = th.getUID().getId();
OrbitBhyveSprinklerHandler handler = (OrbitBhyveSprinklerHandler) th.getHandler();
for (OrbitBhyveDevice device : devices) {
if (deviceId.equals(th.getUID().getId())) {
updateDeviceStatus(device, handler);
}
}
}
}
private void updateDeviceStatus(@Nullable OrbitBhyveDevice device, @Nullable OrbitBhyveSprinklerHandler handler) {
if (device != null && handler != null) {
handler.setDeviceOnline(device.isConnected());
handler.updateDeviceStatus(device.getStatus());
handler.updateSmartWatering(device.getWaterSenseMode());
return;
}
}
private void updateDeviceStatus(String deviceId) {
for (Thing th : getThing().getThings()) {
if (deviceId.equals(th.getUID().getId())) {
OrbitBhyveSprinklerHandler handler = (OrbitBhyveSprinklerHandler) th.getHandler();
OrbitBhyveDevice device = getDevice(deviceId);
updateDeviceStatus(device, handler);
}
}
}
private void updateDeviceProgramStatus(OrbitBhyveProgram program) {
for (Thing th : getThing().getThings()) {
if (program.getDeviceId().equals(th.getUID().getId())) {
OrbitBhyveSprinklerHandler handler = (OrbitBhyveSprinklerHandler) th.getHandler();
if (handler != null) {
handler.updateProgram(program);
}
}
}
}
private void disableZones(String deviceId) {
disableChannel(deviceId, "zone_");
}
private void disablePrograms(String deviceId) {
disableChannel(deviceId, "program_");
}
private void disableChannel(String deviceId, String name) {
for (Thing th : getThing().getThings()) {
if (deviceId.equals(th.getUID().getId())) {
for (Channel ch : th.getChannels()) {
if (ch.getUID().getId().startsWith(name)) {
updateState(ch.getUID(), OnOffType.OFF);
}
}
return;
}
}
}
private @Nullable Channel getThingChannel(String deviceId, int station) {
for (Thing th : getThing().getThings()) {
if (deviceId.equals(th.getUID().getId())) {
return th.getChannel("zone_" + station);
}
}
logger.debug("Cannot find zone: {} for device: {}", station, deviceId);
return null;
}
private @Nullable Channel getThingChannel(String deviceId, String name) {
for (Thing th : getThing().getThings()) {
if (deviceId.equals(th.getUID().getId())) {
return th.getChannel(name);
}
}
logger.debug("Cannot find channel: {} for device: {}", name, deviceId);
return null;
}
private @Nullable Session createSession() {
String url = BHYVE_WS_URL;
URI uri = URI.create(url);
try {
// The socket that receives events
OrbitBhyveSocket socket = new OrbitBhyveSocket(this);
// Attempt Connect
Future<Session> fut = webSocketClient.connect(socket, uri);
// Wait for Connect
return fut.get();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot connect websocket client");
} catch (InterruptedException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot create websocket session");
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot create websocket session");
}
return null;
}
private synchronized void initializeWebSocketSession() {
logger.debug("Initializing WebSocket session");
closeSession();
session = createSession();
Session localSession = session;
if (localSession != null) {
logger.debug("WebSocket connected!");
try {
String msg = "{\"event\":\"app_connection\",\"orbit_session_token\":\"" + sessionToken + "\"}";
logger.trace("sending message:\n {}", msg);
localSession.getRemote().sendString(msg);
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Cannot send hello string to web socket!");
}
}
}
private void closeSession() {
Session localSession = session;
if (localSession != null && localSession.isOpen()) {
localSession.close();
}
}
public void runZone(String deviceId, String zone, int time) {
String dateTime = format.format(new Date());
try {
ping();
Session localSession = session;
if (localSession != null && localSession.isOpen() && localSession.getRemote() != null) {
localSession.getRemote()
.sendString("{\"event\":\"change_mode\",\"device_id\":\"" + deviceId + "\",\"timestamp\":\""
+ dateTime + "\",\"mode\":\"manual\",\"stations\":[{\"station\":" + zone
+ ",\"run_time\":" + time + "}]}");
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error during zone watering execution");
}
}
public void runProgram(String deviceId, String program) {
String dateTime = format.format(new Date());
try {
ping();
Session localSession = session;
if (localSession != null && localSession.isOpen() && localSession.getRemote() != null) {
localSession.getRemote().sendString("{\"event\":\"change_mode\",\"mode\":\"manual\",\"program\":\""
+ program + "\",\"device_id\":\"" + deviceId + "\",\"timestamp\":\"" + dateTime + "\"}");
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Error during program watering execution");
}
}
public void enableProgram(OrbitBhyveProgram program, boolean enable) {
try {
String payLoad = "{\"sprinkler_timer_program\":{\"id\":\"" + program.getId() + "\",\"device_id\":\""
+ program.getDeviceId() + "\",\"program\":\"" + program.getProgram() + "\",\"enabled\":" + enable
+ "}}";
logger.debug("updating program {} with data {}", program.getProgram(), payLoad);
ContentResponse response = sendRequestBuilder(BHYVE_PROGRAMS + "/" + program.getId(), HttpMethod.PUT)
.content(new StringContentProvider(payLoad), "application/json; charset=utf-8").send();
if (response.getStatus() == 200) {
if (logger.isTraceEnabled()) {
logger.trace("Enable programs response: {}", response.getContentAsString());
}
return;
} else {
logger.debug("Returned status: {}", response.getStatus());
updateStatus(ThingStatus.OFFLINE);
}
} catch (TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during updating programs");
} catch (InterruptedException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during updating programs");
Thread.currentThread().interrupt();
}
}
public void setRainDelay(String deviceId, int delay) {
String dateTime = format.format(new Date());
try {
ping();
Session localSession = session;
if (localSession != null && localSession.isOpen() && localSession.getRemote() != null) {
localSession.getRemote().sendString("{\"event\":\"rain_delay\",\"device_id\":\"" + deviceId
+ "\",\"delay\":" + delay + ",\"timestamp\":\"" + dateTime + "\"}");
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during rain delay setting");
}
}
public void stopWatering(String deviceId) {
String dateTime = format.format(new Date());
try {
ping();
Session localSession = session;
if (localSession != null && localSession.isOpen() && localSession.getRemote() != null) {
localSession.getRemote().sendString("{\"event\":\"change_mode\",\"device_id\":\"" + deviceId
+ "\",\"timestamp\":\"" + dateTime + "\",\"mode\":\"manual\",\"stations\":[]}");
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during watering stopping");
}
}
public List<OrbitBhyveProgram> getPrograms() {
try {
ContentResponse response = sendRequestBuilder(BHYVE_PROGRAMS, HttpMethod.GET).send();
if (response.getStatus() == 200) {
if (logger.isTraceEnabled()) {
logger.trace("Programs response: {}", response.getContentAsString());
}
OrbitBhyveProgram[] devices = gson.fromJson(response.getContentAsString(), OrbitBhyveProgram[].class);
return Arrays.asList(devices);
} else {
logger.debug("Returned status: {}", response.getStatus());
updateStatus(ThingStatus.OFFLINE);
}
} catch (TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during getting programs");
} catch (InterruptedException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during getting programs");
Thread.currentThread().interrupt();
}
return new ArrayList<>();
}
public void changeRunMode(String deviceId, String mode) {
String dateTime = format.format(new Date());
try {
ping();
Session localSession = session;
if (localSession != null && localSession.isOpen() && localSession.getRemote() != null) {
localSession.getRemote().sendString("{\"event\":\"change_mode\",\"mode\":\"" + mode
+ "\",\"device_id\":\"" + deviceId + "\",\"timestamp\":\"" + dateTime + "\"}");
}
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during setting run mode");
}
}
public void setSmartWatering(String deviceId, boolean enable) {
OrbitBhyveDevice device = getDevice(deviceId);
if (device != null && device.getId().equals(deviceId)) {
device.setWaterSenseMode(enable ? "auto" : "off");
updateDevice(deviceId, gson.toJson(device));
}
}
private void updateDevice(String deviceId, String deviceString) {
String payload = "{\"device\":" + deviceString + "}";
logger.trace("New String: {}", payload);
try {
ContentResponse response = sendRequestBuilder(BHYVE_DEVICES + "/" + deviceId, HttpMethod.PUT)
.content(new StringContentProvider(payload), "application/json;charset=UTF-8").send();
if (logger.isTraceEnabled()) {
logger.trace("Device update response: {}", response.getContentAsString());
}
if (response.getStatus() != 200) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Update device response status: " + response.getStatus());
}
} catch (TimeoutException | ExecutionException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during updating device");
} catch (InterruptedException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error during updating device");
Thread.currentThread().interrupt();
}
}
}

View File

@@ -0,0 +1,270 @@
/**
* 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.orbitbhyve.internal.handler;
import static org.openhab.binding.orbitbhyve.internal.OrbitBhyveBindingConstants.*;
import java.util.HashMap;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.orbitbhyve.internal.model.OrbitBhyveDevice;
import org.openhab.binding.orbitbhyve.internal.model.OrbitBhyveDeviceStatus;
import org.openhab.binding.orbitbhyve.internal.model.OrbitBhyveProgram;
import org.openhab.binding.orbitbhyve.internal.model.OrbitBhyveZone;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
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.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OrbitBhyveSprinklerHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveSprinklerHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(OrbitBhyveSprinklerHandler.class);
public OrbitBhyveSprinklerHandler(Thing thing) {
super(thing);
}
private int wateringTime = 5;
private HashMap<String, OrbitBhyveProgram> programs = new HashMap<>();
private String deviceId = "";
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
OrbitBhyveBridgeHandler handler = getBridgeHandler();
if (handler != null) {
if (CHANNEL_CONTROL.equals(channelUID.getId()) && command instanceof OnOffType) {
String mode = OnOffType.ON.equals(command) ? "auto" : "off";
handler.changeRunMode(deviceId, mode);
return;
}
if (CHANNEL_SMART_WATERING.equals(channelUID.getId()) && command instanceof OnOffType) {
boolean enable = OnOffType.ON.equals(command);
handler.setSmartWatering(deviceId, enable);
return;
}
if (!channelUID.getId().startsWith("enable_program") && OnOffType.OFF.equals(command)) {
handler.stopWatering(deviceId);
return;
}
if (CHANNEL_WATERING_TIME.equals(channelUID.getId()) && command instanceof QuantityType) {
final QuantityType<?> value = ((QuantityType<?>) command).toUnit(Units.MINUTE);
if (value != null) {
wateringTime = value.intValue();
updateState(CHANNEL_WATERING_TIME, new DecimalType(wateringTime));
}
return;
}
if (channelUID.getId().startsWith("zone")) {
if (OnOffType.ON.equals(command)) {
handler.runZone(deviceId, channelUID.getId().replace("zone_", ""), wateringTime);
}
return;
}
if (channelUID.getId().startsWith("program")) {
if (OnOffType.ON.equals(command)) {
handler.runProgram(deviceId, channelUID.getId().replace("program_", ""));
}
return;
}
if (channelUID.getId().startsWith("enable_program") && command instanceof OnOffType) {
String id = channelUID.getId().replace("enable_program_", "");
OrbitBhyveProgram prog = programs.get(id);
if (prog != null) {
handler.enableProgram(prog, OnOffType.ON.equals(command));
} else {
logger.debug("Cannot get program id: {}", id);
}
return;
}
if (CHANNEL_RAIN_DELAY.equals(channelUID.getId()) && command instanceof DecimalType) {
final QuantityType<?> value = ((QuantityType<?>) command).toUnit(Units.HOUR);
if (value != null) {
handler.setRainDelay(deviceId, value.intValue());
}
}
}
}
private String getSprinklerId() {
return getThing().getConfiguration().get("id") != null ? getThing().getConfiguration().get("id").toString()
: "";
}
private @Nullable OrbitBhyveBridgeHandler getBridgeHandler() {
Bridge bridge = getBridge();
if (bridge != null) {
return (OrbitBhyveBridgeHandler) bridge.getHandler();
}
return null;
}
@Override
public void initialize() {
Bridge bridge = getBridge();
if (bridge != null) {
logger.debug("Initializing, bridge is {}", bridge.getStatus());
if (ThingStatus.ONLINE == bridge.getStatus()) {
doInit();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
}
private synchronized void doInit() {
OrbitBhyveBridgeHandler handler = getBridgeHandler();
if (handler != null) {
deviceId = getSprinklerId();
if ("".equals(deviceId)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Sprinkler id is missing!");
} else {
OrbitBhyveDevice device = handler.getDevice(deviceId);
if (device != null) {
setDeviceOnline(device.isConnected());
createChannels(device.getZones());
updateDeviceStatus(device.getStatus());
}
List<OrbitBhyveProgram> programs = handler.getPrograms();
for (OrbitBhyveProgram program : programs) {
if (deviceId.equals(program.getDeviceId())) {
cacheProgram(program);
createProgram(program);
}
}
updateState(CHANNEL_WATERING_TIME, new DecimalType(wateringTime));
logger.debug("Finished initializing of sprinkler!");
}
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
doInit();
}
}
private synchronized void cacheProgram(OrbitBhyveProgram program) {
if (!programs.containsKey(program.getProgram())) {
programs.put(program.getProgram(), program);
}
}
public void updateDeviceStatus(OrbitBhyveDeviceStatus status) {
if (!status.getMode().isEmpty()) {
updateState(CHANNEL_MODE, new StringType(status.getMode()));
updateState(CHANNEL_CONTROL, "off".equals(status.getMode()) ? OnOffType.OFF : OnOffType.ON);
}
if (!status.getNextStartTime().isEmpty()) {
DateTimeType dt = new DateTimeType(status.getNextStartTime());
updateState(CHANNEL_NEXT_START, dt);
logger.debug("Next start time: {}", status.getNextStartTime());
}
updateState(CHANNEL_RAIN_DELAY, new DecimalType(status.getDelay()));
}
private void createProgram(OrbitBhyveProgram program) {
String channelName = "program_" + program.getProgram();
if (thing.getChannel(channelName) == null) {
logger.debug("Creating channel for program: {} with name: {}", program.getProgram(), program.getName());
createProgramChannel(channelName, "Switch", "Program " + program.getName());
}
String enableChannelName = "enable_" + channelName;
if (thing.getChannel(enableChannelName) == null) {
logger.debug("Creating enable channel for program: {} with name: {}", program.getProgram(),
program.getName());
createProgramChannel(enableChannelName, "Switch", "Enable program " + program.getName());
}
Channel ch = thing.getChannel(enableChannelName);
if (ch != null) {
updateState(ch.getUID(), program.isEnabled() ? OnOffType.ON : OnOffType.OFF);
}
}
private void createProgramChannel(String name, String type, String label) {
ChannelTypeUID program = new ChannelTypeUID(BINDING_ID, "program");
createChannel(name, type, label, program);
}
private void createChannels(List<OrbitBhyveZone> zones) {
for (OrbitBhyveZone zone : zones) {
String channelName = "zone_" + zone.getStation();
if (thing.getChannel(channelName) == null) {
logger.debug("Creating channel for zone: {} with name: {}", zone.getStation(), zone.getName());
createZoneChannel(channelName, "Switch", "Zone " + zone.getName());
}
}
}
private void createZoneChannel(String name, String type, String label) {
ChannelTypeUID zone = new ChannelTypeUID(BINDING_ID, "zone");
createChannel(name, type, label, zone);
}
private void createChannel(String name, String type, String label, ChannelTypeUID typeUID) {
ThingBuilder thingBuilder = editThing();
Channel channel = ChannelBuilder.create(new ChannelUID(thing.getUID(), name), type).withLabel(label)
.withType(typeUID).build();
thingBuilder.withChannel(channel);
updateThing(thingBuilder.build());
}
public void setDeviceOnline(boolean connected) {
if (!connected) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Not connected to Orbit BHyve Cloud");
} else {
updateStatus(ThingStatus.ONLINE);
}
}
public void updateProgram(OrbitBhyveProgram program) {
String enableChannelName = "enable_program_" + program.getProgram();
Channel ch = thing.getChannel(enableChannelName);
if (ch != null) {
updateState(ch.getUID(), program.isEnabled() ? OnOffType.ON : OnOffType.OFF);
}
}
public void updateSmartWatering(String senseMode) {
updateState(CHANNEL_SMART_WATERING, ("auto".equals(senseMode)) ? OnOffType.ON : OnOffType.OFF);
}
}

View File

@@ -0,0 +1,118 @@
/**
* 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.orbitbhyve.internal.model;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
/**
* The {@link OrbitBhyveDevice} holds information about a B-Hyve
* device.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveDevice {
String name = "";
String type = "";
String id = "";
List<OrbitBhyveZone> zones = new ArrayList<>();
OrbitBhyveDeviceStatus status = new OrbitBhyveDeviceStatus();
@SerializedName("is_connected")
boolean isConnected = false;
@SerializedName("hardware_version")
String hwVersion = "";
@SerializedName("firmware_version")
String fwVersion = "";
@SerializedName("mac_address")
String macAddress = "";
@SerializedName("num_stations")
int numStations = 0;
@SerializedName("last_connected_at")
String lastConnectedAt = "";
JsonObject location = new JsonObject();
@SerializedName("restricted_frequency")
JsonObject restrictedFrequency = new JsonObject();
@SerializedName("suggested_start_time")
String suggestedStartTime = "";
JsonObject timezone = new JsonObject();
@SerializedName("water_sense_mode")
String waterSenseMode = "";
@SerializedName("wifi_version")
int wifiVersion = 0;
public String getName() {
return name;
}
public String getType() {
return type;
}
public boolean isConnected() {
return isConnected;
}
public String getHwVersion() {
return hwVersion;
}
public String getFwVersion() {
return fwVersion;
}
public String getMacAddress() {
return macAddress;
}
public int getNumStations() {
return numStations;
}
public List<OrbitBhyveZone> getZones() {
return zones;
}
public String getId() {
return id;
}
public OrbitBhyveDeviceStatus getStatus() {
return status;
}
public String getWaterSenseMode() {
return waterSenseMode;
}
public void setWaterSenseMode(String waterSenseMode) {
this.waterSenseMode = waterSenseMode;
}
}

View File

@@ -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.orbitbhyve.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* The {@link OrbitBhyveDeviceStatus} holds information about a B-Hyve
* device status.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveDeviceStatus {
@SerializedName("run_mode")
String mode = "";
@SerializedName("next_start_time")
String nextStartTime = "";
@SerializedName("rain_delay")
int delay = 0;
@SerializedName("rain_delay_started_at")
String rainDelayStartedAt = "";
public String getMode() {
return mode;
}
public String getNextStartTime() {
return nextStartTime;
}
public int getDelay() {
return delay;
}
}

View File

@@ -0,0 +1,54 @@
/**
* 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.orbitbhyve.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* The {@link OrbitBhyveProgram} holds information about a B-Hyve
* device programs.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveProgram {
@SerializedName("device_id")
String deviceId = "";
String program = "";
String name = "";
String id = "";
boolean enabled = false;
public String getDeviceId() {
return deviceId;
}
public String getProgram() {
return program;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
public boolean isEnabled() {
return enabled;
}
}

View File

@@ -0,0 +1,33 @@
/**
* 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.orbitbhyve.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* The {@link OrbitBhyveSessionResponse} holds information about a B-Hyve
* session response.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveSessionResponse {
@SerializedName("orbit_session_token")
String orbitSessionToken = "";
public String getOrbitSessionToken() {
return orbitSessionToken;
}
}

View File

@@ -0,0 +1,63 @@
/**
* 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.orbitbhyve.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
/**
* The {@link OrbitBhyveSocketEvent} holds information about a B-Hyve
* event received on web socket.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveSocketEvent {
String event = "";
String mode = "";
JsonElement program = new JsonObject();
int delay = 0;
@SerializedName("device_id")
String deviceId = "";
@SerializedName("current_station")
int station = 0;
public String getEvent() {
return event;
}
public String getMode() {
return mode;
}
public String getDeviceId() {
return deviceId;
}
public int getStation() {
return station;
}
public JsonElement getProgram() {
return program;
}
public int getDelay() {
return delay;
}
}

View File

@@ -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.orbitbhyve.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonArray;
import com.google.gson.annotations.SerializedName;
/**
* The {@link OrbitBhyveZone} holds information about a B-Hyve
* zone.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveZone {
String name = "";
int station = 0;
@SerializedName("catch_cup_run_time")
int catchCupRunTime = 0;
@SerializedName("catch_cup_volumes")
JsonArray catchCupVolumes = new JsonArray();
@SerializedName("num_sprinklers")
int numSprinklers = 0;
@SerializedName("landscape_type")
@Nullable
String landscapeType;
@SerializedName("soil_type")
@Nullable
String soilType;
@SerializedName("sprinkler_type")
@Nullable
String sprinklerType;
@SerializedName("sun_shade")
@Nullable
String sunShade;
@SerializedName("slope_grade")
int slopeGrade = 0;
@SerializedName("image_url")
String imageUrl = "";
@SerializedName("smart_watering_enabled")
boolean smartWateringEnabled = false;
public String getName() {
return name;
}
public int getStation() {
return station;
}
public boolean isSmartWateringEnabled() {
return smartWateringEnabled;
}
public void setSmartWateringEnabled(boolean smartWateringEnabled) {
this.smartWateringEnabled = smartWateringEnabled;
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.orbitbhyve.internal.net;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.openhab.binding.orbitbhyve.internal.handler.OrbitBhyveBridgeHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OrbitBhyveSocket} class defines websocket used for connection with
* the Orbit B-Hyve cloud.
*
* @author Ondrej Pecta - Initial contribution
*/
@NonNullByDefault
public class OrbitBhyveSocket extends WebSocketAdapter {
private final Logger logger = LoggerFactory.getLogger(OrbitBhyveSocket.class);
private OrbitBhyveBridgeHandler handler;
public OrbitBhyveSocket(OrbitBhyveBridgeHandler handler) {
this.handler = handler;
}
@Override
public void onWebSocketText(@Nullable String message) {
super.onWebSocketText(message);
if (message != null) {
logger.trace("Got message: {}", message);
handler.processStatusResponse(message);
}
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="orbitbhyve" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Orbit B-hyve Binding</name>
<description>This is the binding for Orbit B-hyve Wi-Fi irrigation systems.</description>
</binding:binding>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="bridge-type:orbitbhyve:bridge">
<parameter name="email" type="text" required="true">
<label>Email</label>
<context>email</context>
<description>This is a login to your B-hyve account.</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<context>password</context>
<description>This is a password to your B-hyve account.</description>
</parameter>
<parameter name="refresh" type="integer" required="false" min="10">
<label>Refresh</label>
<description>Specifies the refresh time in seconds for polling data from Orbit cloud</description>
<default>30</default>
</parameter>
</config-description>
<config-description uri="thing-type:orbitbhyve:sprinkler">
<parameter name="id" type="text" required="true">
<label>Sprinkler Device ID</label>
<description>The identifier of the Orbit sprinkler device</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="orbitbhyve"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Bridge Type -->
<bridge-type id="bridge">
<label>Bridge</label>
<description>Bridge for Orbit B-hyve Binding</description>
<config-description-ref uri="bridge-type:orbitbhyve:bridge"/>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="orbitbhyve"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Mode Channel Type -->
<channel-type id="mode">
<item-type>String</item-type>
<label>Irrigation Mode</label>
<description>Channel representing mode of Orbit B-hyve Device (auto/manual)</description>
<state readOnly="true">
<options>
<option value="auto">Auto</option>
<option value="manual">Manual</option>
</options>
</state>
</channel-type>
<channel-type id="next_start">
<item-type>DateTime</item-type>
<label>Next Watering Time</label>
<description>Channel representing start time of the next watering</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="rain_delay">
<item-type>Number:Time</item-type>
<label>Rain Delay</label>
<description>Channel representing rain delay in hours</description>
<state pattern="%d h"></state>
</channel-type>
<channel-type id="watering_time">
<item-type>Number:Time</item-type>
<label>Zone Watering Time</label>
<description>Channel representing the manual zone watering time in minutes</description>
<state max="240" min="0" pattern="%d min"></state>
</channel-type>
<channel-type id="control">
<item-type>Switch</item-type>
<label>Sprinkler State Control</label>
<description>Channel for enabling/disabling the sprinkler (ON/OFF)</description>
</channel-type>
<channel-type id="smart_watering">
<item-type>Switch</item-type>
<label>Smart Watering Control</label>
<description>Channel for enabling/disabling the smart watering mode</description>
</channel-type>
<channel-type id="program">
<item-type>Switch</item-type>
<label>Program Channel</label>
<description>Dynamic channel representing a program</description>
</channel-type>
<channel-type id="zone">
<item-type>Switch</item-type>
<label>Zone Channel</label>
<description>Dynamic channel representing a zone</description>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="orbitbhyve"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="sprinkler">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Sprinkler</label>
<description>Orbit B-hyve Sprinkler</description>
<channels>
<channel id="control" typeId="control"/>
<channel id="mode" typeId="mode"/>
<channel id="smart_watering" typeId="smart_watering"/>
<channel id="next_start" typeId="next_start"/>
<channel id="rain_delay" typeId="rain_delay"/>
<channel id="watering_time" typeId="watering_time"/>
</channels>
<config-description-ref uri="thing-type:orbitbhyve:sprinkler"/>
</thing-type>
</thing:thing-descriptions>