Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sensorpush] Initial contribution #15933

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -305,6 +305,7 @@
/bundles/org.openhab.binding.sensebox/ @hakan42
/bundles/org.openhab.binding.sensibo/ @seime
/bundles/org.openhab.binding.sensorcommunity/ @weymann
/bundles/org.openhab.binding.sensorpush/ @bobadair
/bundles/org.openhab.binding.serial/ @MikeJMajor
/bundles/org.openhab.binding.serialbutton/ @kaikreuzer
/bundles/org.openhab.binding.shelly/ @markus7017
Expand Down
5 changes: 5 additions & 0 deletions bom/openhab-addons/pom.xml
Expand Up @@ -1511,6 +1511,11 @@
<artifactId>org.openhab.binding.sensorcommunity</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.sensorpush</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.serial</artifactId>
Expand Down
13 changes: 13 additions & 0 deletions bundles/org.openhab.binding.sensorpush/NOTICE
@@ -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
89 changes: 89 additions & 0 deletions bundles/org.openhab.binding.sensorpush/README.md
@@ -0,0 +1,89 @@
# SensorPush Binding

[SensorPush](https://www.sensorpush.com/) sells a line of battery-powered wireless sensors that, depending on the model, provide data on temperature, relative humidity, barometric pressure, dew point, and vapor pressure deficit (VPD).
The sensors communicate using Bluetooth LE.
While they can be used directly via BLE, when multiple sensors are in use they are typically configured to relay data to the cloud via the G1 WiFi Gateway.

This binding retrieves sensor data from the SensorPush Gateway Cloud using a published API.
It requires use of the G1 WiFi gateway and a connection to the SensorPush Gateway Cloud.

Supported sensors: HT1, HT.W, and HTP.XW

## Supported Things

The binding supports the following thing types:

* `cloudbridge` - Provides connectivity to the SensorPush Gateway Cloud.
* `sensor` - Represents a HT1, HT.W, or HTP.XW sensor.

## Discovery

Automatic discovery is supported for the sensors, but not for the cloud gateway.
It is recommended that you configure the cloudbridge thing manually using the UI and let the associated sensors be discovered automatically.

## Thing Configuration

It is recommended that the SensorPush binding be configured through the management UI.
There is no easy way for the user to determine the sensor IDs in advance, so it is best to simply auto-discover them.
After configuring the bridge, all active sensors should appear in the discovery inbox.

### Cloudbridge thing

The `cloudbridge` thing is responsible for communicating with the SensorPush Gateway Cloud.
You must supply your user name and password.
The poll and timeout parameters are optional.

Parameters:

| Name | Required | Type | Default | Description |
|--------------|----------|---------------|---------|--------------------------|
| `user` | Yes |text | n/a | Your SensorPush cloud service user name (email address) |
| `password`| Yes |text | n/a | Your SensorPush cloud service password |
| `poll` | No |integer, 2-60 | 5 | Polling interval in minutes |
| `timeout` | No |integer, 1-120 | 30 | Timeout period for API requests in seconds. |

**Note:** To activate your API access, you must log in to the [Gateway Cloud Dashboard](https://dashboard.sensorpush.com/) and agree to the terms of service.
Once you've logged in that initial time, your account will have access.

Thing config file example:

```
Thing sensorpush:cloudbridge:bridge [ user="mickey@disney.com", password="mouse", poll=5 ]
```

### Sensor thing

The `sensor` thing represents an individual SensorPush sensor.
It has a variety of channels that will be populated with the latest sensor readings at each poll period.
Sensors relay their readings at approximate 1 minute intervals, so in normal operation the oldest a reading should be is approximately 1 minute plus the configured poll interval.
The `time` channel will contain the timestamp of the latest readings.
If your particular sensor model does not support a given channel, the value for that channel will remain NULL.
The id parameter must be supplied.

Parameters:

| Name | Required | Type | Default | Description |
|-------------------|----------|---------------|---------|------------------------------------|
| `id` | Yes | text | n/a | The unique ID number of the sensor |
| `pressureMode`| No | text | station | The reporting mode for barometric pressure. Must be set to either "station" or "meteorological". The station mode reports the pressure as recorded by the sensor, while the meteorological mode adjusts the reported pressure to mean sea level as is common for weather reporting.|
| `altitude` | No | integer | n/a | The altitude of the sensor in feet above MSL. It is only necessary to set this parameter if you selected the "meteorological" option for pressureMode and have not set the sensor altitude in the SensorPush app. The altitude set in the SensorPush app will override this value.|

## Channels

The following channels are provided by the `sensor` thing:

| Channel | Type | Description |
|-------------|--------------------------|----------------------------------------------------|
| temperature | Number:Temperature | Temperature |
| humidity | Number:Dimensionless | Relative humidity |
| pressure | Number:Pressure | Barometric pressure |
| dewpoint | Number:Temperature | Dew point |
| vpd | Number:Pressure | Vapor pressure deficit |
| time | DateTime | Time of reading |
| rssi | Number | Received signal strength expressed as a number 1-4 |
| rssidbm | Number:Power | Received signal strength in dBm |
| voltage | Number:ElectricPotential | Battery voltage |

Note that all channels except `time` and `rssi` use QuantityType values.

No channels are provided by the `cloudbridge` thing.
17 changes: 17 additions & 0 deletions bundles/org.openhab.binding.sensorpush/pom.xml
@@ -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 https://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>4.1.0-SNAPSHOT</version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<version>4.1.0-SNAPSHOT</version>
<version>4.2.0-SNAPSHOT</version>

</parent>

<artifactId>org.openhab.binding.sensorpush</artifactId>

<name>openHAB Add-ons :: Bundles :: SensorPush Binding</name>

</project>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.sensorpush-${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-sensorpush" description="SensorPush Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.sensorpush/${project.version}</bundle>
</feature>
</features>
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2023 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.sensorpush.internal;

import java.util.Collections;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;

/**
* The {@link SensorPushBindingConstants} class defines common constants, which are used across the whole binding.
*
* @author Bob Adair - Initial contribution
*/
@NonNullByDefault
public class SensorPushBindingConstants {

private static final String BINDING_ID = "sensorpush";

// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "cloudbridge");
public static final ThingTypeUID THING_TYPE_SENSOR = new ThingTypeUID(BINDING_ID, "sensor");

// Set of discoverable Thing Type UIDs
public static final Set<ThingTypeUID> DISCOVERABLE_DEVICE_TYPE_UIDS = Collections.singleton(THING_TYPE_SENSOR);

// Properties
public static final String PROPERTY_ID = "id";
public static final String PROPERTY_ADDRESS = "bluetoothAddress";

// List of all Channel ids
public static final String CHANNEL_TEMPERATURE = "temperature";
public static final String CHANNEL_HUMIDITY = "humidity";
public static final String CHANNEL_TIME = "time";
public static final String CHANNEL_RSSI = "rssi";
public static final String CHANNEL_RSSI_DBM = "rssidbm";
public static final String CHANNEL_VOLTAGE = "voltage";
public static final String CHANNEL_PRESSURE = "pressure";
public static final String CHANNEL_DEWPOINT = "dewpoint";
public static final String CHANNEL_VPD = "vpd";
}
@@ -0,0 +1,106 @@
/**
* Copyright (c) 2010-2023 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.sensorpush.internal;

import static org.openhab.binding.sensorpush.internal.SensorPushBindingConstants.*;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sensorpush.internal.handler.CloudBridgeHandler;
import org.openhab.binding.sensorpush.internal.protocol.Sensor;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
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 SensorPushDiscoveryService} handles discovery of sensors as they are identified by the bridge handler.
* Requests from the framework to startScan() will initiate a call to the bridge handler's pollSensors() method.
* Otherwise the bridge handler will poll for sensors every other poll interval.
*
* @author Bob Adair - Initial contribution
*/
@NonNullByDefault
public class SensorPushDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {

private final Logger logger = LoggerFactory.getLogger(SensorPushDiscoveryService.class);

private @NonNullByDefault({}) CloudBridgeHandler bridgeHandler;
private final Set<String> discoveredSensorSet = new HashSet<>();

public SensorPushDiscoveryService() {
super(DISCOVERABLE_DEVICE_TYPE_UIDS, 0, false);
}

@Override
protected void startScan() {
logger.trace("Starting discovery scan");
discoveredSensorSet.clear();
if (bridgeHandler != null) {
bridgeHandler.pollSensors();
}
}

public void processSensor(Sensor sensor) {
Boolean active = sensor.active;
String deviceId = sensor.deviceId;
String name = sensor.name;
if (deviceId != null && name != null && active != null) {
if (!discoveredSensorSet.contains(deviceId) && active) {
notifyDiscovery(deviceId, name);
discoveredSensorSet.add(deviceId);
}
} else {
logger.debug("Processing sensor with unexpected null values. Ignoring.");
}
}

private void notifyDiscovery(String idString, String label) {
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
ThingUID uid = new ThingUID(THING_TYPE_SENSOR, bridgeUID, idString.replace(".", ""));

Map<String, Object> properties = new HashMap<>();
properties.put(PROPERTY_ID, idString);

DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withProperties(properties)
.withLabel(label).withRepresentationProperty(PROPERTY_ID).build();
thingDiscovered(result);
}

@Override
public void setThingHandler(ThingHandler handler) {
if (handler instanceof CloudBridgeHandler) {
bridgeHandler = (CloudBridgeHandler) handler;
bridgeHandler.setDiscoveryService(this);
}
}

@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}

@Override
public void deactivate() {
super.deactivate();
}
}
@@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2023 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.sensorpush.internal;

import static org.openhab.binding.sensorpush.internal.SensorPushBindingConstants.*;

import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.sensorpush.internal.handler.CloudBridgeHandler;
import org.openhab.binding.sensorpush.internal.handler.SensorHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
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 SensorPushHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Bob Adair - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.sensorpush", service = ThingHandlerFactory.class)
public class SensorPushHandlerFactory extends BaseThingHandlerFactory {

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_SENSOR);
private final HttpClient httpClient;

@Activate
public SensorPushHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
this.httpClient = httpClientFactory.getCommonHttpClient();
}

@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 CloudBridgeHandler((Bridge) thing, httpClient);
} else {
if (THING_TYPE_SENSOR.equals(thingTypeUID)) {
return new SensorHandler(thing);
}
}

return null;
}
}