Homeassistant Custom Integration – Add new entities via options change in config_flow.py and use updated value in update method

I have a reduced version of my custom integration. The problem is that once I change the Configuration of the integration via the ItemWatcherOptionsFlowHandler the updated values are unfortunately not used in the cyclic _update method.

The problem ist that after the line Updating config data the following updates Updating the Item Watcher total value sensor. are still using the old value. (see logfile below)

Below the logfile I also added the files needed for the integration

Logfile of the issue:

DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_user: Item amount
DEBUG (MainThread) [custom_components.item_watcher] Set up Item Watcher from a config entry: b119b4c83f6da3e07fccff613a1ff807
DEBUG (MainThread) [custom_components.item_watcher.sensor] async_setup_entry: Setting up the sensor
DEBUG (MainThread) [custom_components.item_watcher.sensor] async_setup_entry: scan_interval=20
DEBUG (MainThread) [custom_components.item_watcher.sensor] async_setup_entry: items=['item a']
DEBUG (MainThread) [custom_components.item_watcher.sensor] async_setup_entry: item_amounts={'item a': 1.0}
DEBUG (MainThread) [custom_components.item_watcher.sensor] Construction of ItemTotalSensor
DEBUG (MainThread) [custom_components.item_watcher.sensor] Construction of ItemSensor
ERROR (MainThread) [custom_components.item_watcher.sensor] Failed to update Item item a value.
DEBUG (SyncWorker_2) [custom_components.item_watcher.sensor] Updating the Item Watcher total value sensor.
DEBUG (SyncWorker_2) [custom_components.item_watcher.sensor] Fetching item prices from API
DEBUG (SyncWorker_2) [custom_components.item_watcher.sensor] ITEM A (1.0): $100.00
DEBUG (SyncWorker_2) [custom_components.item_watcher.sensor] Total value: $100.00
INFO (SyncWorker_2) [custom_components.item_watcher.sensor] Updated Item Watcher total value: $100.00
INFO (SyncWorker_2) [custom_components.item_watcher.sensor] Updated Item item a value: $100.00
DEBUG (SyncWorker_3) [custom_components.item_watcher.sensor] Updating the Item Watcher total value sensor.
DEBUG (SyncWorker_3) [custom_components.item_watcher.sensor] Fetching item prices from API
DEBUG (SyncWorker_3) [custom_components.item_watcher.sensor] ITEM A (1.0): $100.00
DEBUG (SyncWorker_3) [custom_components.item_watcher.sensor] Total value: $100.00
INFO (SyncWorker_3) [custom_components.item_watcher.sensor] Updated Item Watcher total value: $100.00
INFO (SyncWorker_3) [custom_components.item_watcher.sensor] Updated Item item a value: $100.00
DEBUG (MainThread) [custom_components.item_watcher.config_flow] ItemWatcherOptionsFlowHandler: init
DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_init: Init options called with user_input: None
DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_init: Fetching available items
DEBUG (MainThread) [custom_components.item_watcher.helpers] Reuse available items from list
DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_init: Items available: ['item a']
DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_init: Showing form
DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_init: Init options called with user_input: {'item_list': ['item a', 'item b']}
DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_init: User input received: {'item_list': ['item a', 'item b']}
DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_item_amounts: Called with user_input: None
DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_item_amounts: Showing form for item amounts
DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_item_amounts: Called with user_input: {'item a': 1.0, 'item b': 2.0}
DEBUG (MainThread) [custom_components.item_watcher.config_flow] async_step_item_amounts: Updating config data with: {'item a': 1.0, 'item b': 2.0}
DEBUG (SyncWorker_3) [custom_components.item_watcher.sensor] Updating the Item Watcher total value sensor.
DEBUG (SyncWorker_3) [custom_components.item_watcher.sensor] Fetching item prices from API
DEBUG (SyncWorker_3) [custom_components.item_watcher.sensor] ITEM A (1.0): $100.00
DEBUG (SyncWorker_3) [custom_components.item_watcher.sensor] Total value: $100.00
INFO (SyncWorker_3) [custom_components.item_watcher.sensor] Updated Item Watcher total value: $100.00
INFO (SyncWorker_3) [custom_components.item_watcher.sensor] Updated Item item a value: $100.00
DEBUG (SyncWorker_1) [custom_components.item_watcher.sensor] Updating the Item Watcher total value sensor.
DEBUG (SyncWorker_1) [custom_components.item_watcher.sensor] Fetching item prices from API
DEBUG (SyncWorker_1) [custom_components.item_watcher.sensor] ITEM A (1.0): $100.00
DEBUG (SyncWorker_1) [custom_components.item_watcher.sensor] Total value: $100.00
INFO (SyncWorker_1) [custom_components.item_watcher.sensor] Updated Item Watcher total value: $100.00
INFO (SyncWorker_1) [custom_components.item_watcher.sensor] Updated Item item a value: $100.00

config_flow.py

"""Config flow for Item Watcher integration."""
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.core import callback
from homeassistant.helpers import selector
import homeassistant.helpers.config_validation as cv
from .const import (
    DOMAIN,
    CONF_ITEM_WATCHER_ACCESS_TOKEN,
    CONF_ITEM_LIST,
    CONF_SCAN_INTERVAL,
    CONF_ITEM_AMOUNTS
)
from .helpers import get_available_items  # Import the helper function
import logging

_LOGGER = logging.getLogger(__name__)

class ItemWatcherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for Item Watcher."""

    VERSION = 1

    def __init__(self):
        _LOGGER.debug("ItemWatcherConfigFlow: init")
        self.config_data = {}
        self.available_items = []

    @staticmethod
    @callback
    def async_get_options_flow(config_entry):
        return ItemWatcherOptionsFlowHandler(config_entry)

    async def async_step_user(self, user_input=None):
        """Handle the initial step."""
        _LOGGER.debug("async_step_user: Initial Step")
        if not self.available_items:
            self.available_items = await get_available_items(self.hass)

        if user_input is not None:
            self.config_data.update(user_input)
            return await self.async_step_item_amounts()

        data_schema = vol.Schema({
            vol.Required(CONF_ITEM_WATCHER_ACCESS_TOKEN): cv.string,
            vol.Optional(CONF_ITEM_LIST): selector.SelectSelector(
                selector.SelectSelectorConfig(
                    options=self.available_items,
                    multiple=True,
                    mode=selector.SelectSelectorMode.DROPDOWN,
                ),
            ),
            vol.Optional(CONF_SCAN_INTERVAL, default=60): vol.All(vol.Coerce(int), vol.Range(min=10))
        })

        return self.async_show_form(step_id="user", data_schema=data_schema)

    async def async_step_item_amounts(self, user_input=None):
        """Handle the step to specify amounts for each item."""
        _LOGGER.debug("async_step_user: Item amount")
        if user_input is not None:
            # Store item amounts in the config_data
            self.config_data.update({CONF_ITEM_AMOUNTS: user_input})
            return self.async_create_entry(title="", data=self.config_data)

        items = self.config_data.get(CONF_ITEM_LIST, [])
        item_amounts_schema = {vol.Required(item): vol.All(vol.Coerce(float), vol.Range(min=0)) for item in items}

        return self.async_show_form(step_id="item_amounts", data_schema=vol.Schema(item_amounts_schema))


class ItemWatcherOptionsFlowHandler(config_entries.OptionsFlow):
    """Handle options flow."""

    def __init__(self, config_entry):
        _LOGGER.debug("ItemWatcherOptionsFlowHandler: init")
        self.config_entry = config_entry
        self.config_data = dict(config_entry.data)  # Initialize with existing config data
        self.available_items = []

    async def async_step_init(self, user_input=None):
        """Manage the options."""
        _LOGGER.debug(f"async_step_init: Init options called with user_input: {user_input}")

        if not self.available_items:
            _LOGGER.debug("async_step_init: Fetching available items")
            self.available_items = await get_available_items(self.hass)

        if user_input is not None:
            _LOGGER.debug(f"async_step_init: User input received: {user_input}")
            self.config_data.update(user_input)
            return await self.async_step_item_amounts()

        items = self.config_data.get(CONF_ITEM_LIST, [])
        _LOGGER.debug(f"async_step_init: Items available: {items}")

        options_schema = vol.Schema({
            vol.Optional(CONF_ITEM_LIST, default=items): selector.SelectSelector(
                selector.SelectSelectorConfig(
                    options=self.available_items,
                    multiple=True,
                    mode=selector.SelectSelectorMode.DROPDOWN,
                ),
            ),
        })

        _LOGGER.debug("async_step_init: Showing form")
        return self.async_show_form(step_id="init", data_schema=options_schema)

    async def async_step_item_amounts(self, user_input=None):
        """Handle the step to specify amounts for each item in options."""
        _LOGGER.debug(f"async_step_item_amounts: Called with user_input: {user_input}")

        if user_input is not None:
            # Update item amounts in the config_data
            _LOGGER.debug(f"async_step_item_amounts: Updating config data with: {user_input}")
            self.config_data.update({CONF_ITEM_AMOUNTS: user_input})
            self.hass.config_entries.async_update_entry(self.config_entry, data=self.config_data)  # Update the entry with new data
            return self.async_create_entry(title="Item Watcher", data=self.config_data)

        items = self.config_data.get(CONF_ITEM_LIST, [])
        item_amounts = self.config_entry.data.get(CONF_ITEM_AMOUNTS, {})
        item_amounts_schema = {vol.Required(item, default=item_amounts.get(item, 1.0)): vol.All(vol.Coerce(float), vol.Range(min=0)) for item in items}

        _LOGGER.debug("async_step_item_amounts: Showing form for item amounts")
        return self.async_show_form(step_id="item_amounts", data_schema=vol.Schema(item_amounts_schema))

sensor.py

"""Item Watcher Integration for Home Assistant."""
import logging
import requests
from datetime import timedelta

from homeassistant.components.sensor import SensorEntity
from homeassistant.util import Throttle

from .const import (
    CONF_ITEM_WATCHER_ACCESS_TOKEN,
    CONF_ITEM_LIST,
    CONF_SCAN_INTERVAL,
    CONF_ITEM_AMOUNTS
)

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass, config_entry, async_add_entities):
    """Set up the Item Watcher sensors."""
    _LOGGER.debug("async_setup_entry: Setting up the sensor")
    items = config_entry.data.get(CONF_ITEM_LIST, [])
    scan_interval = config_entry.data.get(CONF_SCAN_INTERVAL, 60)
    item_amounts = config_entry.data.get(CONF_ITEM_AMOUNTS, {})
    _LOGGER.debug(f"async_setup_entry: scan_interval={scan_interval}")
    _LOGGER.debug(f"async_setup_entry: items={items}")
    _LOGGER.debug(f"async_setup_entry: item_amounts={item_amounts}")
    item_sensors = []
    total_sensor = ItemTotalSensor(hass, config_entry, items, item_amounts, item_sensors, timedelta(seconds=scan_interval))

    # Add individual token sensors with the correct amounts
    for item in items:
        item_amount = item_amounts.get(item, 1)  # Default to 1 if not specified
        item_sensor = ItemSensor(item, item_amount, total_sensor)
        item_sensors.append(item_sensor)

    # Add the sensors to Home Assistant
    async_add_entities([total_sensor] + item_sensors, True)

    # Handle config update listener
    async def _update_listener(hass, config_entry):
        _LOGGER.info(f"Updating Item sensors due to configuration change: {config_entry}")
        await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
        await async_setup_entry(hass, config_entry, async_add_entities)

    config_entry.async_on_unload(config_entry.add_update_listener(_update_listener))


class ItemTotalSensor(SensorEntity):
    """Representation of the total Item value sensor."""

    def __init__(self, hass, config_entry, items, item_amounts, item_sensors, scan_interval):
        """Initialize the sensor."""
        _LOGGER.debug("Construction of ItemTotalSensor")
        self._hass = hass
        self._config_entry = config_entry
        self._items = items
        self._item_amounts = item_amounts
        self._item_sensors = item_sensors
        self._state = None
        self._name = "Item Watcher Total"
        self._unit_of_measurement = "USD"
        self._prices = {}
        self.update = Throttle(scan_interval)(self._update)

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the sensor."""
        return self._state

    @property
    def unit_of_measurement(self):
        """Return the unit of measurement."""
        return self._unit_of_measurement

    def _update(self, now=None):
        """Fetch the value and calculate the total value."""
        _LOGGER.debug("Updating the Item Watcher total value sensor.")
        self._prices = self.get_item_prices()
        if self._prices:
            total_value = self.calculate_total_value()
            self._state = total_value
            _LOGGER.info(f"Updated Item Watcher total value: ${total_value:.2f}")
            for sensor in self._item_sensors:
                sensor.update_from_total_sensor()
        else:
            _LOGGER.error("Failed to update Item Watcher total value.")

    def get_item_prices(self):
        """Fetch the item prices from the API."""
        _LOGGER.debug("Fetching item prices from API")
        prices = dict()
        available_items = ["item a", "item b", "item c", "item d", "item e"]
        for item in available_items:
            prices[item] = 100
        return prices

    def calculate_total_value(self):
        """Calculate the total value based on queried prices and amounts."""
        total_value = 0
        for item in self._items:
            amount = self._item_amounts.get(item, 1)
            price = self._prices.get(item, 0)
            item_value = price * amount
            _LOGGER.debug(f"{item.upper()} ({amount}): ${item_value:.2f}")
            total_value += item_value
        _LOGGER.debug(f"Total value: ${total_value:.2f}")
        return total_value

    def update_config(self, entry):
        """Update the sensor with new configuration."""
        self._items = entry.data.get(CONF_ITEM_LIST, [])
        self._item_amounts = entry.data.get(CONF_ITEM_AMOUNTS, {})
        self.update()


class ItemSensor(SensorEntity):
    """Representation of an individual item sensor."""

    def __init__(self, item_name, amount, total_sensor):
        """Initialize the sensor."""
        _LOGGER.debug("Construction of ItemSensor")
        self._item_name = item_name
        self._amount = amount
        self._total_sensor = total_sensor
        self._state = None
        self._name = f"Item {item_name.upper()}"
        self._unit_of_measurement = "USD"
        self.update_from_total_sensor()

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the sensor."""
        return self._state

    @property
    def unit_of_measurement(self):
        """Return the unit of measurement."""
        return self._unit_of_measurement

    def update_from_total_sensor(self):
        """Update the item value based on the prices fetched by the total sensor."""
        prices = self._total_sensor._prices
        if prices:
            price = prices.get(self._item_name, 0)
            item_value = price * self._amount
            self._state = item_value
            _LOGGER.info(f"Updated Item {self._item_name} value: ${item_value:.2f}")
        else:
            _LOGGER.error(f"Failed to update Item {self._item_name} value.")

const.py

"""Constants for the Item Watcher integration."""

DOMAIN = "item_watcher"

CONF_ITEM_WATCHER_ACCESS_TOKEN = "item_api_access_token"
CONF_ITEM_LIST = "item_list"
CONF_SCAN_INTERVAL = "scan_interval"
CONF_ITEM_AMOUNTS = "item_amounts"

__init__.py

"""Item Watcher Integration for Home Assistant."""
from homeassistant.const import Platform
import logging
_LOGGER = logging.getLogger(__name__)

from .const import DOMAIN

PLATFORMS = [Platform.SENSOR]

async def async_unload_entry(hass, entry) -> bool:
    """Unload Item Watcher config entry."""
    _LOGGER.debug(f"Unload Item Watcher config entry: {entry.entry_id}")
    return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

async def async_setup_entry(hass, entry):
    """Set up Item Watcher from a config entry."""
    _LOGGER.debug(f"Set up Item Watcher from a config entry: {entry.entry_id}")
    hass.async_create_task(
        hass.config_entries.async_forward_entry_setup(entry, "sensor")
    )
    return True

manifest.json

{
  "domain": "item_watcher",
  "name": "Item Watcher",
  "version": "1.0.0",
  "documentation": "https://github.com/your_username/item_watcher",
  "dependencies": [],
  "codeowners": ["@your_username"],
  "requirements": ["requests>=2.25.1"],
  "config_flow": true
}

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật