[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1882 additions and 0 deletions

View File

@ -222,6 +222,7 @@
/bundles/org.openhab.binding.openweathermap/ @cweitkamp
/bundles/org.openhab.binding.openwebnet/ @mvalla
/bundles/org.openhab.binding.oppo/ @mlobstein
/bundles/org.openhab.binding.orbitbhyve/ @octa22
/bundles/org.openhab.binding.orvibo/ @tavalin
/bundles/org.openhab.binding.paradoxalarm/ @theater
/bundles/org.openhab.binding.pentair/ @jsjames

View File

@ -1091,6 +1091,11 @@
<artifactId>org.openhab.binding.oppo</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.orbitbhyve</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.orvibo</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,84 @@
# Orbit B-hyve Binding
This is the binding for the [Orbit B-hyve](https://bhyve.orbitonline.com/) wi-fi sprinklers.
## Supported Things
This binding should support all the sprinklers which can be controlled by the Orbit B-hyve mobile application.
So far only the [Orbit B-hyve 8-zone Indoor Timer](https://bhyve.orbitonline.com/indoor-timer/) has been confirmed working. (Hardware version WT24-0001)
## Discovery
This binding supports the auto discovery of the sprinklers bound to your Orbit B-hyve account.
To start the discovery you need to create a bridge thing and enter valid credentials to your Orbit B-hyve cloud account.
## Thing Configuration
The bridge thing requires a manual configuration. You have to enter valid credentials to your Orbit B-hyve account, and you can also set the refresh time in seconds for polling data from the Orbit cloud.
There is no user configuration related to sprinkler things. Sprinklers do need a configuration property _id_ identifying the device, but the only way how to retrieve it is to let the bridge to auto discover sprinklers.
## Channels
This binding automatically detects all zones and programs for each sprinkler and creates these dynamic channels:
| channel | type | description |
|------------------|--------|------------------------------------------------------------------|
| zone_% | Switch | This channel controls the manual zone watering (ON/OFF) |
| program_% | Switch | This channel controls the manual program watering (ON/OFF) |
| enable_program_% | Switch | This channel controls the automatic program scheduling (ON/OFF) |
Beside the dynamic channels each sprinkler thing provides these standard channels:
| channel | type | description |
|----------------|-------------|--------------------------------------------------------------------|
| mode | String | This channel represents the mode of sprinkler device (auto/manual) |
| next_start | DateTime | This channel represents the start time of the next watering |
| rain_delay | Number:Time | This channel manages the current rain delay in hours |
| watering_time | Number:Time | This channel manages the manual zone watering time in minutes |
| control | Switch | This channel controls the sprinkler (ON/OFF) |
| smart_watering | Switch | This channel controls the smart watering (ON/OFF) |
## Full Example
_*.things example_
```
Bridge orbitbhyve:bridge:mybridge "Orbit Bridge" [ email="your@ema.il", password="yourPass", refresh=30 ] {
Thing sprinkler indoor_timer "Sprinkler" [ id="4cab55704e0d7ddf98c1cc37" ]
}
```
_*.items example_
```
Switch IrrigationControl "Irrigation active" <bhyve> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:control" }
Switch IrrigationSmartWatering "Smart watering" <bhyve> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:smart_watering" }
Switch Irrigation1 "Zone 1" <water> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:zone_1" }
Switch Irrigation2 "Zone 2" <water> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:zone_2" }
Switch Irrigation3 "Zone 3" <water> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:zone_3" }
Switch Irrigation4 "Zone 4" <water> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:zone_4" }
Switch IrrigationP1 "Run program A" <program> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:program_a" }
Switch IrrigationP1Enable "Schedule program A" <program> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:enable_program_a" }
String IrrigationMode "Irrigation mode [%s]" <water> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:mode" }
Number IrrigationTime "Irrigation time [%d min]" <clock> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:watering_time" }
Number IrrigationRainDelay "Rain delay [%d h]" <hourglass> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:rain_delay" }
DateTime IrrigationNextStart "Next start A [%1$td.%1$tm.%1$tY %1$tR]" <clock> (Out_Irrigation) { channel="orbitbhyve:sprinkler:mybridge:indoor_timer:next_start" }
```
_*.sitemap example_
```
Switch item=IrrigationControl
Switch item=IrrigationSmartWatering
Switch item=Irrigation1
Switch item=Irrigation2
Switch item=Irrigation3
Switch item=Irrigation4
Setpoint item=IrrigationTime minValue=1 maxValue=240 step=1
Switch item=IrrigationP1
Switch item=IrrigationP1Enable
Text item=IrrigationMode
Text item=IrrigationRainDelay
Switch item=IrrigationRainDelay mappings=[0="OFF", 24="24", 48="48", 72="72"]
Text item=IrrigationNextStart visibility=[IrrigationP1Enable==ON]
```

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.2.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.orbitbhyve</artifactId>
<name>openHAB Add-ons :: Bundles :: Orbit B-hyve Binding</name>
</project>

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>

View File

@ -254,6 +254,7 @@
<module>org.openhab.binding.openweathermap</module>
<module>org.openhab.binding.openwebnet</module>
<module>org.openhab.binding.oppo</module>
<module>org.openhab.binding.orbitbhyve</module>
<module>org.openhab.binding.orvibo</module>
<module>org.openhab.binding.paradoxalarm</module>
<module>org.openhab.binding.pentair</module>