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
}