5959from homeassistant .setup import async_get_loaded_integrations
6060
6161from .const import (
62- ANALYTICS_ENDPOINT_URL ,
63- ANALYTICS_ENDPOINT_URL_DEV ,
64- ANALYTICS_SNAPSHOT_ENDPOINT_URL ,
6562 ATTR_ADDON_COUNT ,
6663 ATTR_ADDONS ,
6764 ATTR_ARCH ,
9188 ATTR_USER_COUNT ,
9289 ATTR_UUID ,
9390 ATTR_VERSION ,
91+ BASIC_ENDPOINT_URL ,
92+ BASIC_ENDPOINT_URL_DEV ,
9493 DOMAIN ,
9594 INTERVAL ,
9695 LOGGER ,
9796 PREFERENCE_SCHEMA ,
97+ SNAPSHOT_DEFAULT_URL ,
98+ SNAPSHOT_URL_PATH ,
9899 SNAPSHOT_VERSION ,
99100 STORAGE_KEY ,
100101 STORAGE_VERSION ,
@@ -236,10 +237,18 @@ def from_dict(cls, data: dict[str, Any]) -> AnalyticsData:
236237class Analytics :
237238 """Analytics helper class for the analytics integration."""
238239
239- def __init__ (self , hass : HomeAssistant ) -> None :
240+ def __init__ (
241+ self ,
242+ hass : HomeAssistant ,
243+ snapshots_url : str | None = None ,
244+ disable_snapshots : bool = False ,
245+ ) -> None :
240246 """Initialize the Analytics class."""
241- self .hass : HomeAssistant = hass
242- self .session = async_get_clientsession (hass )
247+ self ._hass : HomeAssistant = hass
248+ self ._snapshots_url = snapshots_url
249+ self ._disable_snapshots = disable_snapshots
250+
251+ self ._session = async_get_clientsession (hass )
243252 self ._data = AnalyticsData (False , {})
244253 self ._store = Store [dict [str , Any ]](hass , STORAGE_VERSION , STORAGE_KEY )
245254 self ._basic_scheduled : CALLBACK_TYPE | None = None
@@ -249,13 +258,15 @@ def __init__(self, hass: HomeAssistant) -> None:
249258 def preferences (self ) -> dict :
250259 """Return the current active preferences."""
251260 preferences = self ._data .preferences
252- return {
261+ result = {
253262 ATTR_BASE : preferences .get (ATTR_BASE , False ),
254- ATTR_SNAPSHOTS : preferences .get (ATTR_SNAPSHOTS , False ),
255263 ATTR_DIAGNOSTICS : preferences .get (ATTR_DIAGNOSTICS , False ),
256264 ATTR_USAGE : preferences .get (ATTR_USAGE , False ),
257265 ATTR_STATISTICS : preferences .get (ATTR_STATISTICS , False ),
258266 }
267+ if not self ._disable_snapshots :
268+ result [ATTR_SNAPSHOTS ] = preferences .get (ATTR_SNAPSHOTS , False )
269+ return result
259270
260271 @property
261272 def onboarded (self ) -> bool :
@@ -272,13 +283,13 @@ def endpoint_basic(self) -> str:
272283 """Return the endpoint that will receive the payload."""
273284 if RELEASE_CHANNEL is ReleaseChannel .DEV :
274285 # dev installations will contact the dev analytics environment
275- return ANALYTICS_ENDPOINT_URL_DEV
276- return ANALYTICS_ENDPOINT_URL
286+ return BASIC_ENDPOINT_URL_DEV
287+ return BASIC_ENDPOINT_URL
277288
278289 @property
279290 def supervisor (self ) -> bool :
280291 """Return bool if a supervisor is present."""
281- return is_hassio (self .hass )
292+ return is_hassio (self ._hass )
282293
283294 async def load (self ) -> None :
284295 """Load preferences."""
@@ -288,7 +299,7 @@ async def load(self) -> None:
288299
289300 if (
290301 self .supervisor
291- and (supervisor_info := hassio .get_supervisor_info (self .hass )) is not None
302+ and (supervisor_info := hassio .get_supervisor_info (self ._hass )) is not None
292303 ):
293304 if not self .onboarded :
294305 # User have not configured analytics, get this setting from the supervisor
@@ -315,15 +326,15 @@ async def save_preferences(self, preferences: dict) -> None:
315326
316327 if self .supervisor :
317328 await hassio .async_update_diagnostics (
318- self .hass , self .preferences .get (ATTR_DIAGNOSTICS , False )
329+ self ._hass , self .preferences .get (ATTR_DIAGNOSTICS , False )
319330 )
320331
321332 async def send_analytics (self , _ : datetime | None = None ) -> None :
322333 """Send analytics."""
323334 if not self .onboarded or not self .preferences .get (ATTR_BASE , False ):
324335 return
325336
326- hass = self .hass
337+ hass = self ._hass
327338 supervisor_info = None
328339 operating_system_info : dict [str , Any ] = {}
329340
@@ -463,7 +474,7 @@ async def send_analytics(self, _: datetime | None = None) -> None:
463474
464475 try :
465476 async with timeout (30 ):
466- response = await self .session .post (self .endpoint_basic , json = payload )
477+ response = await self ._session .post (self .endpoint_basic , json = payload )
467478 if response .status == 200 :
468479 LOGGER .info (
469480 (
@@ -479,11 +490,9 @@ async def send_analytics(self, _: datetime | None = None) -> None:
479490 self .endpoint_basic ,
480491 )
481492 except TimeoutError :
482- LOGGER .error ("Timeout sending analytics to %s" , ANALYTICS_ENDPOINT_URL )
493+ LOGGER .error ("Timeout sending analytics to %s" , BASIC_ENDPOINT_URL )
483494 except aiohttp .ClientError as err :
484- LOGGER .error (
485- "Error sending analytics to %s: %r" , ANALYTICS_ENDPOINT_URL , err
486- )
495+ LOGGER .error ("Error sending analytics to %s: %r" , BASIC_ENDPOINT_URL , err )
487496
488497 @callback
489498 def _async_should_report_integration (
@@ -507,7 +516,7 @@ def _async_should_report_integration(
507516 if not integration .config_flow :
508517 return False
509518
510- entries = self .hass .config_entries .async_entries (integration .domain )
519+ entries = self ._hass .config_entries .async_entries (integration .domain )
511520
512521 # Filter out ignored and disabled entries
513522 return any (
@@ -521,7 +530,7 @@ async def send_snapshot(self, _: datetime | None = None) -> None:
521530 if not self .onboarded or not self .preferences .get (ATTR_SNAPSHOTS , False ):
522531 return
523532
524- payload = await _async_snapshot_payload (self .hass )
533+ payload = await _async_snapshot_payload (self ._hass )
525534
526535 headers = {
527536 "Content-Type" : "application/json" ,
@@ -532,11 +541,16 @@ async def send_snapshot(self, _: datetime | None = None) -> None:
532541 self ._data .submission_identifier
533542 )
534543
544+ url = (
545+ self ._snapshots_url
546+ if self ._snapshots_url is not None
547+ else SNAPSHOT_DEFAULT_URL
548+ )
549+ url += SNAPSHOT_URL_PATH
550+
535551 try :
536552 async with timeout (30 ):
537- response = await self .session .post (
538- ANALYTICS_SNAPSHOT_ENDPOINT_URL , json = payload , headers = headers
539- )
553+ response = await self ._session .post (url , json = payload , headers = headers )
540554
541555 if response .status == 200 : # OK
542556 response_data = await response .json ()
@@ -562,7 +576,7 @@ async def send_snapshot(self, _: datetime | None = None) -> None:
562576 # Clear the invalid identifier and retry on next cycle
563577 LOGGER .warning (
564578 "Invalid submission identifier to %s, clearing: %s" ,
565- ANALYTICS_SNAPSHOT_ENDPOINT_URL ,
579+ url ,
566580 error_message ,
567581 )
568582 self ._data .submission_identifier = None
@@ -571,34 +585,34 @@ async def send_snapshot(self, _: datetime | None = None) -> None:
571585 LOGGER .warning (
572586 "Malformed snapshot analytics submission (%s) to %s: %s" ,
573587 error_kind ,
574- ANALYTICS_SNAPSHOT_ENDPOINT_URL ,
588+ url ,
575589 error_message ,
576590 )
577591
578592 elif response .status == 503 : # Service Unavailable
579593 response_text = await response .text ()
580594 LOGGER .warning (
581595 "Snapshot analytics service %s unavailable: %s" ,
582- ANALYTICS_SNAPSHOT_ENDPOINT_URL ,
596+ url ,
583597 response_text ,
584598 )
585599
586600 else :
587601 LOGGER .warning (
588602 "Unexpected status code %s when submitting snapshot analytics to %s" ,
589603 response .status ,
590- ANALYTICS_SNAPSHOT_ENDPOINT_URL ,
604+ url ,
591605 )
592606
593607 except TimeoutError :
594608 LOGGER .error (
595609 "Timeout sending snapshot analytics to %s" ,
596- ANALYTICS_SNAPSHOT_ENDPOINT_URL ,
610+ url ,
597611 )
598612 except aiohttp .ClientError as err :
599613 LOGGER .error (
600614 "Error sending snapshot analytics to %s: %r" ,
601- ANALYTICS_SNAPSHOT_ENDPOINT_URL ,
615+ url ,
602616 err ,
603617 )
604618
@@ -622,7 +636,7 @@ async def async_schedule(self) -> None:
622636 elif self ._basic_scheduled is None :
623637 # Wait 15 min after started for basic analytics
624638 self ._basic_scheduled = async_call_later (
625- self .hass ,
639+ self ._hass ,
626640 900 ,
627641 HassJob (
628642 self ._async_schedule_basic ,
@@ -631,20 +645,19 @@ async def async_schedule(self) -> None:
631645 ),
632646 )
633647
634- if not self .preferences .get (ATTR_SNAPSHOTS , False ) or RELEASE_CHANNEL not in (
635- ReleaseChannel .DEV ,
636- ReleaseChannel .NIGHTLY ,
637- ):
648+ if not self .preferences .get (ATTR_SNAPSHOTS , False ) or self ._disable_snapshots :
638649 LOGGER .debug ("Snapshot analytics not scheduled" )
639650 if self ._snapshot_scheduled :
640651 self ._snapshot_scheduled ()
641652 self ._snapshot_scheduled = None
642653 elif self ._snapshot_scheduled is None :
643654 snapshot_submission_time = self ._data .snapshot_submission_time
644655
656+ interval_seconds = INTERVAL .total_seconds ()
657+
645658 if snapshot_submission_time is None :
646659 # Randomize the submission time within the 24 hours
647- snapshot_submission_time = random .uniform (0 , 86400 )
660+ snapshot_submission_time = random .uniform (0 , interval_seconds )
648661 self ._data .snapshot_submission_time = snapshot_submission_time
649662 await self ._save ()
650663 LOGGER .debug (
@@ -654,10 +667,10 @@ async def async_schedule(self) -> None:
654667
655668 # Calculate delay until next submission
656669 current_time = time .time ()
657- delay = (snapshot_submission_time - current_time ) % 86400
670+ delay = (snapshot_submission_time - current_time ) % interval_seconds
658671
659672 self ._snapshot_scheduled = async_call_later (
660- self .hass ,
673+ self ._hass ,
661674 delay ,
662675 HassJob (
663676 self ._async_schedule_snapshots ,
@@ -672,7 +685,7 @@ async def _async_schedule_basic(self, _: datetime | None = None) -> None:
672685
673686 # Send basic analytics every day
674687 self ._basic_scheduled = async_track_time_interval (
675- self .hass ,
688+ self ._hass ,
676689 self .send_analytics ,
677690 INTERVAL ,
678691 name = "basic analytics daily" ,
@@ -685,7 +698,7 @@ async def _async_schedule_snapshots(self, _: datetime | None = None) -> None:
685698
686699 # Send snapshot analytics every day
687700 self ._snapshot_scheduled = async_track_time_interval (
688- self .hass ,
701+ self ._hass ,
689702 self .send_snapshot ,
690703 INTERVAL ,
691704 name = "snapshot analytics daily" ,
0 commit comments