Skip to content

Commit

Permalink
initial, potentially working version
Browse files Browse the repository at this point in the history
  • Loading branch information
jay-oswald committed Dec 6, 2023
0 parents commit 8a17728
Show file tree
Hide file tree
Showing 10 changed files with 261 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#HA Config folder
config/

#PY Cache
custom_components/up/__pycache__/*
21 changes: 21 additions & 0 deletions custom_components/up/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .up import UP
from .const import DOMAIN
from homeassistant.const import CONF_API_KEY

PLATFORMS: list[str] = ["sensor"]
_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = UP(entry.data[CONF_API_KEY])
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True

async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok
39 changes: 39 additions & 0 deletions custom_components/up/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import logging
from typing import Any
from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY
import voluptuous as vol
from .const import DOMAIN
from .up import UP

DATA_SCHEMA = vol.Schema({
vol.Required(CONF_API_KEY): str,
})
_LOGGER = logging.getLogger(__name__)

class UpConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
async def async_step_user(self, user_input: dict[str, Any] | None = None):
errors = {}
if user_input is not None:
api_key = user_input[CONF_API_KEY]
try:
up = UP(api_key)
info = await up.test(api_key)

if info:
return self.async_create_entry(title="UP", data=user_input)
else:
errors[CONF_API_KEY] = "API key failed validation"
except ConnectionError:
_LOGGER.exception("Connection Error")
errors[CONF_API_KEY] = "API Connection Error"
except Exception:
_LOGGER.exception("Unexpected exception")
errors[CONF_API_KEY] = "API Key not validated, unknown error"

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

1 change: 1 addition & 0 deletions custom_components/up/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DOMAIN = "up"
10 changes: 10 additions & 0 deletions custom_components/up/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"domain": "up",
"name": "Up Bank Integration",
"dependencies": ["http"],
"codeowners": ["@jay-oswald"],
"version": "0.0.1",
"integration_type": "hub",
"config_flow": true,
"iot_class": "cloud_polling"
}
97 changes: 97 additions & 0 deletions custom_components/up/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import logging
from datetime import timedelta
import async_timeout
from homeassistant.components.number import (
NumberEntity,
NumberDeviceClass
)
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from homeassistant.core import callback

from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass, config_entry, async_add_entities):
up = hass.data[DOMAIN][config_entry.entry_id]

coordinator = UpCoordinator(hass, up)

await coordinator.async_config_entry_first_refresh()

async_add_entities(Account(coordinator, coordinator.data[account]) for account in coordinator.data)

class UpCoordinator(DataUpdateCoordinator):

def __init__(self, hass, api):
super().__init__(
hass,
_LOGGER,
name="UP Coordinator",
update_interval = timedelta(hours=1)
)

self.api = api

async def _async_update_data(self):
try:
async with async_timeout.timeout(20):
return await self.api.getAccounts()
except Exception as err:
raise UpdateFailed(f"Error communicating with API: {err}")



class Account(CoordinatorEntity, NumberEntity):
account = {}
def __init__(self, coordinator, account):
super().__init__(coordinator, context=account)
self.setValues(account)

def setValues(self, account):
_LOGGER.warning(account)
self.account = account
self._attr_unique_id = "up_" + account.id
self._attr_name = account.name
self.balance = account.balance
self._state = account.balance
self.id = account.id

@callback
def _handle_coordinator_update(self) -> None:
self.setValues(self.coordinator.data[self.id])
self.async_write_ha_state()

@property
def device_class(self):
return NumberDeviceClass.MONETARY

@property
def device_info(self):
return {
"identifiers": {(DOMAIN, self._attr_unique_id)},
"name": self.account.name,
"manufacturer": self.account.ownership,
"model": self.account.accountType
}

@property
def available(self) -> bool:
return True

@property
def state(self):
return self.balance

@property
def mode(self):
return 'box'

@property
def native_step(self):
return 0.01


13 changes: 13 additions & 0 deletions custom_components/up/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"config": {
"step": {
"user": {
"title": "Config for Up Integration",
"description": "Enter the API key for UP",
"data": {
"api_key": "API Key"
}
}
}
}
}
13 changes: 13 additions & 0 deletions custom_components/up/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"config": {
"step": {
"user": {
"title": "Config for Up Integration",
"description": "Enter the API key for UP",
"data": {
"api_key": "API Key"
}
}
}
}
}
49 changes: 49 additions & 0 deletions custom_components/up/up.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import aiohttp
import logging
_LOGGER = logging.getLogger(__name__)

base = "https://api.up.com.au/api/v1"

class UP:
api_key = "";
def __init__(self, key):
self.api_key = key;

async def call(self, endpoint, data = {}, method="get"):
headers = { "Authorization": "Bearer " + self.api_key}
match method:
case "get":
async with aiohttp.ClientSession(headers=headers) as session:
async with session.get(base + endpoint) as resp:
resp.data = await resp.json()
return resp



async def test(self, api_key) -> bool:
self.api_key = api_key
result = await self.call("/util/ping")

return result.status == 200

async def getAccounts(self):
result = await self.call('/accounts')
if(result.status != 200):
return False

accounts = {};
for account in result.data['data']:
details = BankAccount(account)
accounts[details.id] = details

return accounts


class BankAccount:
def __init__(self, data):
self.name = data['attributes']['displayName']
self.balance = data['attributes']['balance']['value']
self.id = data['id']
self.createdAt = data['attributes']['createdAt']
self.accountType = data['attributes']['accountType']
self.ownership = data['attributes']['ownershipType']
13 changes: 13 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3'
services:
homeassistant:
container_name: homeassistant
image: "ghcr.io/home-assistant/home-assistant:stable"
volumes:
- ./config:/config
- /etc/localtime:/etc/localtime:ro
- /run/dbus:/run/dbus:ro
- ./custom_components/up:/config/custom_components/up/
restart: unless-stopped
privileged: true
network_mode: host

0 comments on commit 8a17728

Please sign in to comment.