Skip to content
This repository was archived by the owner on Jun 20, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 12 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
> ⚠️ This integration is available in Core. This custom integration won't be maintained and eventually removed. Click here to install the core integration: https://home-assistant.io/integrations/homewizard/

> ⚠️ **Make a back-up before installing this version. Your configuration will be migrated to Home Assistant Core.**

# HomeWizard Energy Integration
Custom integration for the [HomeWizard Energy Products](https://www.homewizard.nl/energie).

## Use the official integration
**⚠️ Since [Home Assistant 2022.2](https://home-assistant.io/blog/2022/02/02/release-20222/) this integration is available in core! Click [here](https://home-assistant.io/integrations/homewizard/) for more info**

## Integration added to core :tada:
This integration is available in Core. This custom integration won't be maintained and eventually removed. Click here to read more and install the core integration: https://home-assistant.io/integrations/homewizard/
<a href="https://my.home-assistant.io/redirect/config_flow_start?domain=homewizard" class="my badge" target="_blank"><img src="https://my.home-assistant.io/badges/config_flow_start.svg"></a>

I highly recommend to convert your configuration to use the core integration. This custom integration won't be updated from now on.
# Migration
**This integration is not maintained and will be removed**

From version [0.13.0](https://github.com/DCSBL/ha-homewizard-energy/releases), this custom integration only exists to allow migration of current configurations.
If you had a pre-0.13.0 version in use and you install this version, it will automaticly migrate to core.

1. **Can I transfer my data to the core integration?**
No. I've spend a couple of hours to try this but it is not possible for now. I will keep an eye on this and will write a migrator when this is possible. You can keep using this custom component.
Give a thumps up or something to [this issue](https://github.com/DCSBL/ha-homewizard-energy/issues/74) so I can determine the priority.
2. **My devices are rediscovered**
This is the core integration that can't see that you already have the same device configured via this custom integration. You can ignore the discovered device.
3. **What if I have any problems with the integration?**
## FAQ
1. **What if I have any problems with the integration?**
If the issues is with the core integration, you can open an issue [here](https://github.com/home-assistant/core/issues/new?assignees=&labels=&template=bug_report.yml). If you have an issue with the custom integration, you can open an issue [here](https://github.com/DCSBL/ha-homewizard-energy/issues)
4. **Where is `gas_timestamp` and `meter_model`?**
2. **Where is `gas_timestamp` and `meter_model`?**
Meter model is renamed to `Smart Meter Model`. Gas timestamp is removed because it is the same as 'last updated total gas'. You can get it back with a template sensor:
```
# configuration.yaml
Expand All @@ -30,65 +29,4 @@ sensor:
device_class: timestamp
value_template: "{{ as_timestamp(states.sensor.p1_meter_<serial>_active_power.last_updated) }}"
```
Replace `p1_meter_<serial>_total_gas` to use the correct entity id. Now you can use `sensor. p1_meter_gas_timestamp`

5. **I don't care about the data, I just want to use the core integration. How do I migrate?**

**Follow these steps:**
1. Remove the integration from your configuration. This step is important, otherwise you will get errors like `Setup failed for homewizard_energy: Integration not found`

<img width="427" alt="Screenshot 2022-02-03 at 09 25 31" src="https://user-images.githubusercontent.com/74970928/152306994-3eff8d06-d212-4909-9326-c7a34685ad52.png">

2. Remove the integration via HACS or remove the `config/custom_components/homewizard_energy` folder
3. Restart Home Assistant
4. Start the normal configuration [Click here](https://my.home-assistant.io/redirect/config_flow_start?domain=homewizard)

## Still wan't to install the custom integration?
### Requirements
* This integration works with:
* [HomeWizard wifi P1 meter](https://www.homewizard.nl/p1-meter)
* [HomeWizard wifi kWh meter single phase](https://www.homewizard.nl/kwh-meter)
* [HomeWizard wifi kWh meter 3-phase](https://www.homewizard.nl/kwh-meter)
* [HomeWizard wifi Energy Socket](https://www.homewizard.nl/energy-socket)
* Make sure the HomeWizard Energy device has been connected to the same network.

### Installation
#### HACS (https://hacs.xyz)
1. Install this integration from HACS (Search for 'HomeWizard Energy').
2. ❗ **Restart Home Assistant**.

#### Manual installation
1. Download the zip `homewizard_energy.zip` from the [latest release](https://github.com/DCSBL/ha-homewizard-energy/releases/latest)
2. Extract this zip in `config/custom_components`. (The config folder where configuration.yaml can be found)
3. ❗**Restart Home Assistant**.
4. Please come back now and then to check if there is a new version available (this can be automated with HACS)

### Usage
1. Go to Configuration > Integrations.
2. Home Assistant should tell you that a new device has been 'discovered'. (if not, please read 'manual configuration')
3. Press configure to add this device, and give it a name if you want.
4. :tada:

#### Manual configuration
If Home Assistant can't automaticly find your device, you can try to install your meter manually.
1. Go to Configuration > Integrations > Add integration > search for 'HomeWizard Energy'.
2. Enter the IP address from your device (eg. `192.168.1.107`).

### Discussion
Please join us at [the HASS forum](https://community.home-assistant.io/t/wi-fi-p1-dsmr-dongle-homewizard-energy) or the Dutch website [Tweakers (NL)](https://gathering.tweakers.net/forum/list_messages/2002754/last)

### Frequent questions and issues
1. **Home Assistant tells my device is offline after updating this integration**
This is a known issue. Please restart your Home Assistant again. That should solve it for you.
2. **I get a 'connection refused' error when trying to connect to `http://<ip_address>/api/v1/data`**
Your P1 meter must be at firmware version `2.11` or higher. You can see this in the HomeWizard Energy app under 'Meters'. Your device should update within an hour after connecting it to the internet. If your device doesn't, contact HomeWizard Support.
3. **Is the HomeWizard Wi-Fi kWh meter supported?**
Yes, since 2021-06-03. You have to enable the API in the HomeWizard Energy app.
4. **Can I see the daily/weekly/montly usage and history with this integration?**
Yes, add the device to the [Home Energy Management](https://www.home-assistant.io/docs/energy/) dashboard

### API documentation
[HomeWizard Energy local API](https://homewizard-energy-api.readthedocs.io/#)

### Disclaimer
This integration is not developed, nor supported by HomeWizard.
Replace `p1_meter_<serial>_total_gas` to use the correct entity id. Now you can use `sensor. p1_meter_gas_timestamp`
89 changes: 11 additions & 78 deletions custom_components/homewizard_energy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,80 +1,28 @@
"""The Homewizard Energy integration."""
import asyncio
import logging

import aiohwenergy
import async_timeout

from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import CONF_API, COORDINATOR, DOMAIN, PLATFORMS
from .coordinator import HWEnergyDeviceUpdateCoordinator as Coordinator
from .const import DOMAIN, TARGET_DOMAIN

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Homewizard Energy from a config entry."""
hass.data.setdefault(DOMAIN, {})

_LOGGER.debug("__init__ async_setup_entry")
_LOGGER.warning("Sending config entry to core...")

# Get api and do a initialization
session = async_get_clientsession(hass)
energy_api = aiohwenergy.HomeWizardEnergy(
entry.data.get("host"), clientsession=session
)

# Validate connection
initialized = False
try:
with async_timeout.timeout(10):
await energy_api.initialize()
initialized = True

except (asyncio.TimeoutError, aiohwenergy.RequestError) as ex:
_LOGGER.error(
"Error connecting to the Energy device at %s",
energy_api.host,
)
raise ConfigEntryNotReady from ex

except aiohwenergy.DisabledError as ex:
_LOGGER.error("API disabled, API must be enabled in the app")
raise ConfigEntryNotReady from ex

except aiohwenergy.AiohwenergyException as ex:
_LOGGER.error("Unknown Energy API error occurred")
raise ConfigEntryNotReady from ex

except Exception as ex: # pylint: disable=broad-except
_LOGGER.error(
"Unknown error connecting with Energy Device at %s",
energy_api.host,
)
raise ConfigEntryNotReady from ex

finally:
if not initialized:
await energy_api.close()

# Create coordinator
coordinator = Coordinator(hass, energy_api)
await coordinator.async_config_entry_first_refresh()

# Finalize
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.data["unique_id"]] = {
COORDINATOR: coordinator,
CONF_API: energy_api,
}

for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
hass.async_create_task(
hass.config_entries.flow.async_init(
TARGET_DOMAIN,
context={"source": SOURCE_IMPORT, "old_config_entry_id": entry.entry_id},
data=entry.data,
)
)

return True

Expand All @@ -83,19 +31,4 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
_LOGGER.debug("__init__ async_unload_entry")

unload_ok = all(
await asyncio.gather(
*(
hass.config_entries.async_forward_entry_unload(entry, component)
for component in PLATFORMS
)
)
)

if unload_ok:
config_data = hass.data[DOMAIN].pop(entry.data["unique_id"])
if "api" in config_data:
energy_api = config_data[CONF_API]
await energy_api.close()

return unload_ok
return True
161 changes: 1 addition & 160 deletions custom_components/homewizard_energy/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,13 @@
import logging
from typing import Any

import aiohwenergy
from aiohwenergy.hwenergy import SUPPORTED_DEVICES
import async_timeout
from voluptuous import All, Length, Required, Schema
from voluptuous.util import Lower

from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.const import CONF_HOST, CONF_IP_ADDRESS, CONF_PORT
from homeassistant.data_entry_flow import FlowResult

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for P1 meter."""

Expand All @@ -36,154 +27,4 @@ async def async_step_user(

_LOGGER.debug("config_flow async_step_user")

if user_input is None:
return self.async_show_form(
step_id="user",
data_schema=Schema(
{
Required(CONF_IP_ADDRESS): str,
}
),
errors=None,
)

entry_info = {
CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS],
CONF_PORT: 80,
}

return await self.async_step_check(entry_info)

async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle zeroconf discovery."""

_LOGGER.debug("config_flow async_step_zeroconf")

# Validate doscovery entry
if (
"api_enabled" not in discovery_info.properties
or "path" not in discovery_info.properties
or "product_name" not in discovery_info.properties
or "product_type" not in discovery_info.properties
or "serial" not in discovery_info.properties
):
return self.async_abort(reason="invalid_discovery_parameters")

if (discovery_info.properties["path"]) != "/api/v1":
return self.async_abort(reason="unsupported_api_version")

if (discovery_info.properties["api_enabled"]) != "1":
return self.async_abort(reason="api_not_enabled")

# Pass parameters
entry_info = {
CONF_IP_ADDRESS: discovery_info.host,
CONF_PORT: discovery_info.port,
}

return await self.async_step_check(entry_info)

async def async_step_check(self, entry_info):
"""Validate API connection and fetch metadata."""

_LOGGER.debug("config_flow async_step_check")

# Make connection with device
energy_api = aiohwenergy.HomeWizardEnergy(entry_info[CONF_IP_ADDRESS])

initialized = False
try:
with async_timeout.timeout(10):
await energy_api.initialize()
if energy_api.device is not None:
initialized = True

except aiohwenergy.DisabledError:
_LOGGER.error("API disabled, API must be enabled in the app")
return self.async_abort(reason="api_not_enabled")

except Exception: # pylint: disable=broad-except
_LOGGER.error(
"Error connecting with Energy Device at %s",
entry_info[CONF_IP_ADDRESS],
)
return self.async_abort(reason="unknown_error")

finally:
await energy_api.close()

if not initialized:
_LOGGER.error("Initialization failed")
return self.async_abort(reason="unknown_error")

# Validate metadata
if energy_api.device.api_version != "v1":
return self.async_abort(reason="unsupported_api_version")

if energy_api.device.product_type not in SUPPORTED_DEVICES:
_LOGGER.error(
"Device (%s) not supported by integration",
energy_api.device.product_type,
)
return self.async_abort(reason="device_not_supported")

# Configure device
entry_info["product_name"] = energy_api.device.product_name
entry_info["product_type"] = energy_api.device.product_type
entry_info["serial"] = energy_api.device.serial

self.context[CONF_HOST] = entry_info[CONF_IP_ADDRESS]
self.context[CONF_PORT] = entry_info[CONF_PORT]
self.context["product_name"] = entry_info["product_name"]
self.context["product_type"] = entry_info["product_type"]
self.context["serial"] = entry_info["serial"]
self.context[
"unique_id"
] = f"{entry_info['product_type']}_{entry_info['serial']}"
self.context[
"name"
] = f"{self.context['product_name']} ({self.context['serial'][-6:]})"

await self.async_set_unique_id(self.context["unique_id"])
self._abort_if_unique_id_configured(updates=entry_info)

self.context["title_placeholders"] = {
"name": self.context["name"],
"unique_id": self.context["unique_id"],
}

return await self.async_step_confirm()

async def async_step_confirm(self, user_input=None):
"""Handle user-confirmation of node."""

_LOGGER.debug("config_flow async_step_confirm")

if user_input is None:
return self.async_show_form(
step_id="confirm",
description_placeholders={"name": self.context["product_name"]},
data_schema=Schema(
{
Required("name", default=self.context["product_name"]): All(
str, Length(min=1)
)
}
),
errors=None,
)

# Format name
self.context["custom_name"] = user_input["name"]
if Lower(self.context["product_name"]) != Lower(user_input["name"]):
title = f"{self.context['product_name']} ({self.context['custom_name']})"
else:
title = self.context["custom_name"]

# Finish up
return self.async_create_entry(
title=title,
data=self.context,
)
return self.async_abort(reason="manual_not_supported")
Loading