Skip to content

Commit

Permalink
Merge pull request #1275 from custom-components/grundfos
Browse files Browse the repository at this point in the history
Add Grundfos ALPHA Reader MI401
  • Loading branch information
Ernst79 authored Dec 19, 2023
2 parents 5e6f359 + 08c1ee8 commit 9c6e9ba
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ This custom component for [Home Assistant](https://www.home-assistant.io) passiv
- b-parasite
- Ela
- Govee
- Grundfos
- HolyIOT
- Hörmann
- HHCC
Expand Down
5 changes: 5 additions & 0 deletions custom_components/ble_monitor/ble_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .bthome import parse_bthome
from .const import JAALEE_TYPES, TILT_TYPES
from .govee import parse_govee
from .grundfos import parse_grundfos
from .helpers import to_mac, to_unformatted_mac
from .hhcc import parse_hhcc
from .holyiot import parse_holyiot
Expand Down Expand Up @@ -401,6 +402,10 @@ def parse_advertisement(
# Govee H5051/H5071/H5072/H5075/H5074
sensor_data = parse_govee(self, man_spec_data, service_class_uuid16, local_name, mac, rssi)
break
elif comp_id == 0xF214 and data_len == 0x16:
# Grundfos
sensor_data = parse_grundfos(self, man_spec_data, mac, rssi)
break
elif comp_id == 0xFFFF and data_len == 0x1E:
# Kegtron
sensor_data = parse_kegtron(self, man_spec_data, mac, rssi)
Expand Down
66 changes: 66 additions & 0 deletions custom_components/ble_monitor/ble_parser/grundfos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Parser for Grundfos BLE advertisements"""
import logging
from struct import unpack

from .helpers import to_mac, to_unformatted_mac

_LOGGER = logging.getLogger(__name__)


PUMP_MODE_DICT = {
0: "Constant speed level 3",
1: "Constant speed level 2",
2: "Constant speed level 1",
3: "Autoadapt",
4: "Proportional pressure level 1",
5: "Proportional pressure level 2",
6: "Proportional pressure level 3",
7: "Constant differential pressure level 1",
8: "Constant differential pressure level 2",
9: "Constant differential pressure level 1",
}


def parse_grundfos(self, data, source_mac, rssi):
"""Grundfos parser"""
device_type = "MI401"
firmware = "Grundfos"
grundfos_mac = source_mac

xvalue = data[6:17]
(packet, bat_status, pump_id, flow, press, pump_mode, temp) = unpack(
"<BBHHhxBB", xvalue
)
pump_mode = PUMP_MODE_DICT[pump_mode]

result = {
"packet": packet,
"flow": round(flow / 6.5534, 1),
"water pressure": round(press / 32767, 3),
"temperature": temp,
"pump mode": pump_mode,
"pump id": pump_id,
"battery status": bat_status,
}

if self.report_unknown == "Grundfos":
_LOGGER.info(
"BLE ADV from UNKNOWN Grundfos DEVICE: RSSI: %s, MAC: %s, ADV: %s",
rssi,
to_mac(grundfos_mac),
data.hex()
)

# check for MAC presence in sensor whitelist, if needed
if self.discovery is False and grundfos_mac not in self.sensor_whitelist:
_LOGGER.debug("Discovery is disabled. MAC: %s is not whitelisted!", to_mac(grundfos_mac))
return None

result.update({
"rssi": rssi,
"mac": to_unformatted_mac(grundfos_mac),
"type": device_type,
"firmware": firmware,
"data": True
})
return result
38 changes: 38 additions & 0 deletions custom_components/ble_monitor/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,17 @@ class BLEMonitorBinarySensorEntityDescription(
suggested_display_precision=1,
state_class=SensorStateClass.MEASUREMENT,
),
BLEMonitorSensorEntityDescription(
key="water pressure",
sensor_class="MeasuringSensor",
update_behavior="Averaging",
name="ble water pressure",
unique_id="wp_",
native_unit_of_measurement=UnitOfPressure.BAR,
device_class=SensorDeviceClass.PRESSURE,
suggested_display_precision=2,
state_class=SensorStateClass.MEASUREMENT,
),
BLEMonitorSensorEntityDescription(
key="conductivity",
sensor_class="MeasuringSensor",
Expand Down Expand Up @@ -958,6 +969,18 @@ class BLEMonitorBinarySensorEntityDescription(
suggested_display_precision=3,
state_class=SensorStateClass.MEASUREMENT,
),
BLEMonitorSensorEntityDescription(
key="flow",
sensor_class="MeasuringSensor",
update_behavior="Averaging",
name="flow",
unique_id="flow_",
icon="mdi:wave",
native_unit_of_measurement="l/hr",
device_class=None,
suggested_display_precision=3,
state_class=SensorStateClass.MEASUREMENT,
),
BLEMonitorSensorEntityDescription(
key="gas",
sensor_class="MeasuringSensor",
Expand Down Expand Up @@ -1314,6 +1337,16 @@ class BLEMonitorBinarySensorEntityDescription(
native_unit_of_measurement=None,
device_class=None,
),
BLEMonitorSensorEntityDescription(
key="pump mode",
sensor_class="StateChangedSensor",
update_behavior="Instantly",
name="pump mode",
unique_id="pump_mode_",
icon="mdi:pump",
native_unit_of_measurement=None,
device_class=None,
),
BLEMonitorSensorEntityDescription(
key="timestamp",
sensor_class="StateChangedSensor",
Expand Down Expand Up @@ -1857,6 +1890,7 @@ class BLEMonitorBinarySensorEntityDescription(
'Amazfit Smart Scale' : 'Amazfit',
'Blustream' : 'Blustream',
'BTHome' : 'BTHome',
'MI401' : 'Grundfos',
'HHCCJCY10' : 'HHCC',
'HolyIOT BLE tracker' : 'HolyIOT',
'Supramatic E4 BS' : 'Hörmann',
Expand Down Expand Up @@ -1940,6 +1974,7 @@ class BLEMonitorBinarySensorEntityDescription(
"distance mm",
"duration",
"energy",
"flow",
"gas",
"gravity",
"gyroscope",
Expand All @@ -1956,6 +1991,7 @@ class BLEMonitorBinarySensorEntityDescription(
"power",
"pressure",
"pulse",
"pump mode",
"roll",
"rotation",
"speed",
Expand All @@ -1972,6 +2008,7 @@ class BLEMonitorBinarySensorEntityDescription(
"volume mL",
"volume flow rate",
"water",
"water pressure",
"weight",
]

Expand All @@ -1987,6 +2024,7 @@ class BLEMonitorBinarySensorEntityDescription(
"Blustream",
"BTHome",
"Govee",
"Grundfos",
"HHCC",
'HolyIOT',
"Hormann",
Expand Down
2 changes: 1 addition & 1 deletion custom_components/ble_monitor/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"btsocket>=0.2.0",
"pyric>=0.1.6.3"
],
"version": "12.7.0"
"version": "12.7.1"
}
18 changes: 12 additions & 6 deletions custom_components/ble_monitor/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ class BaseSensor(RestoreSensor, SensorEntity):
# | | |**humidity
# | |**moisture
# | |**pressure
# | |**water pressure
# | |**conductivity
# | |**illuminance
# | |**formaldehyde
Expand All @@ -327,11 +328,12 @@ class BaseSensor(RestoreSensor, SensorEntity):
# | |**TVOC
# | |**Air Quality Index
# | |**UV index
# | |**Volume
# | |**Volume mL
# | |**Volume flow rate
# | |**Gas
# | |**Water
# | |**volume
# | |**volume mL
# | |**volume flow rate
# | |**flow
# | |**gas
# | |**water
# |--InstantUpdateSensor (Class)
# | |**consumable
# | |**heart rate
Expand Down Expand Up @@ -360,6 +362,7 @@ class BaseSensor(RestoreSensor, SensorEntity):
# | | |**score
# | | |**air quality
# | | |**text
# | | |**pump mode
# | | |**timestamp
# | |--AccelerationSensor (Class)
# | | |**acceleration
Expand Down Expand Up @@ -864,7 +867,10 @@ def collect(self, data, period_cnt, batt_attr=None):
if self.enabled is False or state == data[self.entity_description.key]:
self.pending_update = False
return

if "pump id" in data:
self._extra_state_attributes["pump_id"] = data["pump id"]
if "battery status" in data:
self._extra_state_attributes["battery_status"] = data["battery status"]
super().collect(data, period_cnt, batt_attr)


Expand Down
26 changes: 26 additions & 0 deletions custom_components/ble_monitor/test/test_grundfos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""The tests for the Grundfos ble_parser."""
from ble_monitor.ble_parser import BleParser


class TestGrundfos:
"""Tests for the Grundfos parser"""
def test_grundfos_MI401(self):
"""Test Grundfos parser for ALPHA reader MI401."""
data_string = "043E2A020103009565F1164DAC1E06084D4934303116FF14F230017A03059884103E0F19070D0114FFFFFFFFC0"
data = bytes(bytearray.fromhex(data_string))
# pylint: disable=unused-variable
ble_parser = BleParser()
sensor_msg, tracker_msg = ble_parser.parse_raw_data(data)

assert sensor_msg["firmware"] == "Grundfos"
assert sensor_msg["type"] == "MI401"
assert sensor_msg["mac"] == "AC4D16F16595"
assert sensor_msg["packet"] == 122
assert sensor_msg["data"]
assert sensor_msg["temperature"] == 13
assert sensor_msg["flow"] == 645.2
assert sensor_msg["water pressure"] == 0.119
assert sensor_msg["pump mode"] == "Constant differential pressure level 1"
assert sensor_msg["pump id"] == 38917
assert sensor_msg["battery status"] == 3
assert sensor_msg["rssi"] == -64
29 changes: 29 additions & 0 deletions docs/_devices/Grundfos_MI401.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
manufacturer: Grundfos
name: ALPHA Reader MI401
model: MI401
image: Grundfos_MI401.png
physical_description: Rounded stick
broadcasted_properties:
- flow
- water pressure
- temperature
- battery state
- pump mode
- pump id
- rssi
broadcasted_property_notes:
- property: battery state
note: Battery state is an attribute of the pump mode sensor. The meaning of the state is unknown. If you have more information about the
meaning of the different states, let us know
- property: pump id
note: The pum id is an attribute of the pump mode sensor. The reader can send data of multiple pumps, each with its own id. In the current
implementation, we have assumed that only one pump is connected, which means that data of multiple pumps gets mixed in the sensor output.
If you have multipel pumps, modifications to the code will have to be made. Please open a new issue if you want to request multiple pump support.
broadcast_rate:
active_scan: true
encryption_key:
custom_firmware:
notes:
- actve scan needs to be enabled in the BLE Monitor settings for this sensor to work.
---
Binary file added docs/assets/images/Grundfos_MI401.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/config_params.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Data from sensors with other addresses will be ignored. Default value: True

**Report unknown sensors**

(`Off`, `Acconeer`, `Air Mentor`, `Amazfit`, `ATC`, `BlueMaestro`, `Blustream`, `Brifit`, `BTHome`, `Govee`, `HolyIOT`, `Hormann`, `HHCC`, `iNode`, `iBeacon`, `Jinou`, `Kegtron`, `Mi Scale`, `Mi Band`,`Mikrotik`, `Oras`, `Qingping`, `Relsib`, `rbaron`, `Ruuvitag`, `Sensirion`, `SensorPush`, `SmartDry`, `Switchbot`, `Teltonika`, `Thermoplus`, `Xiaogui`, `Xiaomi`, `Other` or `False`)(Optional) This option is needed primarily for those who want to request an implementation of device support that is not in the list of [supported sensors](devices). If you set this parameter to one of the sensor brands, then the component will log all messages from unknown devices of the specified brand to the Home Assistant log (`logger` component must be enabled at info level, see for instructions the [FAQ](faq#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation)). Using a sensor brand might not catch all BLE advertisements.
(`Off`, `Acconeer`, `Air Mentor`, `Amazfit`, `ATC`, `BlueMaestro`, `Blustream`, `Brifit`, `BTHome`, `Govee`, `Grundfos`, `HolyIOT`, `Hormann`, `HHCC`, `iNode`, `iBeacon`, `Jinou`, `Kegtron`, `Mi Scale`, `Mi Band`,`Mikrotik`, `Oras`, `Qingping`, `Relsib`, `rbaron`, `Ruuvitag`, `Sensirion`, `SensorPush`, `SmartDry`, `Switchbot`, `Teltonika`, `Thermoplus`, `Xiaogui`, `Xiaomi`, `Other` or `False`)(Optional) This option is needed primarily for those who want to request an implementation of device support that is not in the list of [supported sensors](devices). If you set this parameter to one of the sensor brands, then the component will log all messages from unknown devices of the specified brand to the Home Assistant log (`logger` component must be enabled at info level, see for instructions the [FAQ](faq#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation)). Using a sensor brand might not catch all BLE advertisements.

If you can't find the advertisements in this way, you can set this option to `Other`, which will result is all BLE advertisements being logged. You can also enable this option at device level. **Attention!** Enabling this option can lead to huge output to the Home Assistant log, especially when set to `Other`, do not enable it if you do not need it! If you know the MAC address of the sensor, its advised to set this option at device level. Details in the [FAQ](faq#my-sensor-from-the-xiaomi-ecosystem-is-not-in-the-list-of-supported-ones-how-to-request-implementation). Default value: `Off`

Expand Down
1 change: 1 addition & 0 deletions info.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ This custom component for [Home Assistant](https://www.home-assistant.io) passiv
- b-parasite
- Ela
- Govee
- Grundfos
- HolyIOT
- Hörmann
- HHCC
Expand Down

0 comments on commit 9c6e9ba

Please sign in to comment.