Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: sindrebroch/ha-elvia
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.1.2b0
Choose a base ref
...
head repository: sindrebroch/ha-elvia
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Mar 18, 2022

  1. Copy the full SHA
    e04d572 View commit details
  2. Add TODOs

    sindrebroch committed Mar 18, 2022
    Copy the full SHA
    ef59650 View commit details

Commits on Apr 25, 2022

  1. Copy the full SHA
    97a2db6 View commit details

Commits on May 16, 2022

  1. Update devcontainer image

    sindrebroch committed May 16, 2022
    Copy the full SHA
    6b88bad View commit details
  2. Add range

    Sindre Broch committed May 16, 2022
    Copy the full SHA
    4ee2a5b View commit details
  3. Strip down sensors before adding new ones

    Sindre Broch committed May 16, 2022
    Copy the full SHA
    60e3d19 View commit details
  4. Adapt models to new api

    Sindre Broch committed May 16, 2022
    Copy the full SHA
    fd707ee View commit details
  5. Start implementing diagnostics. wip

    Sindre Broch committed May 16, 2022
    Copy the full SHA
    f258aca View commit details

Commits on May 25, 2022

  1. Update README

    Sindre Broch committed May 25, 2022
    Copy the full SHA
    54d05e6 View commit details
  2. Add sensors from new API

    Sindre Broch committed May 25, 2022
    Copy the full SHA
    cb04ee1 View commit details
  3. Fix model-issues

    Sindre Broch committed May 25, 2022
    Copy the full SHA
    b6d95b1 View commit details
  4. Update coordinator.py

    sindrebroch authored May 25, 2022
    Copy the full SHA
    1479f18 View commit details

Commits on Jun 17, 2022

  1. Copy the full SHA
    acd02bf View commit details
  2. Copy the full SHA
    8839eb3 View commit details
  3. Stringify

    sindrebroch committed Jun 17, 2022
    Copy the full SHA
    798da80 View commit details
  4. Update README.md

    sindrebroch authored Jun 17, 2022
    Copy the full SHA
    d141b0a View commit details

Commits on Jun 24, 2022

  1. Update README.md

    sindrebroch authored Jun 24, 2022
    Copy the full SHA
    445f4cb View commit details

Commits on Jul 7, 2022

  1. Update const.py

    sindrebroch authored Jul 7, 2022
    Copy the full SHA
    7141d4f View commit details
  2. Copy the full SHA
    87374fb View commit details
  3. Add logs

    sindrebroch authored Jul 7, 2022
    Copy the full SHA
    282421d View commit details
  4. Update README.md

    sindrebroch authored Jul 7, 2022
    Copy the full SHA
    97df084 View commit details

Commits on Jul 10, 2022

  1. Increase update interval

    sindrebroch committed Jul 10, 2022
    Copy the full SHA
    6ae21ae View commit details
  2. Copy the full SHA
    44c3685 View commit details
  3. Update README.md

    sindrebroch authored Jul 10, 2022
    Copy the full SHA
    24532e3 View commit details
  4. Add price_info

    sindrebroch committed Jul 10, 2022
    Copy the full SHA
    7f388cd View commit details
  5. Copy the full SHA
    2618ae8 View commit details

Commits on Jul 14, 2022

  1. Remove warning

    sindrebroch authored Jul 14, 2022
    Copy the full SHA
    78cca7e View commit details

Commits on Sep 10, 2022

  1. Add fixed price monthly

    sindrebroch committed Sep 10, 2022
    Copy the full SHA
    15f8467 View commit details
  2. Copy the full SHA
    b14b66c View commit details

Commits on Feb 6, 2023

  1. Copy the full SHA
    38a27aa View commit details
  2. Replace old setup-method

    sindrebroch committed Feb 6, 2023
    Copy the full SHA
    7a167e1 View commit details
  3. Copy the full SHA
    7fba3b1 View commit details
  4. Copy the full SHA
    a09b447 View commit details
  5. Add tariff-price-array

    sindrebroch committed Feb 6, 2023
    Copy the full SHA
    a3f93d2 View commit details
  6. Cleanup

    sindrebroch committed Feb 6, 2023
    Copy the full SHA
    d579d95 View commit details
  7. Update README

    sindrebroch committed Feb 6, 2023
    Copy the full SHA
    eff4134 View commit details
  8. Update README

    sindrebroch committed Feb 6, 2023
    Copy the full SHA
    1f2d642 View commit details
  9. Try fix current-month

    sindrebroch committed Feb 6, 2023
    Copy the full SHA
    9e3e86d View commit details

Commits on Jun 2, 2023

  1. Copy the full SHA
    d70df7f View commit details
  2. Copy the full SHA
    6a04c2b View commit details
  3. Sort manifest.json

    sindrebroch committed Jun 2, 2023
    Copy the full SHA
    e1c71ae View commit details
  4. Update version

    sindrebroch committed Jun 2, 2023
    Copy the full SHA
    c347e12 View commit details

Commits on Dec 7, 2023

  1. Update README.md

    sindrebroch authored Dec 7, 2023
    Copy the full SHA
    e752872 View commit details
1 change: 1 addition & 0 deletions .devcontainer/configuration.yaml
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ default_config:
logger:
default: info
logs:
homeassistant: warn
custom_components.elvia: debug

# If you need to debug uncomment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
4 changes: 2 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"image": "ludeeus/container:integration-debian",
"image": "ghcr.io/ludeeus/devcontainer/integration:stable",
"name": "Elvia development",
"context": "..",
"appPort": [
@@ -27,4 +27,4 @@
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
}
}
}
38 changes: 22 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -7,12 +7,11 @@

HomeAssistant-integration for Elvia

*Work in progress*

## Requirements

- Metering point id. Log into [Elvia](https://www.elvia.no/minside) and find your ID.
- Metering point id. (Målepunkt-ID, not Målernummer) Log into [Elvia](https://www.elvia.no/minside) and find your ID.
- API-key. Sign up for GridTariffAPI at [Elvia developer portal](https://elvia.portal.azure-api.net/) and you will receive an API-key via email. See [API-doc](https://assets.ctfassets.net/jbub5thfds15/1mF3J3xVf9400SDuwkChUC/a069a61a0257ba8c950432000bdefef3/Elvia_GridTariffAPI_for_smart_house_purposes_v1_1_20210212.doc.pdf) for more info.
- Token. [Read how](https://www.elvia.no/smart-forbruk/alt-om-din-strommaler/api-for-malerverdier-tilgjengelig-i-pilot-na/)

## Installation

@@ -39,21 +38,25 @@ HomeAssistant-integration for Elvia
</details>


## Features
### Fixed price
- Fixed
- Taxes
- Total
- Level
- Level Info
## Sensors
- Energy price
- Daily tariff (array of hourly values for the day)

- Fixed price hourly
- Fixed price level
- Fixed price monthly

### Variable price
- Energy
- Level
- Power
- Taxes
- Total
- Average max
- Current month
- Previous month

- Max hours [1, 2, 3]
- Current month
- StartTime (attribute)
- EndTime (attribute)
- Previous month
- StartTime (attribute)
- EndTime (attribute)

## Debugging
If something is not working properly, logs might help with debugging. To turn on debug-logging add this to your `configuration.yaml`
@@ -66,3 +69,6 @@ logger:

## API limitations
Limited to 200 calls/hour/user. The integration normally polls once every hour.

## Inspiration
https://github.com/uphillbattle/NettleieElvia
10 changes: 5 additions & 5 deletions custom_components/elvia/__init__.py
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .api import ElviaApiClient
from .const import CONF_METERING_POINT_ID, DEFAULT_INTERVAL, DOMAIN, LOGGER, PLATFORMS
from .const import CONF_METERING_POINT_ID, CONF_TOKEN, DOMAIN, LOGGER, PLATFORMS
from .coordinator import ElviaDataUpdateCoordinator


@@ -20,24 +20,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
api = ElviaApiClient(
api_key=entry.data[CONF_API_KEY],
metering_point_id=entry.data[CONF_METERING_POINT_ID],
token=entry.data[CONF_TOKEN],
session=async_get_clientsession(hass),
)

data = await api.meteringpoint()


coordinator = ElviaDataUpdateCoordinator(
hass=hass,
api=api,
update_interval=DEFAULT_INTERVAL,
tariffType=data.gridTariff.tariffType,
)

await coordinator.async_config_entry_first_refresh()

hass.data[DOMAIN][entry.entry_id] = coordinator

hass.config_entries.async_setup_platforms(entry, PLATFORMS)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

return True

34 changes: 24 additions & 10 deletions custom_components/elvia/api.py
Original file line number Diff line number Diff line change
@@ -8,6 +8,8 @@
import aiohttp
import socket

from datetime import timedelta, date

from .const import (
LOGGER,
PING_PATH,
@@ -16,6 +18,7 @@
TARIFFQUERY_PATH,
METERINGPOINT_PATH,
API_HEADERS,
MAX_HOURS_PATH,
)
from .models import (
TariffType,
@@ -35,20 +38,23 @@ def __init__(
self,
api_key: str,
metering_point_id: str,
token: str,
session: Optional[aiohttp.client.ClientSession] = None,
) -> None:
"""Initialize connection with Elvia."""

self._session = session
self._api_key = api_key
self._metering_point_id = metering_point_id
self._token = token

async def get(self, url: str) -> Any:
async def get(self, url: str, headers) -> Any:
"""Get request."""
t = self.headers_with_api_key() if headers is None else headers
return await self.api_wrapper(
method="GET",
url=url,
headers=self.headers_with_api_key(),
headers=t,
)

async def post(self, url: str, data: dict[str, Any] = {}) -> Any:
@@ -75,7 +81,7 @@ async def api_wrapper(
)

try:
async with async_timeout.timeout(10):
async with async_timeout.timeout(20):
response = await self._session.request(
method=method,
url=url,
@@ -90,16 +96,17 @@ async def api_wrapper(
LOGGER.debug("Status 200 OK")
elif (
status == HTTPStatus.UNAUTHORIZED
): # TODO throw specialized exception
):
# TODO throw specialized exception
LOGGER.debug("Status 401 Unauthorized")
elif status == HTTPStatus.FORBIDDEN: # TODO throw specialized exception
elif status == HTTPStatus.FORBIDDEN:
# TODO throw specialized exception
LOGGER.debug("Status 403 Forbidden")
else:
LOGGER.debug("Status=%s", status)

return (
await response.json()
) # TODO does not handle requests without body?
return await response.json()

except asyncio.TimeoutError as exception:
raise ApiClientException(
f"Timeout error fetching information from {url}"
@@ -139,12 +146,19 @@ async def meteringpoint(self) -> GridTariffCollection:
"""Returns tariff(s) and MPID(s) for the MPIDs(MeteringpointId/Målepunkt-Id) given as input."""
response = await self.post(
METERINGPOINT_PATH,
'{ "meteringPointIds": [ "' + str(self._metering_point_id) + '" ] }',
'{ "range": "today", "meteringPointIds": [ "' + str(self._metering_point_id) + '" ] }',
)
for collection in response["gridTariffCollections"]:
return GridTariffCollection.from_dict(collection)

async def maxhours(self):
return await self.get(f"{MAX_HOURS_PATH}?meteringPointIds={str(self._metering_point_id)}", headers=self.headers_with_token())

def headers_with_api_key(self) -> Dict[str, str]:
"""Get headers with api_key added."""
assert self._api_key is not None
return {**API_HEADERS, **{"X-API-Key": f"{self._api_key}"}}
return {**API_HEADERS, **{"X-API-Key": f"{self._api_key}"}}

def headers_with_token(self) -> Dict[str, str]:
assert self._token is not None
return {**API_HEADERS, **{"Authorization": f"Bearer {self._token}"}}
53 changes: 14 additions & 39 deletions custom_components/elvia/config_flow.py
Original file line number Diff line number Diff line change
@@ -13,15 +13,17 @@
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .api import ElviaApiClient
from .const import CONF_INTERVAL, CONF_METERING_POINT_ID, DOMAIN, DEFAULT_INTERVAL
from .const import CONF_METERING_POINT_ID, DOMAIN, CONF_TOKEN

SCHEMA = vol.Schema(
{
vol.Required(CONF_API_KEY): str,
vol.Required(CONF_METERING_POINT_ID): str,
vol.Required(CONF_TOKEN): str
}
)


class ElviaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for Elvia."""

@@ -36,21 +38,23 @@ async def async_step_user(

api_key = user_input[CONF_API_KEY]
metering_point_id = user_input[CONF_METERING_POINT_ID]
token = user_input[CONF_TOKEN]

api = ElviaApiClient(
api_key=api_key,
metering_point_id=metering_point_id,
session=async_get_clientsession(self.hass)
token=token,
session=async_get_clientsession(self.hass),
)

#try:
# await api.ping()
#except Exception:
# return self.async_show_form(
# step_id="user",
# data_schema=SCHEMA,
# errors={"base": "cannot_connect"},
# )
try:
await api.meteringpoint()
except Exception:
return self.async_show_form(
step_id="user",
data_schema=SCHEMA,
errors={"base": "cannot_connect"},
)

return self.async_create_entry(
title="Elvia",
@@ -62,32 +66,3 @@ async def async_step_user(
data_schema=SCHEMA,
errors={},
)

@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return ElviaOptionsFlowHandler(config_entry)


class ElviaOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle Elvia client options."""

def __init__(self, config_entry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry

async def async_step_init(
self, user_input: Dict[str, Any] or None = None
) -> FlowResult:
"""Manage Elvia options."""

if user_input is None:
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{vol.Required(CONF_INTERVAL, default=DEFAULT_INTERVAL): int}
),
)

return self.async_create_entry(title="Options", data=user_input)
24 changes: 15 additions & 9 deletions custom_components/elvia/const.py
Original file line number Diff line number Diff line change
@@ -7,22 +7,28 @@
DOMAIN = "elvia"
PLATFORMS = ["sensor"]

CONF_INTERVAL = "update_interval"
CONF_TOKEN = "token"
CONF_METERING_POINT_ID = "metering_point_id"

DEFAULT_INTERVAL = 5

DATE_FORMAT = "%Y-%m-%dT%H:%M:%S"

# API
API_URL: str = f"https://elvia.azure-api.net/grid-tariff"
API_BASE: str = "https://elvia.azure-api.net"

METER_VALUE_API_URL: str = f"{API_BASE}/customer/metervalues"
MAX_HOURS_PATH = f"{METER_VALUE_API_URL}/api/v2/maxhours" # GET

GRID_TARIFF_API_URL: str = f"{API_BASE}/grid-tariff"
API_HEADERS = {
"Content-Type": "application/json",
"Cache-Control": "no-cache",
}
PING_PATH = f"{API_URL}/Ping" # GET
SECURE_PATH = f"{API_URL}/Secure" # GET
TARIFFTYPES_PATH = f"{API_URL}/digin/api/1/tarifftype" # GET - {v}
TARIFFQUERY_PATH = f"{API_URL}/digin/api/1/tariffquery" # ?TariffKey={TariffKey}[&Range][&StartTime][&EndTime]" # GET
PING_PATH = f"{GRID_TARIFF_API_URL}/Ping" # GET
SECURE_PATH = f"{GRID_TARIFF_API_URL}/Secure" # GET
TARIFFTYPES_PATH = f"{GRID_TARIFF_API_URL}/digin/api/1/tarifftype" # GET - {v}
# TODO add tariffKey and range
TARIFFQUERY_PATH = f"{GRID_TARIFF_API_URL}/digin/api/1/tariffquery" # ?TariffKey={TariffKey}[&Range][&StartTime][&EndTime]" # GET
METERINGPOINT_PATH = (
f"{API_URL}/digin/api/1/tariffquery/meteringpointsgridtariffs" # POST
f"{GRID_TARIFF_API_URL}/digin/api/1/tariffquery/meteringpointsgridtariffs" # POST
)

Loading