public static async Task Run(GeotabDataOnlyPlanAPI api)
        {
            ConsoleUtility.LogExampleStarted(typeof(GetFeedLogRecordAsyncExample).Name);

            try
            {
                // Feed parameters.
                int      getFeedNumberOfCallsToMake       = 5;
                int      getFeedSecondsToWaitBetweenCalls = 5;
                DateTime getFeedStartTime = DateTime.UtcNow - TimeSpan.FromDays(1);
                // See MyGeotab SDK <a href="https://geotab.github.io/sdk/software/guides/concepts/#result-limits">Result Limits</a> and <a href="https://geotab.github.io/sdk/software/api/reference/#M:Geotab.Checkmate.Database.DataStore.GetFeed1">GetFeed()</a> documentation for information about the feed result limit defined below.
                int getFeedresultsLimit = 50000;

                long?feedVersion;
                FeedResult <LogRecord> feedResult;

                // Make initial GetFeed call using the "seed" time.  The returned toVersion will be used as the fromVersion to start the subsequent GetFeed loop.
                feedResult = await api.GetFeedLogRecordAsync(getFeedStartTime, getFeedresultsLimit);

                feedVersion = feedResult.ToVersion;

                // Log results to console.
                Console.WriteLine($"Initial feed start time: {getFeedStartTime.ToString()}");
                Console.WriteLine($"Initial FeedResult ToVersion: {feedVersion.ToString()}");
                Console.WriteLine($"Initial FeedResult Records: {feedResult.Data.Count.ToString()}");
                if (feedResult.Data.Count > 0)
                {
                    Console.WriteLine($"Initial FeedResult first record DateTime: {feedResult.Data[0].DateTime.ToString()}");
                    Console.WriteLine($"Initial FeedResult last record DateTime: {feedResult.Data[feedResult.Data.Count - 1].DateTime.ToString()}");
                }

                // Execute a GetFeed loop for the prescribed number of iterations, setting the fromVersion on the first iteration to the toVersion that was returned by the initial GetFeed call.
                for (int getFeedCallNumber = 1; getFeedCallNumber < getFeedNumberOfCallsToMake + 1; getFeedCallNumber++)
                {
                    // Make GetFeed call.
                    feedResult = await api.GetFeedLogRecordAsync(feedVersion, getFeedresultsLimit);

                    feedVersion = feedResult.ToVersion;

                    // Log results to console.
                    Console.WriteLine($"Feed iteration: {getFeedCallNumber.ToString()}");
                    Console.WriteLine($"Feed iteration: {getFeedCallNumber.ToString()} FeedResult ToVersion: {feedVersion.ToString()}");
                    Console.WriteLine($"Feed iteration: {getFeedCallNumber.ToString()} FeedResult Records: {feedResult.Data.Count.ToString()}");
                    if (feedResult.Data.Count > 0)
                    {
                        Console.WriteLine($"Feed iteration: {getFeedCallNumber.ToString()} FeedResult first record DateTime: {feedResult.Data[0].DateTime.ToString()}");
                        Console.WriteLine($"Feed iteration: {getFeedCallNumber.ToString()} FeedResult last record DateTime: {feedResult.Data[feedResult.Data.Count - 1].DateTime.ToString()}");
                    }
                    // Wait for the prescribed amount of time before making the next GetFeed call.
                    Thread.Sleep(getFeedSecondsToWaitBetweenCalls * 1000);
                }
            }
            catch (Exception ex)
            {
                ConsoleUtility.LogError(ex);
            }

            ConsoleUtility.LogExampleFinished(typeof(GetFeedLogRecordAsyncExample).Name);
        }
        /// <summary>
        /// Requests <see cref="LogRecord"/>, <see cref="FaultData"/> and <see cref="StatusData"/> records via data feeds.  Then, updates local caches of "lookup" data.  Finally, iterates through the returned objects, "hydrating" important child objects using their fully-populated counterparts in the caches.
        /// </summary>
        /// <param name="feedParameters">Contains the latest data tokens and collections to be used in the next set of data feed calls.</param>
        /// <returns><see cref="FeedResultData"/></returns>
        public async Task <FeedResultData> GetFeedDataAsync(FeedParameters feedParameters)
        {
            FeedResultData          feedResults = new(new List <LogRecord>(), new List <StatusData>(), new List <FaultData>());
            FeedResult <LogRecord>  feedLogRecordData;
            FeedResult <StatusData> feedStatusData = null;
            FeedResult <FaultData>  feedFaultData  = null;

            try
            {
                if (feedParameters.FeedStartOption == Common.FeedStartOption.CurrentTime || feedParameters.FeedStartOption == Common.FeedStartOption.SpecificTime)
                {
                    // If the feeds are to be started at the current date/time or at a specific date/time, get the appropriate DateTime value and then use it to populate the fromDate parameter when making the GetFeed() calls.
                    DateTime feedStartTime = DateTime.UtcNow;
                    if (feedParameters.FeedStartOption == Common.FeedStartOption.SpecificTime)
                    {
                        feedStartTime = feedParameters.FeedStartSpecificTimeUTC;
                    }
                    feedLogRecordData = await api.GetFeedLogRecordAsync(feedStartTime);

                    ConsoleUtility.LogListItem("GPS log records received:", feedLogRecordData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedLogRecordData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData);
                    if (UseStatusDataFeed == true)
                    {
                        feedStatusData = await api.GetFeedStatusDataAsync(feedStartTime);

                        ConsoleUtility.LogListItem("StatusData records received:", feedStatusData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedStatusData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData);
                    }
                    if (UseFaultDataFeed == true)
                    {
                        feedFaultData = await api.GetFeedFaultDataAsync(feedStartTime);

                        ConsoleUtility.LogListItem("FaultData records received:", feedFaultData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedFaultData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData);
                    }
                    // Switch to FeedResultToken for subsequent calls.
                    feedParameters.FeedStartOption = Common.FeedStartOption.FeedResultToken;
                }
                else
                {
                    // If the feeds are to be called based on feed result token, use the tokens returned in the toVersion of previous GetFeed() calls (or loaded from file, if continuing where processing last left-off) to populate the fromVersion parameter when making the next GetFeed() calls.
                    feedLogRecordData = await api.GetFeedLogRecordAsync(feedParameters.LastGpsDataToken);

                    ConsoleUtility.LogListItem("GPS log records received:", feedLogRecordData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedLogRecordData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData);
                    if (UseStatusDataFeed == true)
                    {
                        feedStatusData = await api.GetFeedStatusDataAsync(feedParameters.LastStatusDataToken);

                        ConsoleUtility.LogListItem("StatusData records received:", feedStatusData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedStatusData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData);
                    }
                    if (UseFaultDataFeed == true)
                    {
                        feedFaultData = await api.GetFeedFaultDataAsync(feedParameters.LastFaultDataToken);

                        ConsoleUtility.LogListItem("FaultData records received:", feedFaultData.Data.Count.ToString(), Common.ConsoleColorForListItems, (feedFaultData.Data.Count > 0) ? Common.ConsoleColorForChangedData : Common.ConsoleColorForUnchangedData);
                    }
                }

                // Update local caches of "lookup" data.
                if (DateTime.UtcNow > nextCacheRepopulationTime)
                {
                    // "Feedless" caches are for object types not available via data feed in the MyGeotab API.  In this case, it is necessary to updates all of the objects of each type with every update.  Since these lookup data objects are not frequently-changing (if they were, they would be accessible via data feed), these caches are only updated on a specified interval instead of on every call to this GetFeedDataAsync() method in order to avoid unnecessary processing.
                    await UpdateAllFeedlessCachesAsync();

                    nextCacheRepopulationTime = DateTime.UtcNow.AddMinutes(CacheRepopulationIntervalMinutes);
                }
                // For object types supported by the MyGeotab API data feed, the local caches can be updated every time this GetFeedDataAsync() method is executed bacause only new or changed objects are returned.
                await UpdateDeviceCacheAsync();
                await UpdateDiagnosticCacheAsync();

                // Use the local caches to "hydrate" child objects of objects returned via data feed.
                feedParameters.LastGpsDataToken = feedLogRecordData.ToVersion;
                foreach (LogRecord logRecord in feedLogRecordData.Data)
                {
                    // Populate relevant LogRecord fields.
                    logRecord.Device = GetDevice(logRecord.Device);
                    feedResults.GpsRecords.Add(logRecord);
                }
                if (UseStatusDataFeed == true)
                {
                    feedParameters.LastStatusDataToken = feedStatusData.ToVersion;
                    foreach (StatusData statusData in feedStatusData.Data)
                    {
                        // Populate relevant StatusData fields.
                        statusData.Device     = GetDevice(statusData.Device);
                        statusData.Diagnostic = GetDiagnostic(statusData.Diagnostic);
                        feedResults.StatusData.Add(statusData);
                    }
                }
                if (UseFaultDataFeed == true)
                {
                    feedParameters.LastFaultDataToken = feedFaultData.ToVersion;
                    foreach (FaultData faultData in feedFaultData.Data)
                    {
                        // Populate relevant FaultData fields.
                        faultData.Device     = GetDevice(faultData.Device);
                        faultData.Diagnostic = GetDiagnostic(faultData.Diagnostic);
                        faultData.Controller = await GetControllerAsync(faultData.Controller);

                        faultData.FailureMode = await GetFailureModeAsync(faultData.FailureMode);

                        feedResults.FaultData.Add(faultData);
                    }
                }
            }
            catch (Exception e)
            {
                Console.ForegroundColor = Common.ConsoleColorForErrors;
                Console.WriteLine(e.Message);
                Console.ForegroundColor = Common.ConsoleColorDefault;
                if (e is HttpRequestException)
                {
                    await Task.Delay(5000);
                }
                if (e is DbUnavailableException)
                {
                    await Task.Delay(TimeSpan.FromMinutes(5));
                }
            }
            return(feedResults);
        }