Skip to content

Commit

Permalink
Merge branch 'main' into schedule_octopus_agile
Browse files Browse the repository at this point in the history
  • Loading branch information
Meatballs1 authored and Meatballs1 committed Apr 15, 2024
2 parents 85747b8 + fbf89c7 commit 7427909
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
custom_components/hypervolt_charger/__pycache__/
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ One of:

⚠️ `Not Ready - Force Stopped` was added to overcome a gotcha with Hypervolt. If the user manually stops a charge by switching charging off via the app or integration, the Hypervolt charger remembers this state and if later is switched into Schedule Charge activation mode, the scheduled charge _will not automatically start_ as might be expected. To overcome this, the Charging switch needs to be manually toggled. This can be done when in Scheduled mode even outside of the schedule window and will switch the `Hypervolt Charging Readiness` state back to `Ready`. ⚠️ In the Hypervolt app, there doesn't appear to be a way of telling whether the charger is ready or not.

## 👁 EV Power, House Power, Grid Power, Generation Power (Sensors)

Power usage derived from the CT clamp(s), as reported by the Hypervolt app.

ℹ️ This integration just reports the values from the Hypervolt APIs. No assurance of accuracy of the values is given!

## 👁 Hypervolt Voltage, Hypervolt Charger Current (Sensors)

_Only available during a charging session_, these represent the voltage and current from the Hypervolt charger.
Expand All @@ -103,7 +109,7 @@ _Only available during a charging session_, these represent the voltage and curr

## 👁 Hypervolt CT Current, Hypervolt CT Power (Sensors)

_For Hypervolt 2.0 devices: Only available during a charging session_, these represent the current and power seen by the external CT clamp so will typically measure the household, or at least, whole circuit load, not just the Hypervolt.
_Only available during a charging session_, these represent the current and power seen by the external CT clamp so will typically measure the household, or at least, whole circuit load, not just the Hypervolt.

ℹ️ This integration just reports the values from the Hypervolt APIs. No assurance of accuracy of the values is given!

Expand Down
53 changes: 32 additions & 21 deletions custom_components/hypervolt_charger/hypervolt_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,10 @@ async def login(self, session: aiohttp.ClientSession) -> str:
session.headers["user-agent"] = self.get_user_agent()

async with session.post(
"https://auth.hypervolt.co.uk/oauth/token",
"https://kc.prod.hypervolt.co.uk/realms/retail-customers/protocol/openid-connect/token",
data={
"audience": "https://api.hypervolt.co.uk",
"client_id": "1BfAeDNKfu7mfPWCm6XTsZeM2QYhhju2", # Mimic mobile app. If Hypervolt are reading this: Please give the HA community our own Client ID and allow us to support the HA OAuth2 login flow
"grant_type": "http://auth0.com/oauth/grant-type/password-realm",
"realm": "Username-Password-Authentication",
"client_id": "home-assistant",
"grant_type": "password",
"scope": "openid profile email offline_access",
"username": self.username,
"password": self.password,
Expand Down Expand Up @@ -732,19 +730,31 @@ def on_message_session(self, result: dict, state: HypervoltDeviceState):
state.session_id = result["session"]
if "watt_hours" in result:
state.session_watthours = result["watt_hours"]
if "ccy_spent" in result:
state.session_currency_spent = result["ccy_spent"]
if "carbon_saved_grams" in result:
state.session_carbon_saved_grams = result["carbon_saved_grams"]

if "true_milli_amps" in result:
state.current_session_current_milliamps = result["true_milli_amps"]
if "ct_current" in result:
state.current_session_ct_current = result["ct_current"]
if "ct_power" in result:
state.current_session_ct_power = result["ct_power"]
if "voltage" in result:
state.current_session_voltage = result["voltage"]
if "ct_power" in result:
# Not seen outside of the old session/in-progress websocket
state.current_session_ct_power = result["ct_power"]
if "ct_current" in result and "voltage" in result:
# Reproduce old ct_power field from ct_current and voltage
state.current_session_ct_power = (
state.current_session_voltage * result["ct_current"] / 1000 # Convert mA to A
)
if "ev_power" in result:
state.ev_power = result["ev_power"]
if "house_power" in result:
state.house_power = result["house_power"]
if "grid_power" in result:
state.grid_power = result["grid_power"]
if "generation_power" in result:
state.generation_power = result["generation_power"]

# Calculate derived field: session_watthours_total_increasing
if (
Expand All @@ -771,7 +781,7 @@ def on_message_session(self, result: dict, state: HypervoltDeviceState):
)
else:
_LOGGER.debug(
"on_session_in_progress_websocket_message_callback new charging session detected"
"on_message_session new charging session detected"
)

# This is a new session, reset the value
Expand All @@ -783,17 +793,18 @@ def on_message_session(self, result: dict, state: HypervoltDeviceState):
oldest_energy_value = (
self.session_total_energy_snapshots_queue.head_element()
)
age_ms = oldest_energy_value.age_ms()
if age_ms > 10000: # 10 seconds
energy_diff_wh = (
state.session_watthours_total_increasing - oldest_energy_value.value
)
state.current_session_power = int(
energy_diff_wh / (age_ms / 1000.0 / 3600.0)
)
else:
# Not enough data points to update the power. Keep current value
pass
if oldest_energy_value:
age_ms = oldest_energy_value.age_ms()
if age_ms > 10000: # 10 seconds
energy_diff_wh = (
state.session_watthours_total_increasing - oldest_energy_value.value
)
state.current_session_power = int(
energy_diff_wh / (age_ms / 1000.0 / 3600.0)
)
else:
# Not enough data points to update the power. Keep current value
pass

else:
state.current_session_power = 0
Expand Down
4 changes: 4 additions & 0 deletions custom_components/hypervolt_charger/hypervolt_device_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ def __init__(self, charger_id) -> None:
self.current_session_ct_current = None
self.current_session_ct_power = None
self.current_session_voltage = None
self.ev_power = None
self.house_power = None
self.grid_power = None
self.generation_power = None
self.led_brightness = None
self.lock_state: HypervoltLockState = None
self.charge_mode: HypervoltChargeMode = None
Expand Down
2 changes: 1 addition & 1 deletion custom_components/hypervolt_charger/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"requirements": [
"websockets==11.0.3"
],
"version": "2.0.1"
"version": "2.1.0"
}
32 changes: 32 additions & 0 deletions custom_components/hypervolt_charger/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,38 @@ async def async_setup_entry(
state_class=SensorStateClass.MEASUREMENT,
unit_of_measure=UnitOfPower.WATT,
),
HypervoltSensor(
coordinator,
"EV Power",
"ev_power",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
unit_of_measure=UnitOfPower.WATT,
),
HypervoltSensor(
coordinator,
"House Power",
"house_power",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
unit_of_measure=UnitOfPower.WATT,
),
HypervoltSensor(
coordinator,
"Grid Power",
"grid_power",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
unit_of_measure=UnitOfPower.WATT,
),
HypervoltSensor(
coordinator,
"Generation Power",
"generation_power",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
unit_of_measure=UnitOfPower.WATT,
),
ChargingReadinessSensor(coordinator),
]

Expand Down

0 comments on commit 7427909

Please sign in to comment.