4646 CONF_ENABLE_THUNDERSTORM ,
4747 CONF_ENABLE_WUNDERGROUND ,
4848 CONF_ENABLE_ZAMBRETTI ,
49+ CONF_FORECAST_API_KEY ,
4950 CONF_FORECAST_ENABLED ,
5051 CONF_FORECAST_INTERVAL_MIN ,
5152 CONF_FORECAST_LAT ,
5253 CONF_FORECAST_LON ,
54+ CONF_FORECAST_PROVIDER ,
5355 CONF_HEMISPHERE ,
5456 CONF_NAME ,
5557 CONF_PREFIX ,
9395 DEFAULT_ENABLE_WUNDERGROUND ,
9496 DEFAULT_FORECAST_ENABLED ,
9597 DEFAULT_FORECAST_INTERVAL_MIN ,
98+ DEFAULT_FORECAST_PROVIDER ,
9699 DEFAULT_HEMISPHERE ,
97100 DEFAULT_NAME ,
98101 DEFAULT_PREFIX ,
112115 DEFAULT_UNITS_MODE ,
113116 DEFAULT_WU_INTERVAL_MIN ,
114117 DOMAIN ,
118+ FORECAST_PROVIDER_MET_NO ,
119+ FORECAST_PROVIDER_NWS ,
120+ FORECAST_PROVIDER_OPEN_METEO ,
121+ FORECAST_PROVIDER_OWM ,
122+ FORECAST_PROVIDER_PIRATE ,
115123 HEMISPHERE_OPTIONS ,
116124 OPTIONAL_SOURCES ,
125+ PROVIDERS_REQUIRING_API_KEY ,
117126 REQUIRED_SOURCES ,
118127 SRC_BATTERY ,
119128 SRC_DEW_POINT ,
@@ -597,6 +606,8 @@ async def async_step_forecast(self, user_input: dict[str, Any] | None = None):
597606 if back :
598607 return back
599608 self ._data .update (user_input )
609+ if user_input .get (CONF_FORECAST_PROVIDER ) in PROVIDERS_REQUIRING_API_KEY :
610+ return await self .async_step_forecast_api_key ()
600611 return await self .async_step_features ()
601612
602613 default_lat = getattr (self .hass .config , "latitude" , 0.0 ) or 0.0
@@ -618,8 +629,64 @@ async def async_step_forecast(self, user_input: dict[str, Any] | None = None):
618629 vol .Optional (CONF_FORECAST_LON , default = round (default_lon , 4 )): selector .NumberSelector (
619630 selector .NumberSelectorConfig (min = - 180 , max = 180 , step = 0.001 , mode = "box" )
620631 ),
632+ vol .Optional (CONF_FORECAST_PROVIDER , default = DEFAULT_FORECAST_PROVIDER ): selector .SelectSelector (
633+ selector .SelectSelectorConfig (
634+ options = [
635+ selector .SelectOptionDict (
636+ value = FORECAST_PROVIDER_OPEN_METEO , label = "Open-Meteo (free, no key)"
637+ ),
638+ selector .SelectOptionDict (
639+ value = FORECAST_PROVIDER_MET_NO , label = "Met.no (free, no key)"
640+ ),
641+ selector .SelectOptionDict (
642+ value = FORECAST_PROVIDER_NWS , label = "NWS/NOAA (free, no key, US only)"
643+ ),
644+ selector .SelectOptionDict (
645+ value = FORECAST_PROVIDER_OWM , label = "OpenWeatherMap (free tier, API key)"
646+ ),
647+ selector .SelectOptionDict (
648+ value = FORECAST_PROVIDER_PIRATE , label = "Pirate Weather (free tier, API key)"
649+ ),
650+ ],
651+ mode = selector .SelectSelectorMode .LIST ,
652+ )
653+ ),
654+ }
655+ ),
656+ last_step = False ,
657+ )
658+
659+ # ------------------------------------------------------------------
660+ # Step 6b: API key for providers that require one
661+ # ------------------------------------------------------------------
662+ async def async_step_forecast_api_key (self , user_input : dict [str , Any ] | None = None ):
663+ """Step: API key for forecast providers that require one."""
664+ if user_input is not None :
665+ back = await self ._handle_back (user_input )
666+ if back :
667+ return back
668+ self ._data .update (user_input )
669+ return await self .async_step_features ()
670+
671+ provider = self ._data .get (CONF_FORECAST_PROVIDER , "" )
672+ provider_labels = {
673+ FORECAST_PROVIDER_OWM : "OpenWeatherMap" ,
674+ FORECAST_PROVIDER_PIRATE : "Pirate Weather" ,
675+ }
676+ provider_name = provider_labels .get (provider , provider )
677+
678+ return self ._show_step (
679+ step_id = "forecast_api_key" ,
680+ data_schema = vol .Schema (
681+ {
682+ vol .Required (
683+ CONF_FORECAST_API_KEY ,
684+ default = self ._data .get (CONF_FORECAST_API_KEY , "" ),
685+ ): selector .TextSelector (selector .TextSelectorConfig (type = selector .TextSelectorType .PASSWORD )),
686+ vol .Optional ("_go_back" , default = False ): selector .BooleanSelector (),
621687 }
622688 ),
689+ description_placeholders = {"provider_name" : provider_name },
623690 last_step = False ,
624691 )
625692
@@ -1056,8 +1123,10 @@ async def async_step_init(self, user_input: dict[str, Any] | None = None):
10561123 out [CONF_RAIN_PENALTY_HEAVY_MMPH ] = _convert_rain_to_mmph (
10571124 float (out .get (CONF_RAIN_PENALTY_HEAVY_MMPH , DEFAULT_RAIN_PENALTY_HEAVY_MMPH )), imperial
10581125 )
1059- # Merge into options — features step comes next
1126+ # Merge into options — features step comes next (API key step if needed)
10601127 self ._opt : dict [str , Any ] = out
1128+ if out .get (CONF_FORECAST_PROVIDER ) in PROVIDERS_REQUIRING_API_KEY :
1129+ return await self .async_step_forecast_api_key_opt ()
10611130 return await self .async_step_features_opt ()
10621131
10631132 return self .async_show_form (
@@ -1117,6 +1186,28 @@ def _build_core_schema(self, imperial: bool, gust_u: str, rain_u: str, temp_u: s
11171186 vol .Optional (
11181187 CONF_FORECAST_LON , default = g (CONF_FORECAST_LON , round (default_lon , 4 ))
11191188 ): selector .NumberSelector (selector .NumberSelectorConfig (min = - 180 , max = 180 , step = 0.001 , mode = "box" )),
1189+ vol .Optional (
1190+ CONF_FORECAST_PROVIDER , default = g (CONF_FORECAST_PROVIDER , DEFAULT_FORECAST_PROVIDER )
1191+ ): selector .SelectSelector (
1192+ selector .SelectSelectorConfig (
1193+ options = [
1194+ selector .SelectOptionDict (
1195+ value = FORECAST_PROVIDER_OPEN_METEO , label = "Open-Meteo (free, no key)"
1196+ ),
1197+ selector .SelectOptionDict (value = FORECAST_PROVIDER_MET_NO , label = "Met.no (free, no key)" ),
1198+ selector .SelectOptionDict (
1199+ value = FORECAST_PROVIDER_NWS , label = "NWS/NOAA (free, no key, US only)"
1200+ ),
1201+ selector .SelectOptionDict (
1202+ value = FORECAST_PROVIDER_OWM , label = "OpenWeatherMap (free tier, API key)"
1203+ ),
1204+ selector .SelectOptionDict (
1205+ value = FORECAST_PROVIDER_PIRATE , label = "Pirate Weather (free tier, API key)"
1206+ ),
1207+ ],
1208+ mode = selector .SelectSelectorMode .LIST ,
1209+ )
1210+ ),
11201211 vol .Optional (
11211212 CONF_THRESH_WIND_GUST_MS , default = round (_convert_gust_to_display (cur_gust_ms , imperial ), 1 )
11221213 ): selector .NumberSelector (
@@ -1241,6 +1332,32 @@ async def async_step_features_opt(self, user_input: dict[str, Any] | None = None
12411332 last_step = False ,
12421333 )
12431334
1335+ async def async_step_forecast_api_key_opt (self , user_input : dict [str , Any ] | None = None ):
1336+ """Options step: API key for providers that require one."""
1337+ if user_input is not None :
1338+ self ._opt .update (user_input )
1339+ return await self .async_step_features_opt ()
1340+
1341+ provider = self ._opt .get (CONF_FORECAST_PROVIDER , "" )
1342+ provider_labels = {
1343+ FORECAST_PROVIDER_OWM : "OpenWeatherMap" ,
1344+ FORECAST_PROVIDER_PIRATE : "Pirate Weather" ,
1345+ }
1346+ provider_name = provider_labels .get (provider , provider )
1347+ current_key = self ._opt .get (CONF_FORECAST_API_KEY , self ._get (CONF_FORECAST_API_KEY , "" ))
1348+
1349+ return self .async_show_form (
1350+ step_id = "forecast_api_key_opt" ,
1351+ data_schema = vol .Schema (
1352+ {
1353+ vol .Required (CONF_FORECAST_API_KEY , default = current_key ): selector .TextSelector (
1354+ selector .TextSelectorConfig (type = selector .TextSelectorType .PASSWORD )
1355+ ),
1356+ }
1357+ ),
1358+ description_placeholders = {"provider_name" : provider_name },
1359+ )
1360+
12441361 # ------------------------------------------------------------------
12451362 # Sub-steps for each configurable feature
12461363 # ------------------------------------------------------------------
0 commit comments