Skip to content

Commit fd0c8a7

Browse files
Next release (#1363)
2 parents 4075f2a + 61b68bb commit fd0c8a7

File tree

23 files changed

+1137
-358
lines changed

23 files changed

+1137
-358
lines changed

_docs/entities/electricity.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,28 @@ The total consumption reported by the meter for all time. This will try and upda
474474
| `is_export` | `boolean` | Determines if the meter exports energy rather than imports |
475475
| `is_smart_meter` | `boolean` | Determines if the meter is considered smart by Octopus Energy |
476476

477+
### Current Total Export
478+
479+
`sensor.octopus_energy_electricity_{{METER_SERIAL_NUMBER}}_{{MPAN_NUMBER}}_current_total_export`
480+
481+
!!! warning
482+
This will only be available if you have specified you have an [Octopus Home Mini](../setup/account.md#home-mini). Do not set unless you have one.
483+
484+
!!! info
485+
Not all meters provide this information. In these scenarios, this sensor will report zero or unknown.
486+
487+
!!! note
488+
This is [disabled by default](../faq.md#there-are-entities-that-are-disabled-why-are-they-disabled-and-how-do-i-enable-them).
489+
490+
The total export reported by the meter for all time. This will try and update every minute for Home Mini.
491+
492+
| Attribute | Type | Description |
493+
|-----------|------|-------------|
494+
| `mpan` | `string` | The mpan for the associated meter |
495+
| `serial_number` | `string` | The serial for the associated meter |
496+
| `is_export` | `boolean` | Determines if the meter exports energy rather than imports |
497+
| `is_smart_meter` | `boolean` | Determines if the meter is considered smart by Octopus Energy |
498+
477499
### Current Accumulative Cost
478500

479501
`sensor.octopus_energy_electricity_{{METER_SERIAL_NUMBER}}_{{MPAN_NUMBER}}_current_accumulative_cost`

_docs/entities/heat_pump.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,30 @@ This represents the temperature reported by a sensor (e.g. Cosy Pod) that is ass
2424

2525
`climate.octopus_energy_heat_pump_{{HEAT_PUMP_ID}}_{{ZONE_CODE}}`
2626

27-
This can be used to control the target temperature and mode for a given zone (e.g. water or zone 1) linked to your heat pump. It will also display the current temperature linked to the primary sensor for the zone.
27+
This can be used to control the target temperature and mode for a given zone (e.g. zone 1) linked to your heat pump. It will also display the current temperature linked to the primary sensor for the zone.
2828

2929
The following operation modes are available
3030

3131
* `Heat` - This represents as `on` in the app
3232
* `Off` - This represents as `off` in the app
3333
* `Auto` - This represents as `auto` in the app
3434

35-
In addition, there is the preset of `boost`, which activates boost mode for the zone for 1 hour. If you require boost to be on for a different amount of time, then you can use the [available service](../services.md#octopus_energyboost_heat_pump_zone).
35+
In addition, there is the preset of `boost`. When `boost` is selected, this activates boost mode for the zone for 1 hour. If a target temperature is not set, then this will default to 50 degrees c. If you require boost to be on for a different amount of time or with a different target temperature, then you can use the [available service](../services.md#octopus_energyboost_heat_pump_zone).
36+
37+
## Water Heater
38+
39+
`water_heater.octopus_energy_heat_pump_{{HEAT_PUMP_ID}}`
40+
41+
This can be used to control the target temperature and mode for a given water heater linked to your heat pump. It will also display the current temperature linked to the primary sensor for the zone.
42+
43+
The following operation modes are available
44+
45+
* `on` - This represents as `on` in the app
46+
* `off` - This represents as `off` in the app
47+
* `heat_pump` - This represents as `auto` in the app
48+
* `high_demand` - This represents as `boost` in the app
49+
50+
When `boost` is selected, this activates boost mode for the zone for 1 hour. If a target temperature is not set, then this will default to 50 degrees c. If you require boost to be on for a different amount of time or with a different target temperature, then you can use the [available service](../services.md#octopus_energyboost_heat_pump_zone).
3651

3752
!!! note
3853

_docs/entities/intelligent.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,15 @@ This sensor is used to determine if you're currently in a planned dispatch perio
3939
| `next_start` | `datetime` | The date/time when the next dispatching or off peak rate starts |
4040
| `next_end` | `datetime` | The date/time when the next dispatching or off peak rate ends |
4141

42-
Each item in `planned_dispatch` or `completed_dispatches` have the following attributes
42+
Each item in `planned_dispatch` have the following attributes
43+
44+
| Attribute | Type | Description |
45+
|-----------|------|-------------|
46+
| `start` | `datetime` | The start date/time of the dispatch |
47+
| `end` | `datetime` | The end date/time of the dispatch |
48+
| `source` | `string` | Determines what has caused the dispatch to be generated. Will be `SMART`, `BOOST`, `TEST` or None. |
49+
50+
Each item in `completed_dispatches` have the following attributes
4351

4452
| Attribute | Type | Description |
4553
|-----------|------|-------------|

_docs/services.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,6 @@ For automation examples, please refer to the available [blueprints](./blueprints
178178

179179
This service allows the user to perform a spin on the [wheel of fortune](./entities/wheel_of_fortune.md) that is awarded to users every month. No point letting them go to waste :)
180180

181-
!!! warning
182-
183-
Due to an ongoing issue with the underlying API, this will not award octopoints if used. If you are on Octoplus, it is advised not to use this service.
184-
185181
| Attribute | Optional | Description |
186182
| ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
187183
| `target.entity_id` | `no` | The name of the wheel of fortune sensor that represents the type of spin to be made. This should always point at one of the [wheel of fortune sensors](./entities/wheel_of_fortune.md) entities. |

custom_components/octopus_energy/__init__.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,13 @@
5151
CONFIG_KIND_TARGET_RATE,
5252
CONFIG_MAIN_HOME_PRO_ADDRESS,
5353
CONFIG_MAIN_HOME_PRO_API_KEY,
54+
CONFIG_MAIN_HOME_PRO_SETTINGS,
5455
CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES,
5556
CONFIG_MAIN_INTELLIGENT_RATE_MODE,
5657
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES,
58+
CONFIG_MAIN_INTELLIGENT_SETTINGS,
5759
CONFIG_MAIN_OLD_API_KEY,
60+
CONFIG_MAIN_PRICE_CAP_SETTINGS,
5861
CONFIG_VERSION,
5962
DATA_DISCOVERY_MANAGER,
6063
DATA_HEAT_PUMP_CONFIGURATION_AND_STATUS_KEY,
@@ -82,7 +85,7 @@
8285
REPAIR_UNKNOWN_INTELLIGENT_PROVIDER
8386
)
8487

85-
ACCOUNT_PLATFORMS = ["sensor", "binary_sensor", "number", "switch", "text", "time", "event", "select", "climate"]
88+
ACCOUNT_PLATFORMS = ["sensor", "binary_sensor", "number", "switch", "text", "time", "event", "select", "climate", "water_heater"]
8689
TARGET_RATE_PLATFORMS = ["binary_sensor"]
8790
COST_TRACKER_PLATFORMS = ["sensor"]
8891
TARIFF_COMPARISON_PLATFORMS = ["sensor"]
@@ -285,12 +288,12 @@ async def async_setup_dependencies(hass, config):
285288
account_id = config[CONFIG_ACCOUNT_ID]
286289

287290
electricity_price_cap = None
288-
if CONFIG_MAIN_ELECTRICITY_PRICE_CAP in config:
289-
electricity_price_cap = config[CONFIG_MAIN_ELECTRICITY_PRICE_CAP]
291+
if (CONFIG_MAIN_PRICE_CAP_SETTINGS in config and CONFIG_MAIN_ELECTRICITY_PRICE_CAP in config[CONFIG_MAIN_PRICE_CAP_SETTINGS]):
292+
electricity_price_cap = config[CONFIG_MAIN_PRICE_CAP_SETTINGS][CONFIG_MAIN_ELECTRICITY_PRICE_CAP]
290293

291294
gas_price_cap = None
292-
if CONFIG_MAIN_GAS_PRICE_CAP in config:
293-
gas_price_cap = config[CONFIG_MAIN_GAS_PRICE_CAP]
295+
if (CONFIG_MAIN_PRICE_CAP_SETTINGS in config and CONFIG_MAIN_GAS_PRICE_CAP in config[CONFIG_MAIN_PRICE_CAP_SETTINGS]):
296+
gas_price_cap = config[CONFIG_MAIN_PRICE_CAP_SETTINGS][CONFIG_MAIN_GAS_PRICE_CAP]
294297

295298
favour_direct_debit_rates = True
296299
if CONFIG_MAIN_FAVOUR_DIRECT_DEBIT_RATES in config:
@@ -304,9 +307,10 @@ async def async_setup_dependencies(hass, config):
304307
client = OctopusEnergyApiClient(config[CONFIG_MAIN_API_KEY], electricity_price_cap, gas_price_cap, favour_direct_debit_rates=favour_direct_debit_rates)
305308
hass.data[DOMAIN][account_id][DATA_CLIENT] = client
306309

307-
if (CONFIG_MAIN_HOME_PRO_ADDRESS in config and
308-
config[CONFIG_MAIN_HOME_PRO_ADDRESS] is not None):
309-
home_pro_client = OctopusEnergyHomeProApiClient(config[CONFIG_MAIN_HOME_PRO_ADDRESS], config[CONFIG_MAIN_HOME_PRO_API_KEY] if CONFIG_MAIN_HOME_PRO_API_KEY in config else None)
310+
if (CONFIG_MAIN_HOME_PRO_SETTINGS in config and
311+
CONFIG_MAIN_HOME_PRO_ADDRESS in config[CONFIG_MAIN_HOME_PRO_SETTINGS] and
312+
config[CONFIG_MAIN_HOME_PRO_SETTINGS][CONFIG_MAIN_HOME_PRO_ADDRESS] is not None):
313+
home_pro_client = OctopusEnergyHomeProApiClient(config[CONFIG_MAIN_HOME_PRO_SETTINGS][CONFIG_MAIN_HOME_PRO_ADDRESS], config[CONFIG_MAIN_HOME_PRO_SETTINGS][CONFIG_MAIN_HOME_PRO_API_KEY] if CONFIG_MAIN_HOME_PRO_API_KEY in config[CONFIG_MAIN_HOME_PRO_SETTINGS] else None)
310314
hass.data[DOMAIN][account_id][DATA_HOME_PRO_CLIENT] = home_pro_client
311315

312316
# Delete any issues that may have been previously raised
@@ -440,7 +444,9 @@ async def async_setup_dependencies(hass, config):
440444
now - timedelta(hours=1)
441445
)
442446

443-
if CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES not in config or config[CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES] == False:
447+
if (CONFIG_MAIN_INTELLIGENT_SETTINGS not in config or
448+
CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES not in config[CONFIG_MAIN_INTELLIGENT_SETTINGS] or
449+
config[CONFIG_MAIN_INTELLIGENT_SETTINGS][CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES] == False):
444450
intelligent_manual_service_enabled = False
445451

446452
await async_save_cached_intelligent_device(hass, account_id, intelligent_device)
@@ -496,13 +502,16 @@ async def async_setup_dependencies(hass, config):
496502
is_smart_meter = meter["is_smart_meter"]
497503
override = await async_get_meter_debug_override(hass, mpan, serial_number)
498504
tariff_override = override.tariff if override is not None else None
505+
intelligent_rate_mode = (config[CONFIG_MAIN_INTELLIGENT_SETTINGS][CONFIG_MAIN_INTELLIGENT_RATE_MODE]
506+
if CONFIG_MAIN_INTELLIGENT_SETTINGS in config and CONFIG_MAIN_INTELLIGENT_RATE_MODE in config[CONFIG_MAIN_INTELLIGENT_SETTINGS]
507+
else CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES)
499508
await async_setup_electricity_rates_coordinator(hass,
500509
account_id,
501510
mpan,
502511
serial_number,
503512
is_smart_meter,
504513
is_export_meter,
505-
config[CONFIG_MAIN_INTELLIGENT_RATE_MODE] if CONFIG_MAIN_INTELLIGENT_RATE_MODE in config else CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES,
514+
intelligent_rate_mode,
506515
tariff_override)
507516

508517
mock_heat_pump = account_debug_override.mock_heat_pump if account_debug_override is not None else False
@@ -533,7 +542,7 @@ async def async_setup_dependencies(hass, config):
533542
hass,
534543
account_id,
535544
account_debug_override.mock_intelligent_controls if account_debug_override is not None else False,
536-
config[CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES] == True if CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES in config else False,
545+
intelligent_manual_service_enabled,
537546
intelligent_features.planned_dispatches_supported if intelligent_features is not None else True
538547
)
539548

custom_components/octopus_energy/api_client/__init__.py

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
consumption
114114
consumptionDelta
115115
demand
116+
export
116117
}}
117118
}}'''
118119

@@ -123,15 +124,11 @@
123124
currentState
124125
}}
125126
}}
126-
plannedDispatches(accountNumber: "{account_id}") {{
127-
start
128-
end
129-
delta
130-
meta {{
131-
source
132-
location
133-
}}
134-
}}
127+
flexPlannedDispatches(deviceId:"{device_id}") {{
128+
start
129+
end
130+
type
131+
}}
135132
completedDispatches(accountNumber: "{account_id}") {{
136133
start
137134
end
@@ -305,20 +302,18 @@
305302
}}'''
306303

307304
wheel_of_fortune_query = '''query {{
308-
wheelOfFortuneSpins(accountNumber: "{account_id}") {{
309-
electricity {{
310-
remainingSpinsThisMonth
311-
}}
312-
gas {{
313-
remainingSpinsThisMonth
314-
}}
305+
electricity: wheelOfFortuneSpinsAllowed(fuelType:ELECTRICITY, accountNumber: "{account_id}") {{
306+
spinsAllowed
307+
}}
308+
gas: wheelOfFortuneSpinsAllowed(fuelType:GAS, accountNumber: "{account_id}") {{
309+
spinsAllowed
315310
}}
316311
}}'''
317312

318313
wheel_of_fortune_mutation = '''mutation {{
319-
spinWheelOfFortune(input: {{ accountNumber: "{account_id}", supplyType: {supply_type}, termsAccepted: true }}) {{
320-
spinResult {{
321-
prizeAmount
314+
spinWheelOfFortune(input: {{ accountNumber: "{account_id}", fuelType: {fuel_type} }}) {{
315+
prize {{
316+
value
322317
}}
323318
}}
324319
}}'''
@@ -647,6 +642,7 @@ def __init__(self, api_key, electricity_price_cap = None, gas_price_cap = None,
647642

648643
self._api_key = api_key
649644
self._base_url = 'https://api.octopus.energy'
645+
self._backend_base_url = 'https://api.backend.octopus.energy'
650646

651647
self._graphql_token = None
652648
self._graphql_expiration = None
@@ -1138,6 +1134,7 @@ async def async_get_smart_meter_consumption(self, device_id: str, period_from: d
11381134
if (response_body is not None and "data" in response_body and "smartMeterTelemetry" in response_body["data"] and response_body["data"]["smartMeterTelemetry"] is not None and len(response_body["data"]["smartMeterTelemetry"]) > 0):
11391135
return list(map(lambda mp: {
11401136
"total_consumption": float(mp["consumption"]) / 1000 if "consumption" in mp and mp["consumption"] is not None else None,
1137+
"total_export": float(mp["export"]) / 1000 if "export" in mp and mp["export"] is not None else None,
11411138
"consumption": float(mp["consumptionDelta"]) / 1000 if "consumptionDelta" in mp and mp["consumptionDelta"] is not None else 0,
11421139
"demand": float(mp["demand"]) if "demand" in mp and mp["demand"] is not None else None,
11431140
"start": parse_datetime(mp["readAt"]),
@@ -1434,11 +1431,11 @@ async def async_get_intelligent_dispatches(self, account_id: str, device_id: str
14341431
planned_dispatches = list(map(lambda ev: IntelligentDispatchItem(
14351432
as_utc(parse_datetime(ev["start"])),
14361433
as_utc(parse_datetime(ev["end"])),
1437-
float(ev["delta"]) if "delta" in ev and ev["delta"] is not None else None,
1438-
ev["meta"]["source"] if "meta" in ev and "source" in ev["meta"] else None,
1439-
ev["meta"]["location"] if "meta" in ev and "location" in ev["meta"] else None,
1440-
), response_body["data"]["plannedDispatches"]
1441-
if "plannedDispatches" in response_body["data"] and response_body["data"]["plannedDispatches"] is not None
1434+
None,
1435+
ev["type"] if "type" in ev else None,
1436+
None
1437+
), response_body["data"]["flexPlannedDispatches"]
1438+
if "flexPlannedDispatches" in response_body["data"] and response_body["data"]["flexPlannedDispatches"] is not None
14421439
else [])
14431440
)
14441441

@@ -1756,20 +1753,21 @@ async def async_get_wheel_of_fortune_spins(self, account_id: str) -> WheelOfFort
17561753
try:
17571754
request_context = "wheel-of-fortune"
17581755
client = self._create_client_session()
1759-
url = f'{self._base_url}/v1/graphql/'
1756+
url = f'{self._backend_base_url}/v1/graphql/'
17601757
payload = { "query": wheel_of_fortune_query.format(account_id=account_id) }
1761-
headers = { "Authorization": f"JWT {self._graphql_token}", integration_context_header: request_context }
1758+
headers = { "Authorization": f"{self._graphql_token}", integration_context_header: request_context }
17621759
async with client.post(url, json=payload, headers=headers) as response:
17631760
response_body = await self.__async_read_response__(response, url)
17641761
_LOGGER.debug(f'async_get_wheel_of_fortune_spins: {response_body}')
17651762

17661763
if (response_body is not None and "data" in response_body and
1767-
"wheelOfFortuneSpins" in response_body["data"]):
1764+
"electricity" in response_body["data"] and
1765+
"gas" in response_body["data"]):
17681766

1769-
spins = response_body["data"]["wheelOfFortuneSpins"]
1767+
spins = response_body["data"]
17701768
return WheelOfFortuneSpinsResponse(
1771-
int(spins["electricity"]["remainingSpinsThisMonth"]) if "electricity" in spins and "remainingSpinsThisMonth" in spins["electricity"] else 0,
1772-
int(spins["gas"]["remainingSpinsThisMonth"]) if "gas" in spins and "remainingSpinsThisMonth" in spins["gas"] else 0
1769+
int(spins["electricity"]["spinsAllowed"]) if "electricity" in spins and "spinsAllowed" in spins["electricity"] else 0,
1770+
int(spins["gas"]["spinsAllowed"]) if "gas" in spins and "spinsAllowed" in spins["gas"] else 0
17731771
)
17741772
else:
17751773
_LOGGER.error("Failed to retrieve wheel of fortune spins")
@@ -1787,20 +1785,20 @@ async def async_spin_wheel_of_fortune(self, account_id: str, is_electricity: boo
17871785
try:
17881786
request_context = "spin-wheel-of-fortune"
17891787
client = self._create_client_session()
1790-
url = f'{self._base_url}/v1/graphql/'
1791-
payload = { "query": wheel_of_fortune_mutation.format(account_id=account_id, supply_type="ELECTRICITY" if is_electricity == True else "GAS") }
1792-
headers = { "Authorization": f"JWT {self._graphql_token}", integration_context_header: request_context }
1788+
url = f'{self._backend_base_url}/v1/graphql/'
1789+
payload = { "query": wheel_of_fortune_mutation.format(account_id=account_id, fuel_type="ELECTRICITY" if is_electricity == True else "GAS") }
1790+
headers = { "Authorization": f"{self._graphql_token}", integration_context_header: request_context }
17931791
async with client.post(url, json=payload, headers=headers) as response:
17941792
response_body = await self.__async_read_response__(response, url)
17951793
_LOGGER.debug(f'async_spin_wheel_of_fortune: {response_body}')
17961794

17971795
if (response_body is not None and
17981796
"data" in response_body and
17991797
"spinWheelOfFortune" in response_body["data"] and
1800-
"spinResult" in response_body["data"]["spinWheelOfFortune"] and
1801-
"prizeAmount" in response_body["data"]["spinWheelOfFortune"]["spinResult"]):
1798+
"prize" in response_body["data"]["spinWheelOfFortune"] and
1799+
"value" in response_body["data"]["spinWheelOfFortune"]["prize"]):
18021800

1803-
return int(response_body["data"]["spinWheelOfFortune"]["spinResult"]["prizeAmount"])
1801+
return int(response_body["data"]["spinWheelOfFortune"]["prize"]["value"])
18041802
else:
18051803
_LOGGER.error("Failed to spin wheel of fortune")
18061804

0 commit comments

Comments
 (0)