Skip to content

Commit

Permalink
Merge pull request #146 from magico13/v8
Browse files Browse the repository at this point in the history
V0.8
  • Loading branch information
magico13 authored Oct 8, 2022
2 parents 469db92 + 4574a3e commit 1d1199c
Show file tree
Hide file tree
Showing 7 changed files with 535 additions and 184 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.vscode
.vs
**/__pycache__
/dist
/build
Expand Down
356 changes: 272 additions & 84 deletions custom_components/emporia_vue/__init__.py

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions custom_components/emporia_vue/charger_entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import logging
from typing import Any, Mapping

from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)

from .const import DOMAIN

from pyemvue import pyemvue
from pyemvue.device import VueDevice

_LOGGER = logging.getLogger(__name__)


class EmporiaChargerEntity(CoordinatorEntity):
"""Emporia Charger Entity"""

def __init__(
self,
coordinator,
vue: pyemvue.PyEmVue,
device: VueDevice,
units: str,
device_class: str,
enabled_default=True,
):
super().__init__(coordinator)
self._coordinator = coordinator
self._device = device
self._vue = vue
self._enabled_default = enabled_default

self._attr_unit_of_measurement = units
self._attr_device_class = device_class
self._attr_name = device.device_name

@property
def entity_registry_enabled_default(self):
return self._enabled_default

@property
def extra_state_attributes(self) -> Mapping[str, Any]:
data = self._coordinator.data[self._device.device_gid]
if data:
return {
"charging_rate": data.charging_rate,
"max_charging_rate": data.max_charging_rate,
"status": data.status,
"message": data.message,
"fault_text": data.fault_text,
}
return None

@property
def unique_id(self) -> str:
"""Unique ID for the charger"""
return f"charger.emporia_vue.{self._device.device_gid}"

@property
def device_info(self):
"""Return the device information."""
return {
"identifiers": {(DOMAIN, "{0}-1,2,3".format(self._device.device_gid))},
"name": self._device.device_name + "-1,2,3",
"model": self._device.model,
"sw_version": self._device.firmware,
"manufacturer": "Emporia",
}

@property
def available(self):
"""Return True if entity is available."""
return self._device
4 changes: 2 additions & 2 deletions custom_components/emporia_vue/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
"name": "Emporia Vue",
"config_flow": true,
"documentation": "https://github.com/magico13/ha-emporia-vue",
"requirements": ["pyemvue==0.15.1"],
"requirements": ["pyemvue==0.16.1"],
"ssdp": [],
"zeroconf": [],
"homekit": {},
"dependencies": [],
"codeowners": ["@magico13"],
"iot_class": "cloud_polling",
"version": "0.7.3"
"version": "0.8.0"
}
104 changes: 49 additions & 55 deletions custom_components/emporia_vue/sensor.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Platform for sensor integration."""
from homeassistant.components.sensor import STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_MEASUREMENT, SensorEntity
from homeassistant.components.sensor import (
SensorStateClass,
SensorDeviceClass,
SensorEntity,
)
import logging

from homeassistant.const import (
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
POWER_WATT,
ENERGY_KILO_WATT_HOUR,
)
Expand Down Expand Up @@ -52,85 +54,76 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class CurrentVuePowerSensor(CoordinatorEntity, SensorEntity):
"""Representation of a Vue Sensor's current power."""

def __init__(self, coordinator, id):
def __init__(self, coordinator, identifier):
"""Pass coordinator to CoordinatorEntity."""
super().__init__(coordinator)
self._id = id
self._scale = coordinator.data[id]["scale"]
device_gid = coordinator.data[id]["device_gid"]
channel_num = coordinator.data[id]["channel_num"]
self._device = coordinator.data[id]["info"]
self._id = identifier
self._scale = coordinator.data[identifier]["scale"]
device_gid = coordinator.data[identifier]["device_gid"]
channel_num = coordinator.data[identifier]["channel_num"]
self._device = coordinator.data[identifier]["info"]
self._channel = None
if self._device is not None:
for channel in self._device.channels:
if channel.channel_num == channel_num:
self._channel = channel
break
if self._channel is None:
_LOGGER.warn(
f"No channel found for device_gid {device_gid} and channel_num {channel_num}"
_LOGGER.warning(
"No channel found for device_gid %s and channel_num %s",
device_gid,
channel_num,
)
raise RuntimeError(
f"No channel found for device_gid {device_gid} and channel_num {channel_num}"
)
dName = self._device.device_name
if self._channel.name and self._channel.name not in ["Main", "Balance"]:
dName = self._channel.name
self._name = f"{dName} {channel_num} {self._scale}"
device_name = self._device.device_name
if self._channel.name and self._channel.name not in [
"Main",
"Balance",
"TotalUsage",
"MainsToGrid",
"MainsFromGrid",
]:
device_name = self._channel.name
self._name = f"{device_name} {channel_num} {self._scale}"
self._iskwh = self.scale_is_energy()

@property
def name(self):
"""Return the name of the sensor."""
return self._name

@property
def state(self):
"""Return the state of the sensor."""
usage = self.coordinator.data[self._id]["usage"]
return self.scale_usage(usage)

@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
self._attr_name = self._name
if self._iskwh:
return ENERGY_KILO_WATT_HOUR
self._attr_native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
self._attr_device_class = SensorDeviceClass.ENERGY
self._attr_state_class = SensorStateClass.TOTAL
else:
return POWER_WATT
self._attr_native_unit_of_measurement = POWER_WATT
self._attr_device_class = SensorDeviceClass.POWER
self._attr_state_class = SensorStateClass.MEASUREMENT

@property
def device_class(self):
"""The type of sensor"""
if self._iskwh:
return DEVICE_CLASS_ENERGY
else:
return DEVICE_CLASS_POWER

@property
def state_class(self):
"""Type of state."""
if self._iskwh:
return STATE_CLASS_TOTAL_INCREASING
else:
return STATE_CLASS_MEASUREMENT
def native_value(self):
"""Return the state of the sensor."""
if self._id in self.coordinator.data:
usage = self.coordinator.data[self._id]["usage"]
return self.scale_usage(usage)
return None

@property
def last_reset(self):
"""The time when the daily/monthly sensor was reset. Midnight local time."""
return self.coordinator.data[self._id]["reset"]
if self._id in self.coordinator.data:
return self.coordinator.data[self._id]["reset"]
return None

@property
def unique_id(self):
"""Unique ID for the sensor"""
if self._scale == Scale.MINUTE.value:
return f"sensor.emporia_vue.instant.{self._channel.device_gid}-{self._channel.channel_num}"
else:
return f"sensor.emporia_vue.{self._scale}.{self._channel.device_gid}-{self._channel.channel_num}"
return f"sensor.emporia_vue.{self._scale}.{self._channel.device_gid}-{self._channel.channel_num}"

@property
def device_info(self):
dName = self._channel.name or self._device.device_name

device_name = self._channel.name or self._device.device_name
return {
"identifiers": {
# Serial numbers are unique identifiers within a specific domain
Expand All @@ -141,9 +134,10 @@ def device_info(self):
),
)
},
"name": dName,
"name": device_name,
"model": self._device.model,
"sw_version": self._device.firmware,
"manufacturer": "Emporia"
# "via_device": self._device.device_gid # might be able to map the extender, nested outlets
}

Expand All @@ -163,8 +157,8 @@ def scale_usage(self, usage):

def scale_is_energy(self):
"""Returns True if the scale is an energy unit instead of power (hour and bigger)"""
return (
self._scale != Scale.MINUTE.value
and self._scale != Scale.SECOND.value
and self._scale != Scale.MINUTES_15.value
return self._scale not in (
Scale.MINUTE.value,
Scale.SECOND.value,
Scale.MINUTES_15.value,
)
36 changes: 36 additions & 0 deletions custom_components/emporia_vue/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
set_charger_current:
# Service name as shown in UI
name: Set Charger Current
# Description of the service
description: Sets the charging current for an EVSE/Charger.
# If the service accepts entity IDs, target allows the user to specify entities by entity, device, or area. If `target` is specified, `entity_id` should not be defined in the `fields` map. By default it shows only targets matching entities from the same domain as the service, but if further customization is required, target supports the entity, device, and area selectors (https://www.home-assistant.io/docs/blueprint/selectors/). Entity selector parameters will automatically be applied to device and area, and device selector parameters will automatically be applied to area.
target:
entity:
manufacturer: Emporia
device_class: outlet
multiple: false
device:
manufacturer: Emporia
model: VVDN01
multiple: false
# Different fields that your service accepts
fields:
# Key of the field
current:
# Field name as shown in UI
name: Charging Current
# Description of the field
description: The desired charging current in Amps.
# Whether or not field is required (default = false)
required: true
# Advanced fields are only shown when the advanced mode is enabled for the user (default = false)
advanced: false
# Example value that can be passed for this field
example: 6
# The default field value
default: 6
# Selector (https://www.home-assistant.io/docs/blueprint/selectors/) to control the input UI for this field
selector:
number:
min: 6
max: 48
Loading

0 comments on commit 1d1199c

Please sign in to comment.