Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Require `pymbrewclient>=1.11.0` to align with SensorType telemetry support

### Fixed
- Handle Device objects returned by the API without setup errors

### Added
- Map real-time `seconds_until_next_action` to `process_estimate_remaining_seconds`
- Add real-time diagnostic sensors for Peltier fan power and ESP core temperature

### Added
- Initial release of MiniBrew Home Assistant integration
- Support for MiniBrew Craft devices
Expand Down
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A Home Assistant custom component for integrating MiniBrew Craft and Keg devices

## Overview

This integration allows you to monitor and control your MiniBrew brewing devices through Home Assistant. It supports both MiniBrew Craft (brewing devices) and MiniBrew Keg (dispensing devices), providing real-time monitoring of temperatures, brew stages, and device status.
This integration allows you to monitor and control your MiniBrew brewing devices through Home Assistant. It supports both MiniBrew Craft (brewing devices) and MiniBrew Keg (dispensing devices), providing real-time monitoring of temperatures, process phases, and device status.

**IMPORTANT:** A MiniBrew Pro subscription is required for this integration to function. The integration uses the MiniBrew API which requires an active Pro subscription to access device data and control features.

Expand All @@ -15,23 +15,35 @@ This integration allows you to monitor and control your MiniBrew brewing devices
### Craft Device Sensors
- **Current Temperature** - Real-time temperature monitoring
- **Target Temperature** - Configured target temperature
- **Brew Stage** - Current brewing stage
- **Time in Stage** - Duration in current brewing stage
- **Current Stage** - Active stage information
- **Process Phase** - Current phase details from runtime telemetry
- **Online Status** - Device connectivity status
- **Last Time Online** - Timestamp of the last known online heartbeat (diagnostic)
- **Is Updating** - Firmware update status
- **Needs Cleaning** - Cleaning reminder indicator
- **User Action Required** - Notifications for required user actions
- **Next Action** - Timestamp for the next required user action
- **Session ID** - Active brew session identifier (diagnostic)
- **Brew Session Started** - Session start timestamp from MiniBrew session metadata (diagnostic)
- **ESP Core Temperature** *(real-time MQTT)* - Internal controller temperature

### Keg Device Sensors
- **Current Temperature** - Real-time keg temperature
- **Target Temperature** - Configured serving temperature
- **Beer Name** - Currently loaded beer name
- **Beer Style** - Currently loaded beer style
- **Beer Name** - Currently loaded beer name (shows `Custom Fermentation` in custom fermentation mode, including inferred fermenting/primary sessions with unknown beer metadata)
- **Beer Style** - Currently loaded beer style (`N/A` in custom fermentation mode)
- **Online Status** - Device connectivity status
- **Last Time Online** - Timestamp of the last known online heartbeat (diagnostic)
- **Is Updating** - Firmware update status
- **Needs Cleaning** - Cleaning reminder indicator
- **Action Required** - Notifications for required user actions
- **Next Action** - Timestamp for the next required user action
- **Session ID** - Active brew session identifier (diagnostic)
- **Brew Session Started** - Session start timestamp from MiniBrew session metadata (diagnostic)
- **Temp Control Power** *(real-time MQTT)* - Peltier heating/cooling power (%)
- **Fan Duty** *(real-time MQTT)* - Peltier fan output (%)
- **ESP Core Temperature** *(real-time MQTT)* - Internal controller temperature

## Installation

Expand Down Expand Up @@ -67,7 +79,7 @@ This integration allows you to monitor and control your MiniBrew brewing devices

After adding the integration, you can configure additional options:

- **Refresh Interval**: How often to poll the MiniBrew API for updates (default: 60 seconds)
- **Real-time discovery interval**: How often to refresh discovery and fallback data from REST while MQTT drives live telemetry (default: 300 seconds)

To access options:
1. Go to **Settings** → **Devices & Services**
Expand All @@ -79,7 +91,7 @@ To access options:
- Home Assistant 2023.1 or newer
- MiniBrew account with registered devices
- **MiniBrew Pro subscription** (required for API access)
- [`pymbrewclient>=1.0.10`](https://github.com/stuartp44/pymbrewclient) (automatically installed)
- [`pymbrewclient>=1.11.0`](https://github.com/stuartp44/pymbrewclient/releases/tag/v1.11.0) (automatically installed)

## Dependencies

Expand Down
Empty file.
22 changes: 21 additions & 1 deletion custom_components/minibrew/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@


_LOGGER = logging.getLogger(__name__)
_DISPLAY_TITLE = "MiniBrew Pro"
_LEGACY_TITLES = {"Minibrew", "Minibrew Pro", "MiniBrew"}

CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)

Expand All @@ -17,22 +19,40 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up Minibrew from a config entry."""
if config_entry.title in _LEGACY_TITLES:
hass.config_entries.async_update_entry(config_entry, title=_DISPLAY_TITLE)
minibrew_username = config_entry.data["username"]
minibrew_password = config_entry.data["password"]

try:
minibrew_client = BreweryClient(username=minibrew_username, password=minibrew_password)
_LOGGER.debug(f"Minibrew initialized")
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config_entry.entry_id] = minibrew_client
hass.data[DOMAIN][config_entry.entry_id] = {"client": minibrew_client}
except Exception as ex:
_LOGGER.error("Could not connect to Minibrew: %s", ex)
raise ConfigEntryNotReady from ex

# Reload the entry when options change (e.g. toggling real-time updates).
config_entry.async_on_unload(config_entry.add_update_listener(async_update_options))

await hass.config_entries.async_forward_entry_setups(config_entry, ["sensor"])
return True

async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Reload the config entry when its options are updated."""
await hass.config_entries.async_reload(config_entry.entry_id)

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
# Stop the real-time MQTT stream, if one was started.
store = hass.data.get(DOMAIN, {}).get(entry.entry_id)
if store:
coordinator = store.get("coordinator")
if coordinator is not None and getattr(coordinator, "realtime", None) is not None:
await coordinator.realtime.async_stop()

unload_ok = await hass.config_entries.async_unload_platforms(entry, ["sensor"])
if unload_ok:
hass.data.get(DOMAIN, {}).pop(entry.entry_id, None)
return unload_ok
84 changes: 78 additions & 6 deletions custom_components/minibrew/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import logging
import requests
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from dataclasses import asdict

from .const import DOMAIN
from pymbrewclient import BreweryClient, Device
from .const import (
CONF_REALTIME_POLL_INTERVAL,
DEFAULT_REALTIME_POLL_INTERVAL,
DOMAIN,
)
from pymbrewclient import BreweryClient

_LOGGER = logging.getLogger(__name__)

Expand All @@ -20,6 +24,22 @@ class PymbrewClientConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):

VERSION = 1


@staticmethod
def _is_auth_error(err: Exception) -> bool:
"""Return True when an exception indicates invalid credentials."""
response = getattr(err, "response", None)
status_code = getattr(response, "status_code", None)
if status_code in (401, 403):
return True
message = str(err).lower()
return (
"401" in message
or "403" in message
or "unauthorized" in message
or "forbidden" in message
)

async def async_step_user(self, user_input=None):
"""Handle the initial step for config flow."""
if user_input is not None:
Expand All @@ -42,13 +62,17 @@ async def _handle_user_input(self, user_input: dict) -> FlowResult:
return self._show_user_form(errors={"base": "no_devices_found"})
else:
return self.async_create_entry(
title="Minibrew Pro",
title="MiniBrew Pro",
data={
"username": username,
"password": password,
},
)

except requests.exceptions.HTTPError as err:
if self._is_auth_error(err):
return self._show_user_form(errors={"base": "invalid_auth"})
return self._show_user_form(errors={"base": "cannot_connect"})
except ConnectionError:
return self._show_user_form(errors={"base": "cannot_connect"})
except Exception as err:
Expand Down Expand Up @@ -76,6 +100,51 @@ def _is_existing_entry(self, unique_id: str) -> bool:
def async_get_options_flow(config_entry):
return PymbrewClientOptionsFlowHandler()


async def async_step_reauth(self, entry_data):
"""Start reauthentication flow when credentials are invalid."""
return await self.async_step_reauth_confirm()

async def async_step_reauth_confirm(self, user_input=None):
"""Confirm reauthentication by validating and storing new credentials."""
errors = {}
if user_input is not None:
username = user_input["username"]
password = user_input["password"]
try:
client = BreweryClient(username, password)
await self.hass.async_add_executor_job(client.get_brewery_overview)

entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
if entry is None:
return self.async_abort(reason="unknown_error")

updated_data = dict(entry.data)
updated_data.update({"username": username, "password": password})
self.hass.config_entries.async_update_entry(
entry,
title="MiniBrew Pro",
data=updated_data,
)
await self.hass.config_entries.async_reload(entry.entry_id)
return self.async_abort(reason="reauth_successful")
except requests.exceptions.HTTPError as err:
if self._is_auth_error(err):
errors["base"] = "invalid_auth"
else:
errors["base"] = "cannot_connect"
except ConnectionError:
errors["base"] = "cannot_connect"
except Exception as err: # noqa: BLE001
_LOGGER.error("Unexpected reauth error: %s", err)
errors["base"] = "unknown_error"

return self.async_show_form(
step_id="reauth_confirm",
data_schema=CONFIG_SCHEMA,
errors=errors,
)

class PymbrewClientOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle options flow for PymbrewClient."""

Expand All @@ -84,9 +153,12 @@ async def async_step_init(self, user_input=None):
if user_input is not None:
return self.async_create_entry(title="", data=user_input)

# Default value from existing options or fallback to 60
options = self.config_entry.options
options_schema = vol.Schema({
vol.Optional("refresh_interval", default=self.config_entry.options.get("refresh_interval", 60)): int,
vol.Optional(
CONF_REALTIME_POLL_INTERVAL,
default=options.get(CONF_REALTIME_POLL_INTERVAL, DEFAULT_REALTIME_POLL_INTERVAL),
): int,
})

return self.async_show_form(
Expand Down
12 changes: 11 additions & 1 deletion custom_components/minibrew/const.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
DOMAIN = "minibrew"
MANUFACTURER = "MiniBrew"
MANUFACTURER = "MiniBrew"

# Options
CONF_REFRESH_INTERVAL = "refresh_interval"
CONF_ENABLE_REALTIME = "enable_realtime"
CONF_REALTIME_POLL_INTERVAL = "realtime_poll_interval"

# Defaults
DEFAULT_REFRESH_INTERVAL = 60
DEFAULT_ENABLE_REALTIME = True
DEFAULT_REALTIME_POLL_INTERVAL = 300
4 changes: 2 additions & 2 deletions custom_components/minibrew/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"domain": "minibrew",
"name": "Minibrew",
"name": "MiniBrew",
"codeowners": [
"@stuartp44"
],
Expand All @@ -11,7 +11,7 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/stuartp44/hambrewclient/issues",
"requirements": [
"pymbrewclient>=1.8.1"
"pymbrewclient>=1.11.0"
],
"version": "0.8.0"
}
Loading