Skip to content

Commit

Permalink
[grundfosalpha] Initial contribution (openhab#15907)
Browse files Browse the repository at this point in the history
* [Bluez] Disable automatic filtering of duplicate data
* Added Grundfos Alpha binding

Signed-off-by: Markus Heberling <[email protected]>
Signed-off-by: Jørgen Austvik <[email protected]>
  • Loading branch information
tisoft authored and austvik committed Mar 27, 2024
1 parent 834798c commit c33ea1f
Show file tree
Hide file tree
Showing 15 changed files with 540 additions and 0 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
/bundles/org.openhab.binding.bluetooth.enoceanble/ @pfink
/bundles/org.openhab.binding.bluetooth.generic/ @cpmeister
/bundles/org.openhab.binding.bluetooth.govee/ @cpmeister
/bundles/org.openhab.binding.bluetooth.grundfosalpha/ @tisoft
/bundles/org.openhab.binding.bluetooth.radoneye/ @petero-dk
/bundles/org.openhab.binding.bluetooth.roaming/ @cpmeister
/bundles/org.openhab.binding.bluetooth.ruuvitag/ @ssalonen
Expand Down
5 changes: 5 additions & 0 deletions bom/openhab-addons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@
<artifactId>org.openhab.binding.bluetooth.govee</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.bluetooth.grundfosalpha</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.bluetooth.radoneye</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@
*/
package org.openhab.binding.bluetooth.bluez.internal;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.bluez.exceptions.BluezFailedException;
import org.bluez.exceptions.BluezInvalidArgumentsException;
import org.bluez.exceptions.BluezNotReadyException;
import org.bluez.exceptions.BluezNotSupportedException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.freedesktop.dbus.types.Variant;
import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
import org.openhab.binding.bluetooth.BluetoothAddress;
import org.openhab.binding.bluetooth.bluez.internal.events.AdapterDiscoveringChangedEvent;
Expand Down Expand Up @@ -139,6 +146,15 @@ public void dispose() {
return null;
}

Map<String, Variant<?>> filter = new HashMap<>();
filter.put("DuplicateData", new Variant<>(true));
try {
adapter.setDiscoveryFilter(filter);
} catch (BluezInvalidArgumentsException | BluezFailedException | BluezNotSupportedException
| BluezNotReadyException e) {
throw new RuntimeException(e);
}

// now lets make sure that discovery is turned on
if (!localAdapter.startDiscovery()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Trying to start discovery");
Expand Down
13 changes: 13 additions & 0 deletions bundles/org.openhab.binding.bluetooth.grundfosalpha/NOTICE
Original file line number Diff line number Diff line change
@@ -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
49 changes: 49 additions & 0 deletions bundles/org.openhab.binding.bluetooth.grundfosalpha/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# GrundfosAlpha Binding

This adds support for reading out the data of Grundfos Alpha Pumps with a [Grundfos Alpha Reader](https://product-selection.grundfos.com/products/alpha-reader)

The reverse engineering of the protocol was taken from [https://github.com/JsBergbau/AlphaDecoder](https://github.com/JsBergbau/AlphaDecoder).

## Supported Things

- `mi401`: The Grundfos MI401 ALPHA Reader

## Discovery

All readers are auto-detected as soon as Bluetooth is configured in openHAB and the MI401 device is powered on.

## Thing Configuration

### `mi401` Thing Configuration

| Name | Type | Description | Default | Required | Advanced |
|---------|------|-----------------------------------------------|---------|----------|----------|
| address | text | Bluetooth address in XX:XX:XX:XX:XX:XX format | N/A | yes | no |

## Channels

| Channel | Type | Read/Write | Description |
|------------------|---------------------------|------------|------------------------------------|
| rssi | Number | R | Received Signal Strength Indicator |
| flow-rate | Number:VolumetricFlowRate | R | The flow rate of the pump |
| pump-head | Number:Length | R | The water head above the pump |
| pump-temperature | Number:Temperature | R | The temperature of the pump |
| battery-level | Number:Dimensionless | R | The battery level of the reader |

## Full Example

grundfos_alpha.things (assuming you have a Bluetooth bridge with the ID `bluetooth:bluegiga:adapter1`:

```java
bluetooth:mi401:hci0:sensor1 "Grundfos Alpha Reader 1" (bluetooth:bluegiga:adapter1) [ address="12:34:56:78:9A:BC" ]
```

grundfos_alpha.items:

```java
Number RSSI "RSSI [%.1f dBm]" <QualityOfService> { channel="bluetooth:mi401:hci0:sensor1:rssi" }
Number:VolumetricFlowRate Flow_rate "Flowrate [%.1f %unit%]" <flow> { channel="bluetooth:mi401:hci0:sensor1:flow-rate" }
Number:Length Pump_Head "Pump head [%.1f %unit%]" <water> { channel="bluetooth:mi401:hci0:sensor1:pump-head" }
Number:Temperature Pump_Temperature "Temperature [%.1f %unit%]" <temperature> { channel="bluetooth:mi401:hci0:sensor1:pump-temperature" }
Number:Dimensionless Battery_Level "Battery Level [%d %%]" <battery> { channel="bluetooth:mi401:hci0:sensor1:battery-level" }
```
25 changes: 25 additions & 0 deletions bundles/org.openhab.binding.bluetooth.grundfosalpha/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?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>
</parent>

<artifactId>org.openhab.binding.bluetooth.grundfosalpha</artifactId>

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

<dependencies>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.bluetooth</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.bluetooth.grundfosalpha-${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-bluetooth-grundfosalpha" description="GrundfosAlpha Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth/${project.version}</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.grundfosalpha/${project.version}</bundle>
</feature>
</features>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* 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.bluetooth.grundfosalpha.internal;

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

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

// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_MI401 = new ThingTypeUID(BluetoothBindingConstants.BINDING_ID, "mi401");

// List of all Channel ids
public static final String CHANNEL_TYPE_FLOW_RATE = "flow-rate";
public static final String CHANNEL_TYPE_PUMP_HEAD = "pump-head";
public static final String CHANNEL_TYPE_BATTERY_LEVEL = "battery-level";
public static final String CHANNEL_TYPE_PUMP_TEMPERATUR = "pump-temperature";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* 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.bluetooth.grundfosalpha.internal;

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

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
import org.openhab.binding.bluetooth.discovery.BluetoothDiscoveryDevice;
import org.openhab.binding.bluetooth.discovery.BluetoothDiscoveryParticipant;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This discovery participant is able to recognize Grundfos Alpha devices and create discovery results for them.
*
* @author Markus Heberling - Initial contribution
*
*/
@NonNullByDefault
@Component
public class GrundfosAlphaDiscoveryParticipant implements BluetoothDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(GrundfosAlphaDiscoveryParticipant.class);

@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Set.of(GrundfosAlphaBindingConstants.THING_TYPE_MI401);
}

@Override
public boolean requiresConnection(BluetoothDiscoveryDevice device) {
return false;
}

@Override
public @Nullable ThingUID getThingUID(BluetoothDiscoveryDevice device) {
Integer manufacturerId = device.getManufacturerId();
@Nullable
String name = device.getName();
logger.debug("Discovered device {} with manufacturerId {} and name {}", device.getAddress(), manufacturerId,
name);
if ("MI401".equals(name)) {
return new ThingUID(GrundfosAlphaBindingConstants.THING_TYPE_MI401, device.getAdapter().getUID(),
device.getAddress().toString().toLowerCase().replace(":", ""));
}
return null;
}

@Override
public @Nullable DiscoveryResult createResult(BluetoothDiscoveryDevice device) {
ThingUID thingUID = getThingUID(device);
if (thingUID == null) {
return null;
}
String label = "Grundfos Alpha Reader MI401";
Map<String, Object> properties = new HashMap<>();
properties.put(BluetoothBindingConstants.CONFIGURATION_ADDRESS, device.getAddress().toString());
properties.put(Thing.PROPERTY_VENDOR, "Grundfos");
Integer txPower = device.getTxPower();
if (txPower != null) {
properties.put(BluetoothBindingConstants.PROPERTY_TXPOWER, Integer.toString(txPower));
}

// Create the discovery result and add to the inbox
return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
.withRepresentationProperty(BluetoothBindingConstants.CONFIGURATION_ADDRESS)
.withBridge(device.getAdapter().getUID()).withLabel(label).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* 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.bluetooth.grundfosalpha.internal;

import static org.openhab.binding.bluetooth.grundfosalpha.internal.GrundfosAlphaBindingConstants.*;

import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Length;
import javax.measure.quantity.Temperature;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bluetooth.BeaconBluetoothHandler;
import org.openhab.binding.bluetooth.BluetoothDeviceListener;
import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
import org.openhab.core.library.dimension.VolumetricFlowRate;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Thing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link GrundfosAlphaHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Markus Heberling - Initial contribution
*/
@NonNullByDefault
public class GrundfosAlphaHandler extends BeaconBluetoothHandler implements BluetoothDeviceListener {

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

public GrundfosAlphaHandler(Thing thing) {
super(thing);
}

@Override
public void onScanRecordReceived(BluetoothScanNotification scanNotification) {
super.onScanRecordReceived(scanNotification);
byte[] data = scanNotification.getManufacturerData();
if (data != null && data.length == 21) {
int batteryLevel = (data[5] & 0xFF) * 25;
QuantityType<Dimensionless> quantity = new QuantityType<>(batteryLevel, Units.PERCENT);
updateState(CHANNEL_TYPE_BATTERY_LEVEL, quantity);

float flowRate = ((data[9] & 0xFF) << 8 | (data[8] & 0xFF)) / 6553.5f;
QuantityType<VolumetricFlowRate> quantity2 = new QuantityType<>(flowRate, Units.CUBICMETRE_PER_HOUR);
updateState(CHANNEL_TYPE_FLOW_RATE, quantity2);

float pumpHead = ((data[11] & 0xFF) << 8 | (data[10] & 0xFF)) / 3276.7f;
QuantityType<Length> quantity3 = new QuantityType<>(pumpHead, SIUnits.METRE);
updateState(CHANNEL_TYPE_PUMP_HEAD, quantity3);

float pumpTemperature = data[14] & 0xFF;
QuantityType<Temperature> quantity4 = new QuantityType<>(pumpTemperature, SIUnits.CELSIUS);
updateState(CHANNEL_TYPE_PUMP_TEMPERATUR, quantity4);
}
}
}
Loading

0 comments on commit c33ea1f

Please sign in to comment.