-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 056ac4d
Showing
16 changed files
with
657 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
name: Validate with hassfest | ||
|
||
on: | ||
push: | ||
pull_request: | ||
schedule: | ||
- cron: "0 0 * * *" | ||
|
||
jobs: | ||
validate: | ||
runs-on: "ubuntu-latest" | ||
steps: | ||
- uses: "actions/checkout@v3" | ||
- uses: home-assistant/actions/hassfest@master |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
name: Validate | ||
|
||
on: | ||
push: | ||
pull_request: | ||
schedule: | ||
- cron: "0 0 * * *" | ||
workflow_dispatch: | ||
|
||
jobs: | ||
validate-hacs: | ||
runs-on: "ubuntu-latest" | ||
steps: | ||
- uses: "actions/checkout@v3" | ||
- name: HACS validation | ||
uses: "hacs/action@main" | ||
with: | ||
category: "integration" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 Vasilis Koulis | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[![GitHub Release](https://img.shields.io/github/release/bkbilly/medisanabp_ble.svg?style=flat-square)](https://github.com/bkbilly/medisanabp_ble/releases) | ||
[![License](https://img.shields.io/github/license/bkbilly/medisanabp_ble.svg?style=flat-square)](LICENSE) | ||
[![hacs](https://img.shields.io/badge/HACS-default-orange.svg?style=flat-square)](https://hacs.xyz) | ||
|
||
|
||
# Medisana Blood Pressure BLE | ||
Integrates Bluetooth LE (https://www.medisana.com/en/Health-control/Blood-pressure-monitor/) to Home Assistant using active connection to get infromation from the sensors. | ||
|
||
Exposes the following sensors: | ||
- Battery | ||
- Diastolic pressure | ||
- Systolic pressure | ||
- Pulses | ||
- Measured date | ||
|
||
## Installation | ||
|
||
Easiest install is via [HACS](https://hacs.xyz/): | ||
|
||
[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=bkbilly&repository=medisanabp_ble&category=integration) | ||
|
||
`HACS -> Explore & Add Repositories -> Medisana Blood Pressure BLE` | ||
|
||
The device will be autodiscovered once the data are received by any bluetooth proxy. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
"""The MedisanaBP integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
import logging | ||
|
||
|
||
from homeassistant.components.bluetooth import ( | ||
BluetoothScanningMode, | ||
BluetoothServiceInfoBleak, | ||
async_ble_device_from_address, | ||
) | ||
from homeassistant.components.bluetooth.active_update_processor import ( | ||
ActiveBluetoothProcessorCoordinator, | ||
) | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import Platform | ||
from homeassistant.core import CoreState, HomeAssistant | ||
|
||
from .medisana_bp import MedisanaBPBluetoothDeviceData, SensorUpdate | ||
from .const import DOMAIN | ||
|
||
PLATFORMS: list[Platform] = [Platform.SENSOR] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Set up MedisanaBP BLE device from a config entry.""" | ||
address = entry.unique_id | ||
assert address is not None | ||
data = MedisanaBPBluetoothDeviceData() | ||
|
||
def _needs_poll( | ||
service_info: BluetoothServiceInfoBleak, last_poll: float | None | ||
) -> bool: | ||
# Only poll if hass is running, we need to poll, | ||
# and we actually have a way to connect to the device | ||
return ( | ||
hass.state is CoreState.running | ||
and data.poll_needed(service_info, last_poll) | ||
and bool( | ||
async_ble_device_from_address( | ||
hass, service_info.device.address, connectable=True | ||
) | ||
) | ||
) | ||
|
||
async def _async_poll(service_info: BluetoothServiceInfoBleak) -> SensorUpdate: | ||
# BluetoothServiceInfoBleak is defined in HA, otherwise would just pass it | ||
# directly to the elissabp code | ||
# Make sure the device we have is one that we can connect with | ||
# in case its coming from a passive scanner | ||
if service_info.connectable: | ||
connectable_device = service_info.device | ||
elif device := async_ble_device_from_address( | ||
hass, service_info.device.address, True | ||
): | ||
connectable_device = device | ||
else: | ||
# We have no bluetooth controller that is in range of | ||
# the device to poll it | ||
raise RuntimeError( | ||
f"No connectable device found for {service_info.device.address}" | ||
) | ||
return await data.async_poll(connectable_device) | ||
|
||
coordinator = hass.data.setdefault(DOMAIN, {})[ | ||
entry.entry_id | ||
] = ActiveBluetoothProcessorCoordinator( | ||
hass, | ||
_LOGGER, | ||
address=address, | ||
mode=BluetoothScanningMode.PASSIVE, | ||
update_method=data.update, | ||
needs_poll_method=_needs_poll, | ||
poll_method=_async_poll, | ||
# We will take advertisements from non-connectable devices | ||
# since we will trade the BLEDevice for a connectable one | ||
# if we need to poll it | ||
connectable=False, | ||
) | ||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
entry.async_on_unload( | ||
# only start after all platforms have had a chance to subscribe | ||
coordinator.async_start() | ||
) | ||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): | ||
hass.data[DOMAIN].pop(entry.entry_id) | ||
|
||
return unload_ok |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
"""Config flow for MedisanaBP BLE integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from typing import Any | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant.components.bluetooth import ( | ||
BluetoothServiceInfoBleak, | ||
async_discovered_service_info, | ||
) | ||
from homeassistant.config_entries import ConfigFlow | ||
from homeassistant.data_entry_flow import FlowResult | ||
from homeassistant.const import CONF_ADDRESS | ||
|
||
from .medisana_bp import MedisanaBPBluetoothDeviceData | ||
from .const import DOMAIN | ||
|
||
|
||
class MedisanaBPConfigFlow(ConfigFlow, domain=DOMAIN): | ||
"""Handle a config flow for MedisanaBP.""" | ||
|
||
VERSION = 1 | ||
|
||
def __init__(self) -> None: | ||
"""Initialize the config flow.""" | ||
self._discovery_info: BluetoothServiceInfoBleak | None = None | ||
self._discovered_device: MedisanaBPBluetoothDeviceData | None = None | ||
self._discovered_devices: dict[str, str] = {} | ||
|
||
async def async_step_bluetooth( | ||
self, discovery_info: BluetoothServiceInfoBleak | ||
) -> FlowResult: | ||
"""Handle the bluetooth discovery step.""" | ||
await self.async_set_unique_id(discovery_info.address) | ||
self._abort_if_unique_id_configured() | ||
device = MedisanaBPBluetoothDeviceData() | ||
if not device.supported(discovery_info): | ||
return self.async_abort(reason="not_supported") | ||
self._discovery_info = discovery_info | ||
self._discovered_device = device | ||
return await self.async_step_bluetooth_confirm() | ||
|
||
async def async_step_bluetooth_confirm( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> FlowResult: | ||
"""Confirm discovery.""" | ||
assert self._discovered_device is not None | ||
device = self._discovered_device | ||
assert self._discovery_info is not None | ||
discovery_info = self._discovery_info | ||
title = device.title or device.get_device_name() or discovery_info.name | ||
if user_input is not None: | ||
return self.async_create_entry(title=title, data={}) | ||
|
||
self._set_confirm_only() | ||
placeholders = {"name": title} | ||
self.context["title_placeholders"] = placeholders | ||
return self.async_show_form( | ||
step_id="bluetooth_confirm", description_placeholders=placeholders | ||
) | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> FlowResult: | ||
"""Handle the user step to pick discovered device.""" | ||
if user_input is not None: | ||
address = user_input[CONF_ADDRESS] | ||
await self.async_set_unique_id(address, raise_on_progress=False) | ||
self._abort_if_unique_id_configured() | ||
return self.async_create_entry( | ||
title=self._discovered_devices[address], data={} | ||
) | ||
|
||
current_addresses = self._async_current_ids() | ||
for discovery_info in async_discovered_service_info(self.hass, False): | ||
address = discovery_info.address | ||
if address in current_addresses or address in self._discovered_devices: | ||
continue | ||
device = MedisanaBPBluetoothDeviceData() | ||
if device.supported(discovery_info): | ||
self._discovered_devices[address] = ( | ||
device.title or device.get_device_name() or discovery_info.name | ||
) | ||
|
||
if not self._discovered_devices: | ||
return self.async_abort(reason="no_devices_found") | ||
|
||
return self.async_show_form( | ||
step_id="user", | ||
data_schema=vol.Schema( | ||
{vol.Required(CONF_ADDRESS): vol.In(self._discovered_devices)} | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
"""Constants for MedisanaBP BLE.""" | ||
|
||
DOMAIN = "medisanabp_ble" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
"""Constants for MedisanaBP BLE.""" | ||
|
||
from __future__ import annotations | ||
|
||
from .medisana_bp import DeviceKey | ||
|
||
from homeassistant.components.bluetooth.passive_update_processor import ( | ||
PassiveBluetoothEntityKey, | ||
) | ||
|
||
|
||
def device_key_to_bluetooth_entity_key( | ||
device_key: DeviceKey, | ||
) -> PassiveBluetoothEntityKey: | ||
"""Convert a device key to an entity key.""" | ||
return PassiveBluetoothEntityKey(device_key.key, device_key.device_id) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"domain": "medisanabp_ble", | ||
"name": "Medisana Blood Pressure BLE", | ||
"config_flow": true, | ||
"documentation": "https://github.com/bkbilly/medisanabp_ble", | ||
"issue_tracker": "https://github.com/bkbilly/medisanabp_ble/issues", | ||
"bluetooth": [ | ||
{ | ||
"manufacturer_id": 18498, | ||
"connectable": true | ||
} | ||
], | ||
"codeowners": ["@bkbilly"], | ||
"iot_class": "local_push", | ||
"dependencies": ["bluetooth_adapters"], | ||
"requirements": [], | ||
"version": "0.1.0" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
"""Parser for MedisanaBP BLE advertisements""" | ||
from __future__ import annotations | ||
|
||
from sensor_state_data import ( | ||
BinarySensorDeviceClass, | ||
BinarySensorValue, | ||
DeviceKey, | ||
SensorDescription, | ||
SensorDeviceClass, | ||
SensorDeviceInfo, | ||
SensorUpdate, | ||
SensorValue, | ||
Units, | ||
) | ||
|
||
from .parser import MedisanaBPBluetoothDeviceData, MedisanaBPSensor | ||
|
||
__version__ = "0.1.0" | ||
|
||
__all__ = [ | ||
"MedisanaBPSensor", | ||
"MedisanaBPBluetoothDeviceData", | ||
"BinarySensorDeviceClass", | ||
"DeviceKey", | ||
"SensorUpdate", | ||
"SensorDeviceClass", | ||
"SensorDeviceInfo", | ||
"SensorValue", | ||
"Units", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
"""Constants for MedisanaBP BLE parser""" | ||
|
||
CHARACTERISTIC_BLOOD_PRESSURE = "00002A35-0000-1000-8000-00805f9b34fb" | ||
CHARACTERISTIC_BATTERY = "00002A19-0000-1000-8000-00805F9B34FB" | ||
UPDATE_INTERVAL = 10 |
Oops, something went wrong.