Skip to content

Commit 64eeeae

Browse files
authored
[grundfosalpha] Add support for Alpha3 pump (openhab#18187)
* Add support for Alpha3 * Add I18N support for discovery labels Signed-off-by: Jacob Laursen <[email protected]>
1 parent 0ccaa33 commit 64eeeae

19 files changed

+1342
-54
lines changed

bundles/org.openhab.binding.bluetooth.grundfosalpha/README.md

+42-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,44 @@
11
# GrundfosAlpha Binding
22

3-
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)
3+
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.
44

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

77
## Supported Things
88

9+
- `alpha3`: The Grundfos Alpha3 pump
910
- `mi401`: The Grundfos MI401 ALPHA Reader
1011

1112
## Discovery
1213

13-
All readers are auto-detected as soon as Bluetooth is configured in openHAB and the MI401 device is powered on.
14+
All pumps and readers are auto-detected as soon as Bluetooth is configured in openHAB and the devices are powered on.
1415

1516
## Thing Configuration
1617

18+
### `alpha3` Thing Configuration
19+
20+
| Name | Type | Description | Default | Required | Advanced |
21+
|-----------------|---------|---------------------------------------------------------|---------|----------|----------|
22+
| address | text | Bluetooth address in XX:XX:XX:XX:XX:XX format | N/A | yes | no |
23+
| refreshInterval | integer | Number of seconds between fetching values from the pump | 30 | no | yes |
24+
25+
### Pairing
26+
27+
After creating the Thing, the binding will attempt to connect to the pump.
28+
To start the pairing process, press the blue LED button on the pump.
29+
When the LED stops blinking and stays lit, the connection has been established, and the Thing should appear online.
30+
31+
However, the pump may still not be bonded correctly, which could prevent the binding from reconnecting after a disconnection.
32+
On Linux, you can take additional steps to fix this issue by manually pairing the pump:
33+
34+
```shell
35+
bluetoothctl pair XX:XX:XX:XX:XX:XX
36+
Attempting to pair with XX:XX:XX:XX:XX:XX
37+
[CHG] Device XX:XX:XX:XX:XX:XX Bonded: yes
38+
[CHG] Device XX:XX:XX:XX:XX:XX Paired: yes
39+
Pairing successful
40+
```
41+
1742
### `mi401` Thing Configuration
1843

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

2348
## Channels
2449

50+
### `alpha3` Channels
51+
52+
| Channel | Type | Read/Write | Description |
53+
|------------------|---------------------------|------------|------------------------------------|
54+
| rssi | Number:Power | R | Received Signal Strength Indicator |
55+
| flow-rate | Number:VolumetricFlowRate | R | The flow rate of the pump |
56+
| pump-head | Number:Length | R | The water head above the pump |
57+
| voltage-ac | Number:ElectricPotential | R | Current AC pump voltage |
58+
| power | Number:Power | R | Current pump power consumption |
59+
| motor-speed | Number:Frequency | R | Current rotation of the pump motor |
60+
61+
### `mi401` Channels
62+
2563
| Channel | Type | Read/Write | Description |
2664
|------------------|---------------------------|------------|------------------------------------|
27-
| rssi | Number | R | Received Signal Strength Indicator |
65+
| rssi | Number:Power | R | Received Signal Strength Indicator |
2866
| flow-rate | Number:VolumetricFlowRate | R | The flow rate of the pump |
2967
| pump-head | Number:Length | R | The water head above the pump |
3068
| pump-temperature | Number:Temperature | R | The temperature of the pump |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright (c) 2010-2025 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.bluetooth.grundfosalpha.internal;
14+
15+
import java.util.Arrays;
16+
import java.util.Objects;
17+
import java.util.UUID;
18+
19+
import org.eclipse.jdt.annotation.NonNullByDefault;
20+
import org.eclipse.jdt.annotation.Nullable;
21+
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
22+
import org.openhab.binding.bluetooth.BluetoothDevice;
23+
import org.openhab.binding.bluetooth.grundfosalpha.internal.protocol.MessageType;
24+
import org.openhab.core.util.HexUtils;
25+
26+
/**
27+
* This represents a request for writing characteristic to a Bluetooth device.
28+
*
29+
* This can be used for adding such requests to a queue.
30+
*
31+
* @author Jacob Laursen - Initial contribution
32+
*/
33+
@NonNullByDefault
34+
public class CharacteristicRequest {
35+
private UUID uuid;
36+
private byte[] value;
37+
38+
/**
39+
* Creates a new request object.
40+
*
41+
* @param uuid The UUID of the characteristic
42+
* @param messageType The {@link MessageType} containing the data to write
43+
*/
44+
public CharacteristicRequest(UUID uuid, MessageType messageType) {
45+
this.uuid = uuid;
46+
this.value = messageType.request();
47+
}
48+
49+
/**
50+
* Writes the characteristic to the provided {@link BluetoothDevice}.
51+
*
52+
* @param device The Bluetooth device
53+
* @return true if written, false if the characteristic is not found in the device
54+
*/
55+
public boolean send(BluetoothDevice device) {
56+
BluetoothCharacteristic characteristic = device.getCharacteristic(uuid);
57+
if (characteristic != null) {
58+
device.writeCharacteristic(characteristic, value);
59+
return true;
60+
}
61+
return false;
62+
}
63+
64+
public UUID getUUID() {
65+
return uuid;
66+
}
67+
68+
public byte[] getValue() {
69+
return value;
70+
}
71+
72+
@Override
73+
public boolean equals(@Nullable Object o) {
74+
if (o == this) {
75+
return true;
76+
}
77+
if (!(o instanceof CharacteristicRequest other)) {
78+
return false;
79+
}
80+
81+
return uuid.equals(other.uuid) && Arrays.equals(value, other.value);
82+
}
83+
84+
@Override
85+
public int hashCode() {
86+
return Objects.hash(uuid, value);
87+
}
88+
89+
@Override
90+
public String toString() {
91+
return uuid + ": " + HexUtils.bytesToHex(value);
92+
}
93+
}

bundles/org.openhab.binding.bluetooth.grundfosalpha/src/main/java/org/openhab/binding/bluetooth/grundfosalpha/internal/GrundfosAlphaBindingConstants.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
*/
1313
package org.openhab.binding.bluetooth.grundfosalpha.internal;
1414

15+
import java.util.Set;
16+
1517
import org.eclipse.jdt.annotation.NonNullByDefault;
1618
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
1719
import org.openhab.core.thing.ThingTypeUID;
@@ -26,11 +28,21 @@
2628
public class GrundfosAlphaBindingConstants {
2729

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

35+
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ALPHA3, THING_TYPE_MI401);
36+
37+
// List of configuration parameters
38+
public static final String CONFIGURATION_REFRESH_INTERVAL = "refreshInterval";
39+
3140
// List of all Channel ids
32-
public static final String CHANNEL_TYPE_FLOW_RATE = "flow-rate";
33-
public static final String CHANNEL_TYPE_PUMP_HEAD = "pump-head";
34-
public static final String CHANNEL_TYPE_BATTERY_LEVEL = "battery-level";
35-
public static final String CHANNEL_TYPE_PUMP_TEMPERATUR = "pump-temperature";
41+
public static final String CHANNEL_FLOW_RATE = "flow-rate";
42+
public static final String CHANNEL_PUMP_HEAD = "pump-head";
43+
public static final String CHANNEL_BATTERY_LEVEL = "battery-level";
44+
public static final String CHANNEL_PUMP_TEMPERATURE = "pump-temperature";
45+
public static final String CHANNEL_VOLTAGE_AC = "voltage-ac";
46+
public static final String CHANNEL_POWER = "power";
47+
public static final String CHANNEL_MOTOR_SPEED = "motor-speed";
3648
}

bundles/org.openhab.binding.bluetooth.grundfosalpha/src/main/java/org/openhab/binding/bluetooth/grundfosalpha/internal/GrundfosAlphaHandlerFactory.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414

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

17-
import java.util.Set;
18-
1917
import org.eclipse.jdt.annotation.NonNullByDefault;
2018
import org.eclipse.jdt.annotation.Nullable;
19+
import org.openhab.binding.bluetooth.grundfosalpha.internal.handler.GrundfosAlpha3Handler;
20+
import org.openhab.binding.bluetooth.grundfosalpha.internal.handler.GrundfosAlphaReaderHandler;
2121
import org.openhab.core.thing.Thing;
2222
import org.openhab.core.thing.ThingTypeUID;
2323
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
@@ -35,8 +35,6 @@
3535
@Component(configurationPid = "binding.bluetooth.grundfosalpha", service = ThingHandlerFactory.class)
3636
public class GrundfosAlphaHandlerFactory extends BaseThingHandlerFactory {
3737

38-
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_MI401);
39-
4038
@Override
4139
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
4240
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
@@ -47,7 +45,9 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
4745
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
4846

4947
if (THING_TYPE_MI401.equals(thingTypeUID)) {
50-
return new GrundfosAlphaHandler(thing);
48+
return new GrundfosAlphaReaderHandler(thing);
49+
} else if (THING_TYPE_ALPHA3.equals(thingTypeUID)) {
50+
return new GrundfosAlpha3Handler(thing);
5151
}
5252

5353
return null;
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,53 @@
1010
*
1111
* SPDX-License-Identifier: EPL-2.0
1212
*/
13-
package org.openhab.binding.bluetooth.grundfosalpha.internal;
13+
package org.openhab.binding.bluetooth.grundfosalpha.internal.discovery;
14+
15+
import static org.openhab.binding.bluetooth.BluetoothBindingConstants.*;
16+
import static org.openhab.binding.bluetooth.grundfosalpha.internal.GrundfosAlphaBindingConstants.*;
1417

1518
import java.util.HashMap;
1619
import java.util.Map;
1720
import java.util.Set;
1821

1922
import org.eclipse.jdt.annotation.NonNullByDefault;
2023
import org.eclipse.jdt.annotation.Nullable;
21-
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
2224
import org.openhab.binding.bluetooth.discovery.BluetoothDiscoveryDevice;
2325
import org.openhab.binding.bluetooth.discovery.BluetoothDiscoveryParticipant;
2426
import org.openhab.core.config.discovery.DiscoveryResult;
2527
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
28+
import org.openhab.core.i18n.TranslationProvider;
2629
import org.openhab.core.thing.Thing;
2730
import org.openhab.core.thing.ThingTypeUID;
2831
import org.openhab.core.thing.ThingUID;
32+
import org.osgi.framework.FrameworkUtil;
33+
import org.osgi.service.component.annotations.Activate;
2934
import org.osgi.service.component.annotations.Component;
35+
import org.osgi.service.component.annotations.Reference;
3036
import org.slf4j.Logger;
3137
import org.slf4j.LoggerFactory;
3238

3339
/**
3440
* This discovery participant is able to recognize Grundfos Alpha devices and create discovery results for them.
3541
*
3642
* @author Markus Heberling - Initial contribution
37-
*
43+
* @author Jacob Laursen - Added support for Alpha3
3844
*/
3945
@NonNullByDefault
4046
@Component
4147
public class GrundfosAlphaDiscoveryParticipant implements BluetoothDiscoveryParticipant {
4248
private final Logger logger = LoggerFactory.getLogger(GrundfosAlphaDiscoveryParticipant.class);
4349

50+
private final TranslationProvider translationProvider;
51+
52+
@Activate
53+
public GrundfosAlphaDiscoveryParticipant(final @Reference TranslationProvider translationProvider) {
54+
this.translationProvider = translationProvider;
55+
}
56+
4457
@Override
4558
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
46-
return Set.of(GrundfosAlphaBindingConstants.THING_TYPE_MI401);
59+
return SUPPORTED_THING_TYPES_UIDS;
4760
}
4861

4962
@Override
@@ -54,15 +67,26 @@ public boolean requiresConnection(BluetoothDiscoveryDevice device) {
5467
@Override
5568
public @Nullable ThingUID getThingUID(BluetoothDiscoveryDevice device) {
5669
Integer manufacturerId = device.getManufacturerId();
57-
@Nullable
5870
String name = device.getName();
5971
logger.debug("Discovered device {} with manufacturerId {} and name {}", device.getAddress(), manufacturerId,
6072
name);
61-
if ("MI401".equals(name)) {
62-
return new ThingUID(GrundfosAlphaBindingConstants.THING_TYPE_MI401, device.getAdapter().getUID(),
63-
device.getAddress().toString().toLowerCase().replace(":", ""));
73+
74+
if (name == null) {
75+
return null;
6476
}
65-
return null;
77+
78+
ThingTypeUID thingTypeUID = switch (name) {
79+
case "Alpha3" -> THING_TYPE_ALPHA3;
80+
case "MI401" -> THING_TYPE_MI401;
81+
default -> null;
82+
};
83+
84+
if (thingTypeUID == null) {
85+
return null;
86+
}
87+
88+
return new ThingUID(thingTypeUID, device.getAdapter().getUID(),
89+
device.getAddress().toString().toLowerCase().replace(":", ""));
6690
}
6791

6892
@Override
@@ -71,18 +95,25 @@ public boolean requiresConnection(BluetoothDiscoveryDevice device) {
7195
if (thingUID == null) {
7296
return null;
7397
}
74-
String label = "Grundfos Alpha Reader MI401";
98+
99+
String thingID = thingUID.getAsString().split(ThingUID.SEPARATOR)[1];
100+
String label = translationProvider.getText(FrameworkUtil.getBundle(getClass()),
101+
"discovery.%s.label".formatted(thingID), null, null);
102+
75103
Map<String, Object> properties = new HashMap<>();
76-
properties.put(BluetoothBindingConstants.CONFIGURATION_ADDRESS, device.getAddress().toString());
77-
properties.put(Thing.PROPERTY_VENDOR, "Grundfos");
104+
properties.put(CONFIGURATION_ADDRESS, device.getAddress().toString());
105+
String deviceName = device.getName();
106+
if (deviceName != null) {
107+
properties.put(Thing.PROPERTY_MODEL_ID, deviceName);
108+
}
78109
Integer txPower = device.getTxPower();
79110
if (txPower != null) {
80-
properties.put(BluetoothBindingConstants.PROPERTY_TXPOWER, Integer.toString(txPower));
111+
properties.put(PROPERTY_TXPOWER, Integer.toString(txPower));
81112
}
82113

83114
// Create the discovery result and add to the inbox
84115
return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
85-
.withRepresentationProperty(BluetoothBindingConstants.CONFIGURATION_ADDRESS)
86-
.withBridge(device.getAdapter().getUID()).withLabel(label).build();
116+
.withRepresentationProperty(CONFIGURATION_ADDRESS).withBridge(device.getAdapter().getUID())
117+
.withLabel(label).build();
87118
}
88119
}

0 commit comments

Comments
 (0)