Skip to content

Commit

Permalink
Add support for Alpha3
Browse files Browse the repository at this point in the history
Signed-off-by: Jacob Laursen <[email protected]>
  • Loading branch information
jlaur committed Feb 4, 2025
1 parent 2474953 commit ef112e4
Show file tree
Hide file tree
Showing 19 changed files with 1,328 additions and 54 deletions.
46 changes: 42 additions & 4 deletions bundles/org.openhab.binding.bluetooth.grundfosalpha/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
# 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)
This binding adds support for reading out the data of Grundfos Alpha pumps with a [Grundfos Alpha Reader](https://product-selection.grundfos.com/products/alpha-reader) or [Alpha3 pump](https://product-selection.grundfos.com/products/alpha/alpha3) with built-in Bluetooth.

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

## Supported Things

- `alpha3`: The Grundfos Alpha3 pump
- `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.
All pumps and readers are auto-detected as soon as Bluetooth is configured in openHAB and the devices are powered on.

## Thing Configuration

### `alpha3` Thing Configuration

| Name | Type | Description | Default | Required | Advanced |
|-----------------|---------|---------------------------------------------------------|---------|----------|----------|
| address | text | Bluetooth address in XX:XX:XX:XX:XX:XX format | N/A | yes | no |
| refreshInterval | integer | Number of seconds between fetching values from the pump | 30 | no | yes |

### Pairing

After creating the Thing, the binding will attempt to connect to the pump.
To start the pairing process, press the blue LED button on the pump.
When the LED stops blinking and stays lit, the connection has been established, and the Thing should appear online.

However, the pump may still not be bonded correctly, which could prevent the binding from reconnecting after a disconnection.
On Linux, you can take additional steps to fix this issue by manually pairing the pump:

```shell
bluetoothctl pair XX:XX:XX:XX:XX:XX
Attempting to pair with XX:XX:XX:XX:XX:XX
[CHG] Device XX:XX:XX:XX:XX:XX Bonded: yes
[CHG] Device XX:XX:XX:XX:XX:XX Paired: yes
Pairing successful
```

### `mi401` Thing Configuration

| Name | Type | Description | Default | Required | Advanced |
Expand All @@ -22,9 +47,22 @@ All readers are auto-detected as soon as Bluetooth is configured in openHAB and

## Channels

### `alpha3` Channels

| Channel | Type | Read/Write | Description |
|------------------|---------------------------|------------|------------------------------------|
| rssi | Number:Power | 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 |
| voltage-ac | Number:ElectricPotential | R | Current AC pump voltage |
| power | Number:Power | R | Current pump power consumption |
| motor-speed | Number:Frequency | R | Current rotation of the pump motor |

### `mi401` Channels

| Channel | Type | Read/Write | Description |
|------------------|---------------------------|------------|------------------------------------|
| rssi | Number | R | Received Signal Strength Indicator |
| rssi | Number:Power | 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 |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright (c) 2010-2025 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.Arrays;
import java.util.Objects;
import java.util.UUID;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
import org.openhab.binding.bluetooth.BluetoothDevice;
import org.openhab.binding.bluetooth.grundfosalpha.internal.protocol.MessageType;
import org.openhab.core.util.HexUtils;

/**
* This represents a request for writing characteristic to a Bluetooth device.
*
* This can be used for adding such requests to a queue.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class CharacteristicRequest {
private UUID uuid;
private byte[] value;

/**
* Creates a new request object.
*
* @param uuid The UUID of the characteristic
* @param messageType The {@link MessageType} containing the data to write
*/
public CharacteristicRequest(UUID uuid, MessageType messageType) {
this.uuid = uuid;
this.value = messageType.request();
}

/**
* Writes the characteristic to the provided {@link BluetoothDevice}.
*
* @param device The Bluetooth device
* @return true if written, false if the characteristic is not found in the device
*/
public boolean send(BluetoothDevice device) {
BluetoothCharacteristic characteristic = device.getCharacteristic(uuid);
if (characteristic != null) {
device.writeCharacteristic(characteristic, value);
return true;
}
return false;
}

public UUID getUUID() {
return uuid;
}

public byte[] getValue() {
return value;
}

@Override
public boolean equals(@Nullable Object o) {
if (o == this) {
return true;
}
if (!(o instanceof CharacteristicRequest other)) {
return false;
}

return uuid.equals(other.uuid) && Arrays.equals(value, other.value);
}

@Override
public int hashCode() {
return Objects.hash(uuid, value);
}

@Override
public String toString() {
return uuid + ": " + HexUtils.bytesToHex(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.bluetooth.grundfosalpha.internal;

import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
import org.openhab.core.thing.ThingTypeUID;
Expand All @@ -26,11 +28,21 @@
public class GrundfosAlphaBindingConstants {

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

public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ALPHA3, THING_TYPE_MI401);

// List of configuration parameters
public static final String CONFIGURATION_REFRESH_INTERVAL = "refreshInterval";

// 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";
public static final String CHANNEL_FLOW_RATE = "flow-rate";
public static final String CHANNEL_PUMP_HEAD = "pump-head";
public static final String CHANNEL_BATTERY_LEVEL = "battery-level";
public static final String CHANNEL_PUMP_TEMPERATURE = "pump-temperature";
public static final String CHANNEL_VOLTAGE_AC = "voltage-ac";
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_MOTOR_SPEED = "motor-speed";
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

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

import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bluetooth.grundfosalpha.internal.handler.GrundfosAlpha3Handler;
import org.openhab.binding.bluetooth.grundfosalpha.internal.handler.GrundfosAlphaReaderHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
Expand All @@ -35,8 +35,6 @@
@Component(configurationPid = "binding.bluetooth.grundfosalpha", service = ThingHandlerFactory.class)
public class GrundfosAlphaHandlerFactory extends BaseThingHandlerFactory {

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_MI401);

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
Expand All @@ -47,7 +45,9 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();

if (THING_TYPE_MI401.equals(thingTypeUID)) {
return new GrundfosAlphaHandler(thing);
return new GrundfosAlphaReaderHandler(thing);
} else if (THING_TYPE_ALPHA3.equals(thingTypeUID)) {
return new GrundfosAlpha3Handler(thing);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bluetooth.grundfosalpha.internal;
package org.openhab.binding.bluetooth.grundfosalpha.internal.discovery;

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

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;
Expand All @@ -34,7 +36,7 @@
* This discovery participant is able to recognize Grundfos Alpha devices and create discovery results for them.
*
* @author Markus Heberling - Initial contribution
*
* @author Jacob Laursen - Added support for Alpha3
*/
@NonNullByDefault
@Component
Expand All @@ -43,7 +45,7 @@ public class GrundfosAlphaDiscoveryParticipant implements BluetoothDiscoveryPart

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

@Override
Expand All @@ -54,15 +56,26 @@ public boolean requiresConnection(BluetoothDiscoveryDevice device) {
@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(":", ""));

if (name == null) {
return null;
}

ThingTypeUID thingTypeUID = switch (name) {
case "Alpha3" -> THING_TYPE_ALPHA3;
case "MI401" -> THING_TYPE_MI401;
default -> null;
};

if (thingTypeUID == null) {
return null;
}
return null;

return new ThingUID(thingTypeUID, device.getAdapter().getUID(),
device.getAddress().toString().toLowerCase().replace(":", ""));
}

@Override
Expand All @@ -71,18 +84,27 @@ public boolean requiresConnection(BluetoothDiscoveryDevice device) {
if (thingUID == null) {
return null;
}
String label = "Grundfos Alpha Reader MI401";

String label;
if ("MI401".equals(device.getName())) {
label = "Grundfos Alpha Reader MI401";
} else {
label = "Grundfos Alpha3";
}
Map<String, Object> properties = new HashMap<>();
properties.put(BluetoothBindingConstants.CONFIGURATION_ADDRESS, device.getAddress().toString());
properties.put(Thing.PROPERTY_VENDOR, "Grundfos");
properties.put(CONFIGURATION_ADDRESS, device.getAddress().toString());
String deviceName = device.getName();
if (deviceName != null) {
properties.put(Thing.PROPERTY_MODEL_ID, deviceName);
}
Integer txPower = device.getTxPower();
if (txPower != null) {
properties.put(BluetoothBindingConstants.PROPERTY_TXPOWER, Integer.toString(txPower));
properties.put(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();
.withRepresentationProperty(CONFIGURATION_ADDRESS).withBridge(device.getAdapter().getUID())
.withLabel(label).build();
}
}
Loading

0 comments on commit ef112e4

Please sign in to comment.