Example #1
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 #2
0
        internal static async Task <bool> CheckAccessTokenAsync(string token)
        {
            var accessToken = await HomeObject.GetValueAsync <string>("AccessToken").ConfigureAwait(false);

            var tokens = new List <string>(accessToken.Split(','));

            return(-1 != tokens.IndexOf(token));
        }
Example #3
0
        private static bool CheckStatus()
        {
            try
            {
                if (BackgroundTaskManager.Shutdown.IsCancellationRequested)
                {
                    return(false);
                }
                if (IsClientConnected())
                {
                    return(true);
                }

                if (_homeObject == null)
                {
                    using (HomeObjectLock.Lock())
                    {
                        try
                        {
                            _homeObject = _sysClient.ConnectAsync(PremiseServerAddress)?.GetAwaiter().GetResult();
                        }
                        catch (Exception ex)
                        {
                            Debug.WriteLine(ex.Message);
                            _homeObject = null;
                        }
                    }

                    if (_homeObject == null)
                    {
                        Debug.WriteLine("Cannot connect to Premise server!");
                        return(false);
                    }
                }
                else
                {
                    _sysClient.Disconnect();

                    using (homeObjectSubscriptionLock.Lock())
                    {
                        _asyncEventSubscription = null;
                    }
                    using (subscriptionsLock.Lock())
                    {
                        subscriptions.Clear();
                        _sysClient.Subscriptions.Clear();
                    }
                    using (endpointsLock.Lock())
                    {
                        endpoints.Clear();
                    }
                    using (HomeObjectLock.Lock())
                    {
                        _homeObject = null;
                        try
                        {
                            _homeObject = _sysClient.ConnectAsync(PremiseServerAddress).GetAwaiter().GetResult();
                        }
                        catch
                        {
                            _homeObject = null;
                            return(false);
                        }
                    }
                }
                using (homeObjectSubscriptionLock.Lock())
                {
                    enableAsyncEvents = HomeObject.GetValueAsync <bool>("SendAsyncEventsToAlexa").GetAwaiter().GetResult();
                }
                if (IsAsyncEventsEnabled)
                {
                    Task.Run(SubscribeAllAsync);
                }
                SubScribeToHomeObjectEvents();
                return(true);
            }
            catch (Exception ex)
            {
                _homeObject = null;
                subscriptions.Clear();
                endpoints.Clear();
                Debug.WriteLine(ex.Message);
                return(false);
            }
        }
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
            }
        }
Example #5
0
        public static void AlexaPropertyChanged(dynamic @params)
        {
            Subscription sub = (Subscription)@params;

            using (deDupeLock.Lock())
            {
                // Premise can send multiple notifications for a single object, one for each
                // subscribed property that changes. The state report function here will capture
                // states of all properties, so the DeDupeDictionary prevents multiple state reports
                // from being sent for essentially the same event.
                if (DeDupeDictionary.ContainsKey(sub.sysObjectId))
                {
                    return;
                }

                DeDupeDictionary.Add(sub.sysObjectId, sub);
            }

            Task.Run(() =>
            {
                // get the endpoint and endpoint capabilities
                Guid premiseId          = new Guid(sub.sysObjectId);
                IPremiseObject endpoint = HomeObject.GetObjectAsync(premiseId.ToString("B")).GetAwaiter().GetResult();
                if (!endpoint.IsValidObject())
                {
                    return;
                }

                DiscoveryEndpoint discoveryEndpoint = GetDiscoveryEndpointAsync(endpoint).GetAwaiter().GetResult();
                if (discoveryEndpoint == null)
                {
                    return;
                }

                // get the authorization code for the notification
                string authCode;
                using (asyncObjectsLock.Lock())
                {
                    authCode = (string)HomeObject.GetValueAsync("AlexaAsyncAuthorizationCode").GetAwaiter().GetResult();
                }

                // build the change report
                AlexaChangeReport changeReport                = new AlexaChangeReport();
                [email protected]          = Guid.NewGuid().ToString("D");
                [email protected].@namespace         = "Alexa";
                [email protected]     = "3";
                [email protected]       = "BearerToken";
                [email protected]      = authCode;
                [email protected]       = premiseId.ToString("D").ToUpper();
                [email protected]           = discoveryEndpoint.cookie;
                [email protected] = "PHYSICAL_INTERACTION";

                // get the device type and controller (e.g. AlexaAV, AlexaHVAC)
                IAlexaDeviceType deviceType = null;
                IAlexaController controller = null;
                List <AlexaProperty> relatedPropertyStates = null;

                bool hasScene = false;

                foreach (IAlexaController controllerToTest in Controllers.Values)
                {
                    if (!controllerToTest.HasPremiseProperty(sub.propertyName))
                    {
                        continue;
                    }

                    controller = controllerToTest;
                    Type type  = Type.GetType(controller.GetAssemblyTypeName());
                    if (type == null)
                    {
                        continue;
                    }

                    // found a controller, get an instance of the assembly
                    deviceType = (IAlexaDeviceType)Activator.CreateInstance(type);

                    // Determine if this deviceType supports the desired capability
                    // note: This handles situation where the same property name is used by different
                    // controllers. e.g. "brightness" is used in both ColorController and BrightnessController
                    relatedPropertyStates = deviceType.FindRelatedProperties(endpoint, "");
                    foreach (AlexaProperty property in relatedPropertyStates)
                    {
                        // if so, this is the correct type
                        if (property.@namespace == controller.GetNameSpace())
                        {
                            break;
                        }
                    }
                }

                // ReSharper disable once ConditionIsAlwaysTrueOrFalse
                if ((deviceType == null) || (controller == null || relatedPropertyStates == null))
                {
                    return;
                }

                foreach (AlexaProperty prop in relatedPropertyStates)
                {
                    if (prop.@namespace == "Alexa.SceneController")
                    {
                        hasScene = true;
                        continue;
                    }

                    if (([email protected] == 0) && (prop.name == controller.MapPremisePropertyToAlexaProperty(sub.propertyName)))
                    {
                        [email protected](prop);
                    }
                    else
                    {
                        string propKey = prop.@namespace + "." + prop.name;
                        changeReport.context.propertiesInternal.Add(propKey, prop);
                    }
                }

                [email protected] = "ChangeReport";

                // scenes are special case
                if (hasScene)
                {
                    AlexaSetSceneController sceneController = new AlexaSetSceneController(endpoint);
                    changeReport = sceneController.AlterChangeReport(changeReport);
                }

                StateChangeReportWrapper item = new StateChangeReportWrapper
                {
                    ChangeReport = changeReport
                };

                stateReportQueue.Enqueue(item);

                using (deDupeLock.Lock())
                {
                    DeDupeDictionary.Remove(sub.sysObjectId);
                }
            });
        }
        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();
                }
            }
        }