From ed3997d21f6d8992104cd111ea317725440d6adc Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Fri, 11 Mar 2022 18:07:43 +0000 Subject: [PATCH 01/12] Force update accounts after doing a transfer #4 --- custom_components/sbanken/__init__.py | 5 +- custom_components/sbanken/sbanken_entities.py | 169 ++++++++++++++++++ custom_components/sbanken/sensor.py | 168 +---------------- custom_components/sbanken/services.py | 22 ++- 4 files changed, 193 insertions(+), 171 deletions(-) create mode 100644 custom_components/sbanken/sbanken_entities.py diff --git a/custom_components/sbanken/__init__.py b/custom_components/sbanken/__init__.py index 75262d6..2ba38d0 100644 --- a/custom_components/sbanken/__init__.py +++ b/custom_components/sbanken/__init__.py @@ -1,12 +1,10 @@ """The SBanken integration.""" from __future__ import annotations import logging -import voluptuous as vol -from .sbankenApi import SbankenApi -from .services import async_setup_services from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from .sbankenApi import SbankenApi from .const import DOMAIN, CONF_CLIENT_ID, CONF_SECRET, CONF_NUMBER_OF_TRANSACTIONS _LOGGER = logging.getLogger(__name__) @@ -23,7 +21,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: api = SbankenApi(entry.options.get(CONF_CLIENT_ID), entry.options.get(CONF_SECRET)) hass.data[DOMAIN]["api"] = api hass.data[DOMAIN]["listener"] = entry.add_update_listener(update_listener) - await async_setup_services(hass) return True diff --git a/custom_components/sbanken/sbanken_entities.py b/custom_components/sbanken/sbanken_entities.py new file mode 100644 index 0000000..258163b --- /dev/null +++ b/custom_components/sbanken/sbanken_entities.py @@ -0,0 +1,169 @@ +""" Sbanken entities""" +import logging +from datetime import datetime +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorStateClass, +) +from homeassistant.helpers.entity import Entity +from .sbankenApi import SbankenApi +from .const import ( + CONF_NUMBER_OF_TRANSACTIONS, + ATTR_ACCOUNT_ID, + ATTR_ACCOUNT_LIMIT, + ATTR_ACCOUNT_NUMBER, + ATTR_ACCOUNT_TYPE, + ATTR_AVAILABLE, + ATTR_BALANCE, + ATTR_LAST_UPDATE, + ATTR_NAME, + ATTR_PAYMENTS, + ATTR_STANDING_ORDERS, + ATTR_TRANSACTIONS, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + + +class SbankenAccountSensor(Entity): + """Representation of a Sensor.""" + + def __init__( + self, account, api: SbankenApi, options, hass: HomeAssistant, customer_info + ) -> None: + """Initialize the sensor.""" + self.api = api + self.number_of_transactions = options.get(CONF_NUMBER_OF_TRANSACTIONS) # TODO + self.hass = hass + self.customer_info = customer_info + self._account = account + self._transactions = [] + self._payments = [] + self._standing_orders = [] + self._state = float(account["available"]) + self._attr_state_class = SensorStateClass.MEASUREMENT + self._attr_device_class = SensorDeviceClass.MONETARY + self._attr_unique_id = self._account["accountNumber"] + self._attr_name = f"{self._account['name']} ({self._account['accountNumber']})" + self._attr_unit_of_measurement = "NOK" + self._attr_icon = "mdi:cash" + + @property + def device_info(self): + """Return the device_info of the device.""" + device_info = DeviceInfo( + identifiers={(DOMAIN, self.customer_info["customerId"])}, + name=f"Sbanken: {self.customer_info['firstName']} {self.customer_info['lastName']}", + manufacturer="Sbanken", + ) + return device_info + + @property + def state(self) -> float: + """Return the state of the sensor.""" + return self._state + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + return { + ATTR_ACCOUNT_ID: self._account["accountId"], + ATTR_AVAILABLE: self._account["available"], + ATTR_BALANCE: self._account["balance"], + ATTR_ACCOUNT_NUMBER: self._account["accountNumber"], + ATTR_NAME: self._account["name"], + ATTR_ACCOUNT_TYPE: self._account["accountType"], + ATTR_ACCOUNT_LIMIT: self._account["creditLimit"], + ATTR_LAST_UPDATE: datetime.now().strftime("%d/%m/%Y %H:%M:%S"), + ATTR_TRANSACTIONS: self._transactions, + ATTR_PAYMENTS: self._payments, + ATTR_STANDING_ORDERS: self._standing_orders, + } + + async def async_update(self): + """Fetch new state data for the sensor. + + This is the only method that should fetch new data for Home Assistant. + """ + account = await self.hass.async_add_executor_job( + self.api.get_account, self._account["accountId"] + ) + transactions = await self.hass.async_add_executor_job( + self.api.get_transactions, + self._account["accountId"], + self.number_of_transactions, + ) + payments = await self.hass.async_add_executor_job( + self.api.get_payments, + self._account["accountId"], + self.number_of_transactions, + ) + + standing_orders = await self.hass.async_add_executor_job( + self.api.get_standing_orders, self._account["accountId"] + ) + + self._transactions = transactions + self._payments = payments + self._account = account + self._standing_orders = standing_orders + self._state = float(account["available"]) + _LOGGER.debug(f"Updating Sbanken Sensors: {self._attr_name}") + + +class CustomerInformationSensor(Entity): + """Representation of a Sensor.""" + + def __init__(self, api: SbankenApi, hass: HomeAssistant, customer_info) -> None: + """Initialize the sensor.""" + self.api = api + self.hass = hass + self.customer_info = customer_info + self._state = ( + f"{self.customer_info['firstName']} {self.customer_info['lastName']}" + ) + self._attr_unique_id = self.customer_info["customerId"] + self._attr_name = "Customer information" + self._attr_icon = "mdi:information-outline" + + @property + def device_info(self): + """Return the device_info of the device.""" + device_info = DeviceInfo( + identifiers={(DOMAIN, self.customer_info["customerId"])}, + name=f"Sbanken: {self.customer_info['firstName']} {self.customer_info['lastName']}", + manufacturer="Sbanken", + ) + return device_info + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + return { + "customerId": self.customer_info["customerId"], + "firstName": self.customer_info["firstName"], + "lastName": self.customer_info["lastName"], + "emailAddress": self.customer_info["emailAddress"], + "dateOfBirth": self.customer_info["dateOfBirth"], + "postalAddress": self.customer_info["postalAddress"], + "streetAddress": self.customer_info["streetAddress"], + "phoneNumbers": self.customer_info["phoneNumbers"], + } + + async def async_update(self): + """Fetch new state data for the sensor. + + This is the only method that should fetch new data for Home Assistant. + """ + self.customer_info = await self.hass.async_add_executor_job( + self.api.get_customer_information + ) + _LOGGER.debug(f"Updating Sbanken Sensors: {self._attr_name}") diff --git a/custom_components/sbanken/sensor.py b/custom_components/sbanken/sensor.py index d760826..8dfe58b 100644 --- a/custom_components/sbanken/sensor.py +++ b/custom_components/sbanken/sensor.py @@ -1,32 +1,14 @@ """Sbanken accounts sensor.""" import logging -from datetime import datetime, timedelta +from datetime import timedelta from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.entity import DeviceInfo -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorStateClass, -) -from homeassistant.helpers.entity import Entity +from .services import async_setup_services from .sbankenApi import SbankenApi -from .const import ( - CONF_NUMBER_OF_TRANSACTIONS, - ATTR_ACCOUNT_ID, - ATTR_ACCOUNT_LIMIT, - ATTR_ACCOUNT_NUMBER, - ATTR_ACCOUNT_TYPE, - ATTR_AVAILABLE, - ATTR_BALANCE, - ATTR_LAST_UPDATE, - ATTR_NAME, - ATTR_PAYMENTS, - ATTR_STANDING_ORDERS, - ATTR_TRANSACTIONS, - DOMAIN, -) +from .sbanken_entities import SbankenAccountSensor, CustomerInformationSensor +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -46,144 +28,4 @@ async def async_setup_entry( ] sensors.append(CustomerInformationSensor(api, hass, customer_info)) async_add_entities(sensors, update_before_add=True) - - -class SbankenAccountSensor(Entity): - """Representation of a Sensor.""" - - def __init__( - self, account, api: SbankenApi, options, hass: HomeAssistant, customer_info - ) -> None: - """Initialize the sensor.""" - self.api = api - self.number_of_transactions = options.get(CONF_NUMBER_OF_TRANSACTIONS) # TODO - self.hass = hass - self.customer_info = customer_info - self._account = account - self._transactions = [] - self._payments = [] - self._standing_orders = [] - self._state = float(account["available"]) - self._attr_state_class = SensorStateClass.MEASUREMENT - self._attr_device_class = SensorDeviceClass.MONETARY - self._attr_unique_id = self._account["accountNumber"] - self._attr_name = f"{self._account['name']} ({self._account['accountNumber']})" - self._attr_unit_of_measurement = "NOK" - self._attr_icon = "mdi:cash" - - @property - def device_info(self): - """Return the device_info of the device.""" - device_info = DeviceInfo( - identifiers={(DOMAIN, self.customer_info["customerId"])}, - name=f"Sbanken: {self.customer_info['firstName']} {self.customer_info['lastName']}", - manufacturer="Sbanken", - ) - return device_info - - @property - def state(self) -> float: - """Return the state of the sensor.""" - return self._state - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return { - ATTR_ACCOUNT_ID: self._account["accountId"], - ATTR_AVAILABLE: self._account["available"], - ATTR_BALANCE: self._account["balance"], - ATTR_ACCOUNT_NUMBER: self._account["accountNumber"], - ATTR_NAME: self._account["name"], - ATTR_ACCOUNT_TYPE: self._account["accountType"], - ATTR_ACCOUNT_LIMIT: self._account["creditLimit"], - ATTR_LAST_UPDATE: datetime.now().strftime("%d/%m/%Y %H:%M:%S"), - ATTR_TRANSACTIONS: self._transactions, - ATTR_PAYMENTS: self._payments, - ATTR_STANDING_ORDERS: self._standing_orders, - } - - async def async_update(self): - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - account = await self.hass.async_add_executor_job( - self.api.get_account, self._account["accountId"] - ) - transactions = await self.hass.async_add_executor_job( - self.api.get_transactions, - self._account["accountId"], - self.number_of_transactions, - ) - payments = await self.hass.async_add_executor_job( - self.api.get_payments, - self._account["accountId"], - self.number_of_transactions, - ) - - standing_orders = await self.hass.async_add_executor_job( - self.api.get_standing_orders, self._account["accountId"] - ) - - self._transactions = transactions - self._payments = payments - self._account = account - self._standing_orders = standing_orders - self._state = float(account["available"]) - _LOGGER.debug(f"Updating Sbanken Sensors: {self._attr_name}") - - -class CustomerInformationSensor(Entity): - """Representation of a Sensor.""" - - def __init__(self, api: SbankenApi, hass: HomeAssistant, customer_info) -> None: - """Initialize the sensor.""" - self.api = api - self.hass = hass - self.customer_info = customer_info - self._state = ( - f"{self.customer_info['firstName']} {self.customer_info['lastName']}" - ) - self._attr_unique_id = self.customer_info["customerId"] - self._attr_name = "Customer information" - self._attr_icon = "mdi:information-outline" - - @property - def device_info(self): - """Return the device_info of the device.""" - device_info = DeviceInfo( - identifiers={(DOMAIN, self.customer_info["customerId"])}, - name=f"Sbanken: {self.customer_info['firstName']} {self.customer_info['lastName']}", - manufacturer="Sbanken", - ) - return device_info - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return { - "customerId": self.customer_info["customerId"], - "firstName": self.customer_info["firstName"], - "lastName": self.customer_info["lastName"], - "emailAddress": self.customer_info["emailAddress"], - "dateOfBirth": self.customer_info["dateOfBirth"], - "postalAddress": self.customer_info["postalAddress"], - "streetAddress": self.customer_info["streetAddress"], - "phoneNumbers": self.customer_info["phoneNumbers"], - } - - async def async_update(self): - """Fetch new state data for the sensor. - - This is the only method that should fetch new data for Home Assistant. - """ - self.customer_info = await self.hass.async_add_executor_job( - self.api.get_customer_information - ) - _LOGGER.debug(f"Updating Sbanken Sensors: {self._attr_name}") + await async_setup_services(hass, sensors) diff --git a/custom_components/sbanken/services.py b/custom_components/sbanken/services.py index d1addef..cd9a254 100644 --- a/custom_components/sbanken/services.py +++ b/custom_components/sbanken/services.py @@ -1,11 +1,14 @@ """ Sbanken services.""" +from datetime import timedelta import logging import voluptuous as vol -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, HassJob from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.event import async_track_time_interval, async_call_later from .sbankenApi import SbankenApi +from .sbanken_entities import SbankenAccountSensor from .const import DOMAIN, ATTR_ACCOUNT_ID _LOGGER = logging.getLogger(__name__) @@ -25,7 +28,9 @@ ) -async def async_setup_services(hass: HomeAssistant): +async def async_setup_services( + hass: HomeAssistant, entities: list[SbankenAccountSensor] +): """Setup services for Sbanken.""" async def transfer_service(call): # pylint: disable=possibly-unused-variable @@ -52,11 +57,20 @@ async def transfer_service(call): # pylint: disable=possibly-unused-variable to_account_id = hass.states.get(to_account_entity).attributes[ATTR_ACCOUNT_ID] api: SbankenApi = hass.data[DOMAIN]["api"] - transfer = await hass.async_add_executor_job( + await hass.async_add_executor_job( api.transfer, from_account_id, to_account_id, amount, message ) - return transfer + _LOGGER.debug("Schedule entity update in 5 seconds") + + async def refresh_callback(_): + for entity in entities: + if entity.entity_id == from_account_entity: + await entity.async_update() + if entity.entity_id == to_account_entity: + await entity.async_update() + + async_call_later(hass, timedelta(seconds=5), refresh_callback) hass.services.async_register( DOMAIN, "transfer", transfer_service, schema=SERVICE_TRANSFER_SCHEMA From 9dfe4ecf1df88c24a495d2f14c372dadd225069b Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Fri, 11 Mar 2022 20:16:54 +0000 Subject: [PATCH 02/12] Update account info --- README.md | 11 +++++++---- custom_components/sbanken/services.py | 24 ++++++++++++++++++++++++ custom_components/sbanken/services.yaml | 12 ++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8b8de37..241b3f2 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,18 @@ -[![home-assistant-sbanken](https://img.shields.io/github/release/toringer/home-assistant-sbanken.svg?1)](https://github.com/toringer/home-assistant-sbanken) +[![home-assistant-sbanken](https://img.shields.io/github/release/toringer/home-assistant-sbanken.svg?1)](https://github.com/toringer/home-assistant-sbanken) [![Validate with hassfest](https://github.com/toringer/home-assistant-sbanken/workflows/Validate%20with%20hassfest/badge.svg)](https://github.com/toringer/home-assistant-sbanken/actions/workflows/hassfest.yaml) [![HACS Validation](https://github.com/toringer/home-assistant-sbanken/actions/workflows/validate_hacs.yaml/badge.svg)](https://github.com/toringer/home-assistant-sbanken/actions/workflows/validate_hacs.yaml) [![Maintenance](https://img.shields.io/maintenance/yes/2022.svg)](https://github.com/toringer/home-assistant-sbanken) -[![home-assistant-sbanken_downloads](https://img.shields.io/github/downloads/toringer/home-assistant-sbanken/total)](https://github.com/toringer/home-assistant-sbanken) +[![home-assistant-sbanken_downloads](https://img.shields.io/github/downloads/toringer/home-assistant-sbanken/total)](https://github.com/toringer/home-assistant-sbanken) [![home-assistant-sbanken_downloads](https://img.shields.io/github/downloads/toringer/home-assistant-sbanken/latest/total)](https://github.com/toringer/home-assistant-sbanken) +# Sbanken sensor platform for Home Assistant +Get your Sbanken account information integrated into your Home Assistant. The Sbanken integration will create an entity for each of your bank accounts. The entity displays current available amount, transactions, payments and other information. +The integration adds two services -# Sbanken sensor platform for Home Assistant -Get your Sbanken account information integrated into your Home Assistant. The Sbanken integration will create an entity for each of your bank accounts. The entity displays current available amount, transactions, payments and other information. The integration also adds a service `sbanken.transfer` that allows you to transfer amounts between your accounts. +- `sbanken.transfer` to transfer amounts between your accounts. +- `sbanken.update_account` to force an account information update ![Header](https://github.com/toringer/home-assistant-sbanken/blob/master/accounts.png) diff --git a/custom_components/sbanken/services.py b/custom_components/sbanken/services.py index cd9a254..a908b4b 100644 --- a/custom_components/sbanken/services.py +++ b/custom_components/sbanken/services.py @@ -16,6 +16,7 @@ ATTR_FROM_ACCOUNT_ENTITY = "from_account_entity" ATTR_TO_ACCOUNT_ENTITY = "to_account_entity" ATTR_MESSAGE = "message" +ATTR_UPDATE_ACCOUNT_ENTITY = "update_account_entity" SERVICE_TRANSFER_SCHEMA = vol.Schema( @@ -27,6 +28,12 @@ } ) +SERVICE_UPDATE_ACCOUNT_SCHEMA = vol.Schema( + { + vol.Required(ATTR_UPDATE_ACCOUNT_ENTITY): cv.string, + } +) + async def async_setup_services( hass: HomeAssistant, entities: list[SbankenAccountSensor] @@ -67,11 +74,28 @@ async def refresh_callback(_): for entity in entities: if entity.entity_id == from_account_entity: await entity.async_update() + entity.async_schedule_update_ha_state() if entity.entity_id == to_account_entity: await entity.async_update() + entity.async_schedule_update_ha_state() async_call_later(hass, timedelta(seconds=5), refresh_callback) + async def update_account(call): # pylint: disable=possibly-unused-variable + """Execute a service to Sbanken.""" + _LOGGER.debug(f"Update account: {str(call.data)}") + + update_account_entity = call.data.get(ATTR_UPDATE_ACCOUNT_ENTITY) + + for entity in entities: + if entity.entity_id == update_account_entity: + await entity.async_update() + entity.async_schedule_update_ha_state() + break + hass.services.async_register( DOMAIN, "transfer", transfer_service, schema=SERVICE_TRANSFER_SCHEMA ) + hass.services.async_register( + DOMAIN, "update_account", update_account, schema=SERVICE_UPDATE_ACCOUNT_SCHEMA + ) diff --git a/custom_components/sbanken/services.yaml b/custom_components/sbanken/services.yaml index e013783..c0c9952 100644 --- a/custom_components/sbanken/services.yaml +++ b/custom_components/sbanken/services.yaml @@ -1,3 +1,15 @@ +update_account: + name: Update account + description: "Update account information." + fields: + update_account_entity: + name: Account + description: The account to update + example: 123456 + required: true + selector: + entity: + integration: sbanken transfer: name: "Transfer" description: "Transfer between accounts." From 93ddaabb4bc120b65f88ad12fed4d2683d714061 Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Fri, 11 Mar 2022 20:23:10 +0000 Subject: [PATCH 03/12] cleanup --- custom_components/sbanken/services.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/sbanken/services.py b/custom_components/sbanken/services.py index a908b4b..788a9e8 100644 --- a/custom_components/sbanken/services.py +++ b/custom_components/sbanken/services.py @@ -72,10 +72,10 @@ async def transfer_service(call): # pylint: disable=possibly-unused-variable async def refresh_callback(_): for entity in entities: - if entity.entity_id == from_account_entity: - await entity.async_update() - entity.async_schedule_update_ha_state() - if entity.entity_id == to_account_entity: + if ( + entity.entity_id == from_account_entity + or entity.entity_id == to_account_entity + ): await entity.async_update() entity.async_schedule_update_ha_state() From 59184c60e9f10d3310b706b1131fbb54c6000ce8 Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Sat, 12 Mar 2022 09:38:12 +0000 Subject: [PATCH 04/12] Limit service fields to device_class: monetary --- custom_components/sbanken/services.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/custom_components/sbanken/services.yaml b/custom_components/sbanken/services.yaml index c0c9952..e7ee493 100644 --- a/custom_components/sbanken/services.yaml +++ b/custom_components/sbanken/services.yaml @@ -10,6 +10,7 @@ update_account: selector: entity: integration: sbanken + device_class: monetary transfer: name: "Transfer" description: "Transfer between accounts." @@ -22,6 +23,7 @@ transfer: selector: entity: integration: sbanken + device_class: monetary to_account_entity: name: "To account" description: The account to transfer the amount to @@ -30,6 +32,7 @@ transfer: selector: entity: integration: sbanken + device_class: monetary amount: name: "Amount" description: The amount to transfer From 44d4adb224407d239ce90a947896c97c4d40be94 Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Sat, 12 Mar 2022 09:43:04 +0000 Subject: [PATCH 05/12] Added config url for device --- custom_components/sbanken/sbanken_entities.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/sbanken/sbanken_entities.py b/custom_components/sbanken/sbanken_entities.py index 258163b..caea0e9 100644 --- a/custom_components/sbanken/sbanken_entities.py +++ b/custom_components/sbanken/sbanken_entities.py @@ -58,6 +58,7 @@ def device_info(self): identifiers={(DOMAIN, self.customer_info["customerId"])}, name=f"Sbanken: {self.customer_info['firstName']} {self.customer_info['lastName']}", manufacturer="Sbanken", + configuration_url="https://sbanken.no/", ) return device_info @@ -136,6 +137,7 @@ def device_info(self): identifiers={(DOMAIN, self.customer_info["customerId"])}, name=f"Sbanken: {self.customer_info['firstName']} {self.customer_info['lastName']}", manufacturer="Sbanken", + configuration_url="https://sbanken.no/", ) return device_info From 0decaed70e57c63696fb9580b24057ac25452583 Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Sat, 12 Mar 2022 09:48:25 +0000 Subject: [PATCH 06/12] Moved number_of_transactions to constructor --- custom_components/sbanken/sbanken_entities.py | 10 +++++++--- custom_components/sbanken/sensor.py | 10 ++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/custom_components/sbanken/sbanken_entities.py b/custom_components/sbanken/sbanken_entities.py index caea0e9..5d100cb 100644 --- a/custom_components/sbanken/sbanken_entities.py +++ b/custom_components/sbanken/sbanken_entities.py @@ -10,7 +10,6 @@ from homeassistant.helpers.entity import Entity from .sbankenApi import SbankenApi from .const import ( - CONF_NUMBER_OF_TRANSACTIONS, ATTR_ACCOUNT_ID, ATTR_ACCOUNT_LIMIT, ATTR_ACCOUNT_NUMBER, @@ -32,11 +31,16 @@ class SbankenAccountSensor(Entity): """Representation of a Sensor.""" def __init__( - self, account, api: SbankenApi, options, hass: HomeAssistant, customer_info + self, + account, + api: SbankenApi, + number_of_transactions: int, + hass: HomeAssistant, + customer_info, ) -> None: """Initialize the sensor.""" self.api = api - self.number_of_transactions = options.get(CONF_NUMBER_OF_TRANSACTIONS) # TODO + self.number_of_transactions = number_of_transactions self.hass = hass self.customer_info = customer_info self._account = account diff --git a/custom_components/sbanken/sensor.py b/custom_components/sbanken/sensor.py index 8dfe58b..bedbc57 100644 --- a/custom_components/sbanken/sensor.py +++ b/custom_components/sbanken/sensor.py @@ -8,7 +8,7 @@ from .services import async_setup_services from .sbankenApi import SbankenApi from .sbanken_entities import SbankenAccountSensor, CustomerInformationSensor -from .const import DOMAIN +from .const import CONF_NUMBER_OF_TRANSACTIONS, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -23,7 +23,13 @@ async def async_setup_entry( accounts = await hass.async_add_executor_job(api.get_accounts) customer_info = await hass.async_add_executor_job(api.get_customer_information) sensors = [ - SbankenAccountSensor(account, api, entry.options, hass, customer_info) + SbankenAccountSensor( + account, + api, + entry.options.get(CONF_NUMBER_OF_TRANSACTIONS), + hass, + customer_info, + ) for account in accounts ] sensors.append(CustomerInformationSensor(api, hass, customer_info)) From 62e5a3a3d7e9169c417e7f7c70adaccf7338a749 Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Sat, 12 Mar 2022 09:56:10 +0000 Subject: [PATCH 07/12] Code cleanup --- custom_components/sbanken/__init__.py | 4 ++-- custom_components/sbanken/config_flow.py | 5 ++--- custom_components/sbanken/const.py | 6 ++++- custom_components/sbanken/manifest.json | 2 +- .../sbanken/{sbankenApi.py => sbanken_api.py} | 4 ++-- custom_components/sbanken/sbanken_entities.py | 2 +- custom_components/sbanken/sensor.py | 2 +- custom_components/sbanken/services.py | 22 ++++++++++--------- 8 files changed, 26 insertions(+), 21 deletions(-) rename custom_components/sbanken/{sbankenApi.py => sbanken_api.py} (98%) diff --git a/custom_components/sbanken/__init__.py b/custom_components/sbanken/__init__.py index 2ba38d0..071abe3 100644 --- a/custom_components/sbanken/__init__.py +++ b/custom_components/sbanken/__init__.py @@ -4,8 +4,8 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from .sbankenApi import SbankenApi -from .const import DOMAIN, CONF_CLIENT_ID, CONF_SECRET, CONF_NUMBER_OF_TRANSACTIONS +from .sbanken_api import SbankenApi +from .const import DOMAIN, CONF_CLIENT_ID, CONF_SECRET _LOGGER = logging.getLogger(__name__) PLATFORMS: list[Platform] = [Platform.SENSOR] diff --git a/custom_components/sbanken/config_flow.py b/custom_components/sbanken/config_flow.py index 4e5969e..da9dfdb 100644 --- a/custom_components/sbanken/config_flow.py +++ b/custom_components/sbanken/config_flow.py @@ -1,14 +1,13 @@ """Config flow for Sbanken integration.""" from __future__ import annotations import logging -import voluptuous as vol from typing import Any +import voluptuous as vol from homeassistant import config_entries from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResult -from homeassistant.exceptions import HomeAssistantError from homeassistant.core import callback -from .sbankenApi import SbankenApi +from .sbanken_api import SbankenApi from .const import ( DOMAIN, CONF_CLIENT_ID, diff --git a/custom_components/sbanken/const.py b/custom_components/sbanken/const.py index 4992e50..47e7efc 100644 --- a/custom_components/sbanken/const.py +++ b/custom_components/sbanken/const.py @@ -2,7 +2,6 @@ from homeassistant.exceptions import HomeAssistantError - DOMAIN = "sbanken" TITLE = "Sbanken" CONF_CLIENT_ID = "client_id" @@ -19,6 +18,11 @@ ATTR_TRANSACTIONS = "transactions" ATTR_PAYMENTS = "payments" ATTR_STANDING_ORDERS = "standing_orders" +ATTR_AMOUNT = "amount" +ATTR_FROM_ACCOUNT_ENTITY = "from_account_entity" +ATTR_TO_ACCOUNT_ENTITY = "to_account_entity" +ATTR_MESSAGE = "message" +ATTR_UPDATE_ACCOUNT_ENTITY = "update_account_entity" class InvalidAuth(HomeAssistantError): diff --git a/custom_components/sbanken/manifest.json b/custom_components/sbanken/manifest.json index a2fde3e..4521a0b 100644 --- a/custom_components/sbanken/manifest.json +++ b/custom_components/sbanken/manifest.json @@ -1,7 +1,7 @@ { "domain": "sbanken", "name": "Sbanken", - "version": "2.0.9", + "version": "2.1.0", "config_flow": true, "documentation": "https://github.com/toringer/home-assistant-sbanken", "issue_tracker": "https://github.com/toringer/home-assistant-sbanken/issues", diff --git a/custom_components/sbanken/sbankenApi.py b/custom_components/sbanken/sbanken_api.py similarity index 98% rename from custom_components/sbanken/sbankenApi.py rename to custom_components/sbanken/sbanken_api.py index a452dda..076bff3 100644 --- a/custom_components/sbanken/sbankenApi.py +++ b/custom_components/sbanken/sbanken_api.py @@ -25,7 +25,7 @@ def get_session(self) -> OAuth2Session: """Get session""" if ( self._session is None - or self._session.authorized is False + # or self._session.authorized is False or self._session_expires_at < datetime.now() ): _LOGGER.info("Create new session") @@ -81,7 +81,7 @@ def get_account(self, account_id: str): self.get_session() .get(f"https://publicapi.sbanken.no/apibeta/api/v2/Accounts/{account_id}") .json() - ) + ) # TODO catch and possible retry? if "isError" not in response: return response diff --git a/custom_components/sbanken/sbanken_entities.py b/custom_components/sbanken/sbanken_entities.py index 5d100cb..b3dc021 100644 --- a/custom_components/sbanken/sbanken_entities.py +++ b/custom_components/sbanken/sbanken_entities.py @@ -8,7 +8,7 @@ SensorStateClass, ) from homeassistant.helpers.entity import Entity -from .sbankenApi import SbankenApi +from .sbanken_api import SbankenApi from .const import ( ATTR_ACCOUNT_ID, ATTR_ACCOUNT_LIMIT, diff --git a/custom_components/sbanken/sensor.py b/custom_components/sbanken/sensor.py index bedbc57..7b8e674 100644 --- a/custom_components/sbanken/sensor.py +++ b/custom_components/sbanken/sensor.py @@ -6,7 +6,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from .services import async_setup_services -from .sbankenApi import SbankenApi +from .sbanken_api import SbankenApi from .sbanken_entities import SbankenAccountSensor, CustomerInformationSensor from .const import CONF_NUMBER_OF_TRANSACTIONS, DOMAIN diff --git a/custom_components/sbanken/services.py b/custom_components/sbanken/services.py index 788a9e8..4de1690 100644 --- a/custom_components/sbanken/services.py +++ b/custom_components/sbanken/services.py @@ -1,22 +1,24 @@ """ Sbanken services.""" from datetime import timedelta import logging - import voluptuous as vol -from homeassistant.core import HomeAssistant, HassJob +from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.event import async_track_time_interval, async_call_later -from .sbankenApi import SbankenApi +from homeassistant.helpers.event import async_call_later +from .sbanken_api import SbankenApi from .sbanken_entities import SbankenAccountSensor -from .const import DOMAIN, ATTR_ACCOUNT_ID +from .const import ( + DOMAIN, + ATTR_ACCOUNT_ID, + ATTR_FROM_ACCOUNT_ENTITY, + ATTR_TO_ACCOUNT_ENTITY, + ATTR_AMOUNT, + ATTR_MESSAGE, + ATTR_UPDATE_ACCOUNT_ENTITY, +) _LOGGER = logging.getLogger(__name__) -ATTR_AMOUNT = "amount" -ATTR_FROM_ACCOUNT_ENTITY = "from_account_entity" -ATTR_TO_ACCOUNT_ENTITY = "to_account_entity" -ATTR_MESSAGE = "message" -ATTR_UPDATE_ACCOUNT_ENTITY = "update_account_entity" SERVICE_TRANSFER_SCHEMA = vol.Schema( From 628df1b4bfa7a64f60d204974e120d6685f93015 Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Sat, 12 Mar 2022 10:04:06 +0000 Subject: [PATCH 08/12] Clenup debug logging --- custom_components/sbanken/services.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/custom_components/sbanken/services.py b/custom_components/sbanken/services.py index 4de1690..85de82c 100644 --- a/custom_components/sbanken/services.py +++ b/custom_components/sbanken/services.py @@ -44,12 +44,11 @@ async def async_setup_services( async def transfer_service(call): # pylint: disable=possibly-unused-variable """Execute a service to Sbanken.""" - _LOGGER.debug(f"Handle transfers: {str(call.data)}") - from_account_entity = call.data.get(ATTR_FROM_ACCOUNT_ENTITY) to_account_entity = call.data.get(ATTR_TO_ACCOUNT_ENTITY) amount = call.data.get(ATTR_AMOUNT) message = call.data.get(ATTR_MESSAGE) + _LOGGER.debug(f"Transfer amount: {str(amount)}") if ATTR_ACCOUNT_ID not in hass.states.get(from_account_entity).attributes: raise HomeAssistantError( @@ -85,9 +84,8 @@ async def refresh_callback(_): async def update_account(call): # pylint: disable=possibly-unused-variable """Execute a service to Sbanken.""" - _LOGGER.debug(f"Update account: {str(call.data)}") - update_account_entity = call.data.get(ATTR_UPDATE_ACCOUNT_ENTITY) + _LOGGER.debug(f"Update account: {update_account_entity}") for entity in entities: if entity.entity_id == update_account_entity: From fc5dac207cf5af51d7275c67711c917b8532294f Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Sat, 12 Mar 2022 11:10:25 +0000 Subject: [PATCH 09/12] Fix API timeouts when getting the session --- custom_components/sbanken/sbanken_api.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/custom_components/sbanken/sbanken_api.py b/custom_components/sbanken/sbanken_api.py index 076bff3..6d0a4b7 100644 --- a/custom_components/sbanken/sbanken_api.py +++ b/custom_components/sbanken/sbanken_api.py @@ -23,11 +23,7 @@ def __init__(self, client_id: string, secret: string) -> None: def get_session(self) -> OAuth2Session: """Get session""" - if ( - self._session is None - # or self._session.authorized is False - or self._session_expires_at < datetime.now() - ): + if self._session is None or self._session_expires_at < datetime.now(): _LOGGER.info("Create new session") self._session = self._create_session(self.client_id, self.secret) return self._session From d59cc83e72cb1456ca5af7c184115a1129395994 Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Sat, 12 Mar 2022 13:41:11 +0000 Subject: [PATCH 10/12] Mitigate "KeyError: 'accountId'" error --- custom_components/sbanken/sbanken_entities.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/custom_components/sbanken/sbanken_entities.py b/custom_components/sbanken/sbanken_entities.py index b3dc021..578c75b 100644 --- a/custom_components/sbanken/sbanken_entities.py +++ b/custom_components/sbanken/sbanken_entities.py @@ -44,6 +44,7 @@ def __init__( self.hass = hass self.customer_info = customer_info self._account = account + self._account_id = account["accountId"] self._transactions = [] self._payments = [] self._standing_orders = [] @@ -75,7 +76,7 @@ def state(self) -> float: def extra_state_attributes(self): """Return the state attributes.""" return { - ATTR_ACCOUNT_ID: self._account["accountId"], + ATTR_ACCOUNT_ID: self._account_id, ATTR_AVAILABLE: self._account["available"], ATTR_BALANCE: self._account["balance"], ATTR_ACCOUNT_NUMBER: self._account["accountNumber"], @@ -94,21 +95,21 @@ async def async_update(self): This is the only method that should fetch new data for Home Assistant. """ account = await self.hass.async_add_executor_job( - self.api.get_account, self._account["accountId"] + self.api.get_account, self._account_id ) transactions = await self.hass.async_add_executor_job( self.api.get_transactions, - self._account["accountId"], + self._account_id, self.number_of_transactions, ) payments = await self.hass.async_add_executor_job( self.api.get_payments, - self._account["accountId"], + self._account_id, self.number_of_transactions, ) standing_orders = await self.hass.async_add_executor_job( - self.api.get_standing_orders, self._account["accountId"] + self.api.get_standing_orders, self._account_id ) self._transactions = transactions From d87ff8433b5df060a54bdd0d006e720c8f34a0e3 Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Sat, 12 Mar 2022 13:42:05 +0000 Subject: [PATCH 11/12] Add debug logging --- custom_components/sbanken/sbanken_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/sbanken/sbanken_api.py b/custom_components/sbanken/sbanken_api.py index 6d0a4b7..476f93d 100644 --- a/custom_components/sbanken/sbanken_api.py +++ b/custom_components/sbanken/sbanken_api.py @@ -23,8 +23,9 @@ def __init__(self, client_id: string, secret: string) -> None: def get_session(self) -> OAuth2Session: """Get session""" + _LOGGER.debug("Get session") if self._session is None or self._session_expires_at < datetime.now(): - _LOGGER.info("Create new session") + _LOGGER.debug("Create new session") self._session = self._create_session(self.client_id, self.secret) return self._session From dfa629b092609a2f5dc40e33883f7c67e47f1250 Mon Sep 17 00:00:00 2001 From: Tor Inge Redalen Date: Sat, 12 Mar 2022 13:43:44 +0000 Subject: [PATCH 12/12] Cleanup --- custom_components/sbanken/sbanken_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/sbanken/sbanken_api.py b/custom_components/sbanken/sbanken_api.py index 476f93d..8d3177d 100644 --- a/custom_components/sbanken/sbanken_api.py +++ b/custom_components/sbanken/sbanken_api.py @@ -78,7 +78,7 @@ def get_account(self, account_id: str): self.get_session() .get(f"https://publicapi.sbanken.no/apibeta/api/v2/Accounts/{account_id}") .json() - ) # TODO catch and possible retry? + ) if "isError" not in response: return response