2626DATE_STR_FORMAT = "%Y-%m-%d"
2727DATE_TIME_STR_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
2828
29+ # Hard-wired smart meter day/night rate times
30+ OCTOPUS_NIGHT_RATE_START_HOUR = 0
31+ OCTOPUS_NIGHT_RATE_START_MINUTE = 30
32+ OCTOPUS_NIGHT_RATE_END_HOUR = 7
33+ OCTOPUS_NIGHT_RATE_END_MINUTE = 30
34+
35+ OCTOPUS_DAY_RATE_START_HOUR = 7
36+ OCTOPUS_DAY_RATE_START_MINUTE = 30
37+ OCTOPUS_DAY_RATE_END_HOUR = 0
38+ OCTOPUS_DAY_RATE_END_MINUTE = 30
2939
3040def is_active (now_utc , activeFrom , activeTo ):
3141 if not activeFrom :
@@ -673,6 +683,50 @@ async def async_get_saving_sessions(self, account_id):
673683 else :
674684 return response_data .get ("savingSessions" , {})
675685
686+ async def async_get_day_night_rates (self , url ):
687+ """
688+ Get day and night rates from Octopus
689+ """
690+ mdata = []
691+ self .log ("Info: Octopus tariff has day and night rates, fetching both" )
692+ url_day = url .replace ("standard-unit-rates" , "day-unit-rates" )
693+ url_night = url .replace ("standard-unit-rates" , "night-unit-rates" )
694+ result_day = await self .fetch_url_cached (url_day )
695+ result_night = await self .fetch_url_cached (url_night )
696+ # is_night_rate = self.__is_between_times(rate, "00:30:00", "07:30:00", True)
697+ # Find the current day rate by scanning all the values looking at valid from date and picking the latest that is before now
698+ current_day_rate = None
699+ current_night_rate = None
700+ for rate in result_day :
701+ valid_from_stamp = rate .get ("valid_from" , "" )
702+ # Convert from string to datetime
703+ valid_from_stamp = datetime .strptime (valid_from_stamp , DATE_TIME_STR_FORMAT )
704+ if valid_from_stamp <= self .now_utc :
705+ current_day_rate = rate .get ('value_inc_vat' , None )
706+ for rate in result_night :
707+ valid_from_stamp = rate .get ("valid_from" , "" )
708+ # Convert from string to datetime
709+ valid_from_stamp = datetime .strptime (valid_from_stamp , DATE_TIME_STR_FORMAT )
710+ if valid_from_stamp <= self .now_utc :
711+ current_night_rate = rate .get ('value_inc_vat' , None )
712+ self .log ("Info: Current day rate {} night rate {}" .format (current_day_rate , current_night_rate ))
713+ if current_day_rate is not None and current_night_rate is not None :
714+ # Now create a combined list of rates, start from 2 days back and go forward 3 days with the day and night rates
715+ night_start_time = self .now_utc .replace (hour = OCTOPUS_NIGHT_RATE_START_HOUR , minute = OCTOPUS_NIGHT_RATE_START_MINUTE , second = 0 , microsecond = 0 ) - timedelta (days = 2 )
716+ night_end_time = night_start_time .replace (hour = OCTOPUS_NIGHT_RATE_END_HOUR , minute = OCTOPUS_NIGHT_RATE_END_MINUTE )
717+ day_start_time = night_start_time .replace (hour = OCTOPUS_DAY_RATE_START_HOUR , minute = OCTOPUS_DAY_RATE_START_MINUTE )
718+ day_end_time = night_start_time .replace (hour = OCTOPUS_DAY_RATE_END_HOUR , minute = OCTOPUS_DAY_RATE_END_MINUTE ) + timedelta (days = 1 )
719+ for day in range (8 ):
720+ # Night rate
721+ mdata .append ({"valid_from" : night_start_time .strftime (DATE_TIME_STR_FORMAT ), "valid_to" : night_end_time .strftime (DATE_TIME_STR_FORMAT ), "value_inc_vat" : current_night_rate })
722+ # Day rate
723+ mdata .append ({"valid_from" : day_start_time .strftime (DATE_TIME_STR_FORMAT ), "valid_to" : day_end_time .strftime (DATE_TIME_STR_FORMAT ), "value_inc_vat" : current_day_rate })
724+ night_start_time += timedelta (days = 1 )
725+ night_end_time += timedelta (days = 1 )
726+ day_start_time += timedelta (days = 1 )
727+ day_end_time += timedelta (days = 1 )
728+ return mdata
729+
676730 async def async_download_octopus_url (self , url ):
677731 """
678732 Download octopus rates directly from a URL
@@ -683,7 +737,7 @@ async def async_download_octopus_url(self, url):
683737 while url and pages < 3 :
684738 self .requests_total += 1
685739 r = requests .get (url )
686- if r .status_code not in [200 , 201 ]:
740+ if r .status_code not in [200 , 201 , 400 ]:
687741 self .failures_total += 1
688742 self .log ("Warn: Error downloading Octopus data from URL {}, code {}" .format (url , r .status_code ))
689743 return {}
@@ -694,9 +748,28 @@ async def async_download_octopus_url(self, url):
694748 self .failures_total += 1
695749 self .log ("Warn: Error downloading Octopus data from URL {} (JSONDecodeError)" .format (url ))
696750 return {}
751+
752+ if r .status_code == 400 :
753+ detail = data .get ("detail" , "" )
754+ if 'This tariff has day and night rates' in detail :
755+ self .log ("Info: Octopus tariff has day and night rates, fetching both" )
756+ mdata = await self .async_get_day_night_rates (url )
757+ if mdata :
758+ return mdata
759+ else :
760+ self .failures_total += 1
761+ self .log ("Warn: Error downloading Octopus data from URL {} (No Results)" .format (url ))
762+ return {}
763+ else :
764+ self .failures_total += 1
765+ self .log ("Warn: Error downloading Octopus data from URL {} (400) - {}" .format (url , detail ))
766+ return {}
767+
697768 if "results" in data :
698769 mdata += data ["results" ]
699770 else :
771+ detail = data .get ("detail" , "" )
772+
700773 self .failures_total += 1
701774 self .log ("Warn: Error downloading Octopus data from URL {} (No Results)" .format (url ))
702775 return {}
@@ -751,7 +824,7 @@ async def fetch_tariffs(self, tariffs):
751824 tariff_type = "gas"
752825 else :
753826 tariff_type = "electricity"
754- tariffs [tariff ]["data" ] = await self .fetch_url_cached (f"https://api.octopus.energy/v1/products/{ product_code } /{ tariff_type } -tariffs/{ tariff_code } /standard-unit-rates/" )
827+ tariffs [tariff ]["data" ] = await self .fetch_url_cached (f"https://api.octopus.energy/v1/products/{ product_code } /{ tariff_type } -tariffs/{ tariff_code } /standard-unit-rates/" )
755828 tariffs [tariff ]["standing" ] = await self .fetch_url_cached (f"https://api.octopus.energy/v1/products/{ product_code } /{ tariff_type } -tariffs/{ tariff_code } /standing-charges/" )
756829
757830 entity_id = "sensor.predbat_octopus_" + self .account_id .replace ("-" , "_" ) + "_" + tariff
0 commit comments