Example #1
0
        internal static async Task InformLastContactAsync(string command)
        {
            command += $" Client ip: {GetClientIp()}";
            await HomeObject.SetValueAsync("LastHeardFromAlexa", DateTime.Now.ToString(CultureInfo.CurrentCulture)).ConfigureAwait(false);

            await HomeObject.SetValueAsync("LastHeardCommand", command).ConfigureAwait(false);
        }
Example #2
0
        public static async Task IncrementCounterAsync(string property)
        {
            int count = await HomeObject.GetValueAsync <int>(property).ConfigureAwait(false);

            count++;
            await HomeObject.SetValueAsync(property, count.ToString()).ConfigureAwait(false);
        }
Example #3
0
        public static async Task NotifyErrorAsync(EventLogEntryType errorType, string errorMessage, int id)
        {
            await HomeObject.SetValueAsync("AlexaLastCommunicationsError", errorMessage).ConfigureAwait(false);

            await IncrementCounterAsync("AlexaCommunicationsErrorCount").ConfigureAwait(false);

            WriteToWindowsApplicationEventLog(errorType, errorMessage, id);
            Debug.WriteLine(errorMessage);
        }
Example #4
0
        private static void SendStateChangeReportsToAlexa()
        {
            const int    eventID     = 20;
            const string errorPrefix = "Proactive state update error:";

            // This queue blocks in the enumerator so this is essentially an infinite loop.
            foreach (StateChangeReportWrapper item in stateReportQueue)
            {
                if (BackgroundTaskManager.Shutdown.IsCancellationRequested)
                {
                    BackgroundTaskManager.Shutdown.ThrowIfCancellationRequested();
                    return;
                }
                WebRequest request;
                try
                {
                    string expiry;
                    using (asyncObjectsLock.Lock())
                    {
                        expiry = HomeObject.GetValueAsync <string>("AlexaAsyncAuthorizationCodeExpiry").GetAwaiter()
                                 .GetResult();
                    }
                    if (string.IsNullOrEmpty(expiry))
                    {
                        Task.Run(async() => { await HomeObject.SetValueAsync("SendAsyncEventsToAlexa", "False").ConfigureAwait(false); });
                        NotifyErrorAsync(EventLogEntryType.Error,
                                         $"{errorPrefix}: no PSU expiry datetime. PSUs are now disabled. Enable premise skill.",
                                         eventID + 1).GetAwaiter().GetResult();
                    }
                    if (!DateTime.TryParse(expiry, out var expiryDateTime))
                    {
                        Task.Run(async() => { await HomeObject.SetValueAsync("SendAsyncEventsToAlexa", "False").ConfigureAwait(false); });
                        NotifyErrorAsync(EventLogEntryType.Error,
                                         $"{errorPrefix} Cannot parse expiry date. PSUs are now disabled. Enable premise skill.",
                                         eventID + 1).GetAwaiter().GetResult();
                        continue;
                    }

                    // refresh auth token if expired
                    if (DateTime.Compare(DateTime.UtcNow, expiryDateTime) >= 0)
                    {
                        RefreshAsyncToken(out string newToken);
                        if (string.IsNullOrEmpty(newToken)
                            ) // Error occurred during refresh - error logged in that method.
                        {
                            continue;
                        }
                        [email protected] = newToken;
                        foreach (var queuedItem in stateReportQueue.InternalQueue)
                        {
                            [email protected] = newToken;
                        }
                    }

                    request               = WebRequest.Create(item.uri);
                    request.Method        = WebRequestMethods.Http.Post;
                    request.ContentType   = @"application/json";
                    request.ContentLength = item.Json.Length;

                    Stream stream = request.GetRequestStream();
                    stream.Write(item.Bytes, 0, (int)request.ContentLength);
                    stream.Close();
                }
                catch (Exception ex)
                {
                    NotifyErrorAsync(EventLogEntryType.Error, $"{errorPrefix}: Unexpected error: {ex.Message}", eventID + 1)
                    .GetAwaiter().GetResult();
                    continue;
                }

                try
                {
                    using (HttpWebResponse httpResponse = request.GetResponse() as HttpWebResponse)
                    {
                        if (httpResponse == null ||
                            !(httpResponse.StatusCode == HttpStatusCode.OK ||
                              httpResponse.StatusCode == HttpStatusCode.Accepted))
                        {
                            NotifyErrorAsync(EventLogEntryType.Error,
                                             $"{errorPrefix} Unexpected status ({httpResponse?.StatusCode})", eventID + 2)
                            .GetAwaiter().GetResult();
                            continue;
                        }

                        string responseString;

                        using (Stream response = httpResponse.GetResponseStream())
                        {
                            if (response == null)
                            {
                                NotifyErrorAsync(EventLogEntryType.Warning, $"{errorPrefix} Null response from request.",
                                                 eventID + 3).GetAwaiter().GetResult();
                                continue;
                            }
                            StreamReader reader = new StreamReader(response, Encoding.UTF8);
                            responseString = reader.ReadToEnd();
                        }
                        IncrementCounterAsync("AlexaAsyncUpdateCount").GetAwaiter().GetResult();
                        Debug.WriteLine($"PSU Response Status ({httpResponse.StatusCode}) ContentLength: {httpResponse.ContentLength} Content: {responseString}");
                        Debug.WriteLine(item.Json);
                    }
                }
                catch (WebException e)
                {
                    using (WebResponse webresponse = e.Response)
                    {
                        HttpWebResponse httpResponse = (HttpWebResponse)webresponse;

                        switch (httpResponse.StatusCode)
                        {
                        case HttpStatusCode.Unauthorized
                            : // The skill is enabled, but the authentication token has expired.
                            RefreshAsyncToken(out string newToken);
                            if (string.IsNullOrEmpty(newToken)
                                ) // Error occurred during refresh - error logged in that method.
                            {
                                continue;
                            }
                            [email protected] = newToken;
                            foreach (var queuedItem in stateReportQueue.InternalQueue)
                            {
                                [email protected] = newToken;
                            }
                            stateReportQueue.Enqueue(item);
                            continue;

                        case HttpStatusCode.Forbidden
                            : // The skill is disabled so disable sending Async events to Alexa
                            Task.Run(async() => { await HomeObject.SetValueAsync("SendAsyncEventsToAlexa", "False").ConfigureAwait(false); });
                            NotifyErrorAsync(EventLogEntryType.Error,
                                             $"{errorPrefix} Premise skill has been disabled. PSUs are now disabled. Enable premise skill.",
                                             eventID + 4).GetAwaiter().GetResult();
                            continue;

                        case HttpStatusCode.BadRequest:
                            NotifyErrorAsync(EventLogEntryType.Warning,
                                             $"{errorPrefix} The message contains invalid identifying information such as a invalid endpoint Id or correlation token. Message:\r\n {item.Json}",
                                             eventID + 5).GetAwaiter().GetResult();
                            continue;

                        default:
                            NotifyErrorAsync(EventLogEntryType.Error, $"{errorPrefix} Unexpected status: {e.Message}",
                                             eventID + 6).GetAwaiter().GetResult();
                            continue;
                        }
                    }
                }
                catch (Exception ex)
                {
                    NotifyErrorAsync(EventLogEntryType.Error, $"{errorPrefix} Unexpected error: {ex.Message}", eventID + 7)
                    .GetAwaiter().GetResult();
                    continue;
                }

                Thread.Sleep(100); // throttle per spec
            }
        }
        private static void RefreshAsyncToken(out string newToken)
        {
            const int    eventID     = 10;
            const string errorPrefix = "Refresh async token error:";

            using (asyncObjectsLock.Lock())
            {
                string     previousToken;
                WebRequest refreshRequest;
                newToken = "";

                try
                {
                    refreshRequest             = WebRequest.Create(AlexaEventTokenRefreshEndpoint);
                    refreshRequest.Method      = WebRequestMethods.Http.Post;
                    refreshRequest.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
                    previousToken = HomeObject.GetValueAsync <string>("AlexaAsyncAuthorizationCode").GetAwaiter().GetResult();
                    string refresh_token = HomeObject.GetValueAsync <string>("AlexaAsyncAuthorizationRefreshToken").GetAwaiter().GetResult();
                    string client_id     = HomeObject.GetValueAsync <string>("AlexaAsyncAuthorizationClientId").GetAwaiter().GetResult();
                    string client_secret = HomeObject.GetValueAsync <string>("AlexaAsyncAuthorizationSecret").GetAwaiter().GetResult();
                    if (string.IsNullOrEmpty(refresh_token) || string.IsNullOrEmpty(client_id) || string.IsNullOrEmpty(client_secret))
                    {
                        Task.Run(async() => { await HomeObject.SetValueAsync("SendAsyncEventsToAlexa", "False").ConfigureAwait(false); });
                        NotifyErrorAsync(EventLogEntryType.Warning, $"{errorPrefix} Alexa authorization token is missing. PSUs are now disabled. Re-enable Premise skill.", eventID + 1).GetAwaiter().GetResult();
                        return;
                    }
                    string refreshData = $"grant_type=refresh_token&refresh_token={refresh_token}&client_id={client_id}&client_secret={client_secret}";
                    Stream stream      = refreshRequest.GetRequestStream();
                    stream.Write(Encoding.UTF8.GetBytes(refreshData), 0, Encoding.UTF8.GetByteCount(refreshData));
                    stream.Close();
                }
                catch (Exception e)
                {
                    NotifyErrorAsync(EventLogEntryType.Error, $"{errorPrefix} Unexpected error: {e.Message}", eventID + 2).GetAwaiter().GetResult();
                    return;
                }

                try
                {
                    using (HttpWebResponse httpResponse = refreshRequest.GetResponse() as HttpWebResponse)
                    {
                        if (httpResponse == null || !(httpResponse.StatusCode == HttpStatusCode.OK || httpResponse.StatusCode == HttpStatusCode.Accepted))
                        {
                            NotifyErrorAsync(EventLogEntryType.Error, $"{errorPrefix} Null response or unexpected status: ({httpResponse?.StatusCode})", eventID + 3).GetAwaiter().GetResult();
                            return;
                        }

                        string responseString;

                        using (Stream response = httpResponse.GetResponseStream())
                        {
                            if (response == null)
                            {
                                NotifyErrorAsync(EventLogEntryType.Warning, $"{errorPrefix} Null response from Amazon.", eventID + 4).GetAwaiter().GetResult();
                                return;
                            }
                            StreamReader reader = new StreamReader(response, Encoding.UTF8);
                            responseString = reader.ReadToEnd();
                        }

                        JObject json = JObject.Parse(responseString);

                        newToken = json["access_token"].ToString();
                        HomeObject.SetValueAsync("AlexaAsyncAuthorizationCode", newToken).GetAwaiter().GetResult();
                        HomeObject.SetValueAsync("AlexaAsyncAuthorizationRefreshToken", json["refresh_token"].ToString()).GetAwaiter().GetResult();
                        DateTime expiry = DateTime.UtcNow.AddSeconds((double)json["expires_in"]);
                        HomeObject.SetValueAsync("AlexaAsyncAuthorizationCodeExpiry", expiry.ToString(CultureInfo.InvariantCulture)).GetAwaiter().GetResult();
                        Debug.WriteLine("async token refresh response:" + responseString);
                        WriteToWindowsApplicationEventLog(EventLogEntryType.Information, $"Alexa async auth token successfully refreshed. Previous Token (hash):{previousToken.GetHashCode()} New Token (hash):{newToken.GetHashCode()}", eventID);
                    }
                }
                catch (WebException e)
                {
                    using (WebResponse webresponse = e.Response)
                    {
                        HttpWebResponse httpResponse = (HttpWebResponse)webresponse;

                        if (httpResponse.StatusCode == HttpStatusCode.Unauthorized
                            ) // skill has ben disabled so disable sending Async events to Alexa
                        {
                            Task.Run(async() => { await HomeObject.SetValueAsync("SendAsyncEventsToAlexa", "False").ConfigureAwait(false); });

                            string responseString;
                            using (Stream response = e.Response.GetResponseStream())
                            {
                                if (response == null)
                                {
                                    NotifyErrorAsync(EventLogEntryType.Error, $"{errorPrefix} Null response.", eventID + 5).GetAwaiter().GetResult();
                                    return;
                                }
                                StreamReader reader = new StreamReader(response, Encoding.UTF8);
                                responseString = reader.ReadToEnd();
                            }
                            JObject json    = JObject.Parse(responseString);
                            string  message = $"{errorPrefix} PSUs are disabled. ErrorInfo: {json["error"]} Description: {json["error_description"]} More info: {json["error_uri"]}";
                            NotifyErrorAsync(EventLogEntryType.Error, message, eventID + 6).GetAwaiter().GetResult();
                            return;
                        }
                        NotifyErrorAsync(EventLogEntryType.Error, $"{errorPrefix} Unexpected error status ({httpResponse.StatusCode})", eventID + 7).GetAwaiter().GetResult();
                    }
                }
                catch (Exception e)
                {
                    NotifyErrorAsync(EventLogEntryType.Error, $"{errorPrefix} Unexpected error: {e.Message}", eventID + 8).GetAwaiter().GetResult();
                }
            }
        }