public async virtual Task DeviceCommandAsync(InstalledAppInstance installedApp,
                                                     string deviceId,
                                                     dynamic command)
        {
            _ = installedApp ?? throw new ArgumentNullException(nameof(installedApp));
            _ = deviceId ?? throw new ArgumentNullException(nameof(deviceId));
            _ = command ?? throw new ArgumentNullException(nameof(command));

            await RefreshTokensAsync(installedApp).ConfigureAwait(false);

            _logger.LogDebug($"Sending command: {command} to device: {deviceId}...");

            var uri = new Uri($"https://api.smartthings.com/devices/{deviceId}/commands");

            using var request = new HttpRequestMessage(HttpMethod.Post, uri);

            request.SetBearerAuthHeader(installedApp.AccessToken.TokenValue);

            request.Content = ((JObject)command).ToStringContent();

            var response = await _httpClient.SendAsync(request).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                _logger.LogError($"Error trying to exec command on device...  Response body: {errorBody}");
            }

            response.EnsureSuccessStatusCode();
        }
        public async virtual Task <dynamic> GetDeviceStatusAsync(InstalledAppInstance installedApp,
                                                                 string deviceId)
        {
            _ = installedApp ?? throw new ArgumentNullException(nameof(installedApp));
            _ = deviceId ?? throw new ArgumentNullException(nameof(deviceId));

            await RefreshTokensAsync(installedApp).ConfigureAwait(false);

            _logger.LogDebug($"Getting device status for device: {deviceId}...");

            var uri = new Uri($"https://api.smartthings.com/devices/{deviceId}/status");

            using var request = new HttpRequestMessage(HttpMethod.Get, uri);

            request.Headers.Authorization =
                new AuthenticationHeaderValue("Bearer", installedApp.AccessToken.TokenValue);

            var response = await _httpClient.SendAsync(request).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                _logger.LogError($"Error trying to get device status...  Response body: {errorBody}");
            }

            response.EnsureSuccessStatusCode();

            var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            dynamic device = JObject.Parse(body);

            return(device);
        }
        public static InstalledAppInstance GetValidInstalledAppInstance()
        {
            if (installedAppInstance == null)
            {
                installedAppInstance = new InstalledAppInstance()
                {
                    InstalledAppId    = "d692699d-e7a6-400d-a0b7-d5be96e7a564",
                    InstalledLocation = new Location()
                    {
                        CountryCode = "US",
                        Id          = "e675a3d9-2499-406c-86dc-8a492a88649",
                        Label       = "Home",
                        Latitude    = 40.347054,
                        Longitude   = -74.064308,
                        TempScale   = TemperatureScale.F,
                        TimeZoneId  = "America/New_York",
                        Locale      = "en"
                    }
                };
            }

            installedAppInstance.SetTokens(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (long)TimeSpan.FromDays(99).TotalMilliseconds);

            return(installedAppInstance);
        }
        public async Task HandleInstallUpdateDataAsync(InstalledAppInstance installedApp,
                                                       dynamic data,
                                                       bool shouldSubscribeToEvents = true)
        {
            _ = installedApp ??
                throw new ArgumentNullException(nameof(installedApp));

            var state = await stateManager.GetStateAsync(installedApp.InstalledAppId).ConfigureAwait(false);

            if (state == null)
            {
                state = new MyState()
                {
                    InstalledAppId = installedApp.InstalledAppId
                };
            }

            state.IsAppEnabled = bool.Parse(data.installedApp.config.isAppEnabled[0].stringConfig.value.Value);

            var loadTasks = new Task[] {
                LoadLightSwitchesAsync(installedApp,
                                       state,
                                       data,
                                       shouldSubscribeToEvents)
            };

            Task.WaitAll(loadTasks);

            Logger.LogDebug($"MyState: {state.ToJson()}");

            await stateManager.StoreStateAsync(installedApp.InstalledAppId, state).ConfigureAwait(false);

            Logger.LogInformation($"Updated config for installedApp: {installedApp.InstalledAppId}...");
        }
        public virtual async Task <InstalledAppInstance> RefreshTokensAsync(InstalledAppInstance installedApp)
        {
            _ = installedApp ??
                throw new ArgumentNullException(nameof(installedApp));
            _ = installedApp.InstalledAppId ??
                throw new ArgumentException("installedApp.InstalledAppId is null",
                                            nameof(installedApp));
            _ = installedApp.AccessToken ??
                throw new ArgumentException("installedApp.AccessToken is null",
                                            nameof(installedApp));
            _ = installedApp.RefreshToken ??
                throw new ArgumentException("installedApp.RefreshToken is null",
                                            nameof(installedApp));

            if (installedApp.RefreshToken.IsExpired)
            {
                throw new ArgumentException("installedApp.RefreshToken is expired!",
                                            nameof(installedApp));
            }

            _logger.LogDebug($"Refreshing tokens for installedApp: {installedApp.InstalledAppId}...");

            if (installedApp.AccessToken.IsExpired)
            {
                _logger.LogDebug($"installedApp: {installedApp.InstalledAppId} has an expired token, attempting to refresh...");
                installedApp = await _smartThingsAPIHelper.RefreshTokensAsync(installedApp).ConfigureAwait(false);
                await StoreInstalledAppAsync(installedApp).ConfigureAwait(false);
            }

            return(installedApp);
        }
        public async virtual Task <HttpResponseMessage> ClearSubscriptionsAsync(InstalledAppInstance installedApp)
        {
            _ = installedApp ?? throw new ArgumentNullException(nameof(installedApp));

            await RefreshTokensAsync(installedApp).ConfigureAwait(false);

            _logger.LogDebug($"Clearing device subscriptions for installedApp: {installedApp.InstalledAppId}...");

            var uri = new Uri($"https://api.smartthings.com/installedapps/{installedApp.InstalledAppId}/subscriptions");

            using var request = new HttpRequestMessage(HttpMethod.Delete, uri);

            request.SetBearerAuthHeader(installedApp.AccessToken.TokenValue);

            var response = await _httpClient.SendAsync(request).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                dynamic responseDetails = JObject.Parse(errorBody);
                _logger.LogError($"Error trying to clear subscriptions...  Response body: {errorBody}");
            }

            response.EnsureSuccessStatusCode();

            return(response);
        }
        public void SmartThingsAPIHelper_RefreshTokensAsync_Should_Not_Throw()
        {
            var installedApp = new InstalledAppInstance()
            {
                InstalledAppId    = Guid.NewGuid().ToString(),
                InstalledLocation = new Location()
                {
                    CountryCode = "US",
                    Id          = Guid.NewGuid().ToString(),
                    Label       = "Home",
                    Latitude    = 40.347054,
                    Longitude   = -74.064308,
                    TempScale   = TemperatureScale.F,
                    TimeZoneId  = "America/New_York",
                    Locale      = "en"
                }
            };

            installedApp.SetTokens(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), -100);

            _mockHttpMessageHandler.SetupRequest(HttpMethod.Post,
                                                 "https://auth-global.api.smartthings.com/oauth/token")
            .ReturnsResponse(@"
                {
                    ""access_token"":""foo"",
                    ""refresh_token"":""foo"",
                    ""expires_in"":""1""
                }");

            _smartThingsAPIHelper.RefreshTokensAsync(installedApp);
        }
        public async Task RefreshTokensAsync_ShouldReturnExpectedResult(InstalledAppInstance installedApp)
        {
            var at        = Guid.NewGuid().ToString();
            var rt        = Guid.NewGuid().ToString();
            var atExpires = TimeSpan.FromMinutes(5).ToString("c");
            var expiresDt = DateTime.Now.Add(TimeSpan.FromMinutes(5));

            var responseBody = $@"{{
                ""access_token"": ""{at}"",
                ""refresh_token"": ""{rt}"",
                ""expires_in"": ""{atExpires}""
            }}";

            _mockHttpMessageHandler.SetupRequest(HttpMethod.Post,
                                                 "https://auth-global.api.smartthings.com/oauth/token")
            .ReturnsResponse(responseBody);
            var result = await _smartThingsAPIHelper.RefreshTokensAsync(installedApp);

            Assert.NotNull(result);

            var expiresDiff = result.AccessToken.ExpiresDT.Subtract(expiresDt).Duration();

            Assert.Equal(installedApp.AccessToken.TokenValue,
                         result.AccessToken.TokenValue);
            Assert.True((expiresDiff <= TimeSpan.FromSeconds(45))); // Due to buffer and proc time
            Assert.False(result.AccessToken.IsExpired);
            Assert.Equal(installedApp.RefreshToken.TokenValue,
                         result.RefreshToken.TokenValue);
        }
        public static IEnumerable <object[]> ValidInstalledAppInstance()
        {
            var installedApp = new InstalledAppInstance()
            {
                InstalledAppId    = Guid.NewGuid().ToString(),
                InstalledLocation = new Location()
                {
                    CountryCode = "US",
                    Id          = Guid.NewGuid().ToString(),
                    Label       = "Home",
                    Latitude    = 40.347054,
                    Longitude   = -74.064308,
                    TempScale   = TemperatureScale.F,
                    TimeZoneId  = "America/New_York",
                    Locale      = "en"
                }
            };

            installedApp.SetTokens(Guid.NewGuid().ToString(), Guid.NewGuid().ToString());

            return(new List <object[]>
            {
                new object[] { installedApp }
            });
        }
        public async Task GetDeviceDetailsAsync_ShouldReturnExpectedResult(InstalledAppInstance installedApp)
        {
            var doorLock = new DoorLock()
            {
                CurrentState = LockState.Locked,
                Id           = Guid.NewGuid().ToString(),
                Label        = "Front door lock"
            };

            var responseBody = $@"{{
                ""deviceId"": ""{doorLock.Id}"",
                ""name"": ""Front.door.lock"",
                ""label"": ""{doorLock.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}""
            }}";

            dynamic expectedResult = JObject.Parse(responseBody);

            _mockHttpMessageHandler.SetupRequest(HttpMethod.Get,
                                                 $"{BaseUri}/devices/{doorLock.Id}")
            .ReturnsResponse(responseBody);

            var result = await _smartThingsAPIHelper.GetDeviceDetailsAsync(installedApp,
                                                                           doorLock.Id);

            Assert.Equal(expectedResult, result);
        }
Beispiel #11
0
        public async Task StateGetStateAsync_ShouldReturnExpectedResult(InstalledAppInstance installedApp)
        {
            var result = await _stateManager.GetStateAsync(installedApp.InstalledAppId);

            Assert.NotNull(result);
            Assert.Equal(CommonUtils.GetStateObject(), result);
        }
Beispiel #12
0
        public async Task StateStoreStateAsync_ShouldNotErrorAndShouldNotify(InstalledAppInstance installedApp)
        {
            var observer = new StateObserver(_mockStateLogger.Object);

            _stateManager.Subscribe(observer);
            await _stateManager.StoreStateAsync(installedApp.InstalledAppId, CommonUtils.GetStateObject());
        }
Beispiel #13
0
        public override async Task HandleEventDataAsync(InstalledAppInstance installedApp,
                                                        dynamic eventData)
        {
            _ = installedApp ??
                throw new ArgumentNullException(nameof(installedApp));
            _ = eventData ??
                throw new ArgumentNullException(nameof(eventData));

            Logger.LogDebug($"Handling eventData for installedApp: {installedApp.InstalledAppId}...");

            var state = await stateManager.GetStateAsync(installedApp.InstalledAppId).ConfigureAwait(false);

            if (state == null)
            {
                await installUpdateHandler.HandleUpdateDataAsync(installedApp, eventData, false);

                state = await stateManager.GetStateAsync(installedApp.InstalledAppId).ConfigureAwait(false);
            }

            _ = state ??
                throw new InvalidOperationException($"Unable to retrieve state for app: {installedApp.InstalledAppId}");

            var raisedEvents = eventData.events;

            Logger.LogDebug($"Handling raisedEvents for installedApp: {installedApp.InstalledAppId}...");

            var raisedEvent = raisedEvents[0];

            if (raisedEvent.deviceEvent != null)
            {
                Logger.LogDebug($"Handling raisedEvent for installedApp: {installedApp.InstalledAppId}:  {raisedEvent.deviceEvent}");
                await HandleDeviceEventAsync(state, raisedEvent.deviceEvent).ConfigureAwait(false);
            }
        }
        public void SmartThingsAPIHelper_RefreshTokensAsync_Should_HandleError()
        {
            var installedApp = new InstalledAppInstance()
            {
                InstalledAppId    = Guid.NewGuid().ToString(),
                InstalledLocation = new Location()
                {
                    CountryCode = "US",
                    Id          = Guid.NewGuid().ToString(),
                    Label       = "Home",
                    Latitude    = 40.347054,
                    Longitude   = -74.064308,
                    TempScale   = TemperatureScale.F,
                    TimeZoneId  = "America/New_York",
                    Locale      = "en"
                }
            };

            installedApp.SetTokens(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), -100);

            _mockHttpMessageHandler.SetupRequest(HttpMethod.Post,
                                                 "https://auth-global.api.smartthings.com/oauth/token")
            .ReturnsResponse(System.Net.HttpStatusCode.InternalServerError,
                             "ERROR");

            _smartThingsAPIHelper.RefreshTokensAsync(installedApp);
        }
Beispiel #15
0
        public async Task IAGetInstalledAppAsync_ShouldReturnExpectedResult(InstalledAppInstance installedApp)
        {
            var result = await _installedAppManager.GetInstalledAppAsync(installedApp.InstalledAppId);

            Assert.NotNull(result);
            Assert.Equal(installedApp.InstalledAppId, result.InstalledAppId);
            Assert.Equal(installedApp.InstalledLocation, result.InstalledLocation);
        }
        public async Task ClearSubscriptionsAsync_ShouldNotError(InstalledAppInstance installedApp)
        {
            var uri = $"{BaseUri}/installedapps/{installedApp.InstalledAppId}/subscriptions";

            _mockHttpMessageHandler.SetupRequest(HttpMethod.Delete,
                                                 uri)
            .ReturnsResponse("ok");

            var result = await _smartThingsAPIHelper.ClearSubscriptionsAsync(installedApp);

            Assert.NotNull(result);
        }
        public override async Task HandleInstallDataAsync(InstalledAppInstance installedApp,
                                                          dynamic data,
                                                          bool shouldSubscribeToEvents = true)
        {
            _ = installedApp ?? throw new ArgumentNullException(nameof(installedApp));
            _ = data ?? throw new ArgumentNullException(nameof(data));

            Logger.LogInformation($"Installing installedApp: {installedApp.InstalledAppId}...");

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
            Task.Run(() => HandleInstallUpdateDataAsync(installedApp, data, shouldSubscribeToEvents).ConfigureAwait(false));
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
        }
        public async Task <InstalledAppInstance> RefreshTokensAsync(InstalledAppInstance installedApp)
        {
            _ = installedApp ?? throw new ArgumentNullException(nameof(installedApp));

            if (installedApp.AccessToken.IsExpired ||
                installedApp.RefreshToken.IsExpired)
            {
                _logger.LogDebug($"Refreshing tokens for installedApp: {installedApp.InstalledAppId}...");

                var uri = new Uri($"https://auth-global.api.smartthings.com/oauth/token");

                using var request = new HttpRequestMessage(HttpMethod.Post, uri);

                request.SetBasicAuthHeader(_appConfig.SmartAppClientId,
                                           _appConfig.SmartAppClientSecret);

                var data = $"grant_type=refresh_token&client_id={_appConfig.SmartAppClientId}&client_secret={_appConfig.SmartAppClientSecret}&refresh_token={installedApp.RefreshToken.TokenValue}";
                request.Content = new StringContent(data, Encoding.UTF8, "application/x-www-form-urlencoded");

                var response = await _httpClient.SendAsync(request).ConfigureAwait(false);

                if (!response.IsSuccessStatusCode)
                {
                    var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                    _logger.LogError($"Error trying to refresh tokens...  Response body: {errorBody}");
                }

                response.EnsureSuccessStatusCode();

                var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                dynamic tokenDetails = JObject.Parse(body);

                _ = tokenDetails.access_token ?? throw new InvalidOperationException("tokenDetails.access_token == null!");
                _ = tokenDetails.refresh_token ?? throw new InvalidOperationException("tokenDetails.refresh_token == null!");
                _ = tokenDetails.expires_in ?? throw new InvalidOperationException("tokenDetails.expires_in == null!");

                _logger.LogDebug($"Setting tokens for installedApp: {installedApp.InstalledAppId}...");

                installedApp.SetTokens(tokenDetails.access_token.Value,
                                       tokenDetails.refresh_token.Value,
                                       tokenDetails.expires_in.Value);
            }
            else
            {
                _logger.LogDebug($"NOT Refreshing tokens for installedApp: {installedApp.InstalledAppId}, tokens not expired...");
            }

            return(installedApp);
        }
        public async Task SubscribeToDeviceEventAsync_ShouldReturnExpectedResult(InstalledAppInstance installedApp)
        {
            var doorLock = new DoorLock()
            {
                CurrentState = LockState.Locked,
                Id           = Guid.NewGuid().ToString(),
                Label        = "Front door lock"
            };

            var deviceConfig = $@"{{
                ""deviceId"": ""{doorLock.Id}"",
                ""name"": ""Front.door.lock"",
                ""label"": ""{doorLock.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}"",
                ""componentId"": ""main"",
                ""capability"": ""lock"",
                ""attribute"": ""lock"",
                ""stateChangeOnly"": ""true"",
                ""value"": ""*"",
                ""subscriptionName"": ""DOORLOCKS_1""
            }}";

            dynamic dynDevice = JObject.Parse(deviceConfig);


            var responseBody = $@"{{
                ""id"": ""{Guid.NewGuid()}"",
                ""installedAppId"": ""{installedApp.InstalledAppId}"",
                ""sourceType"": ""DEVICE"",
                ""device"": {deviceConfig}
            }}";

            dynamic expectedResult = JObject.Parse(responseBody);

            _mockHttpMessageHandler.SetupRequest(HttpMethod.Post,
                                                 $"{BaseUri}/installedapps/{installedApp.InstalledAppId}/subscriptions")
            .ReturnsResponse(responseBody);

            var response = await _smartThingsAPIHelper.SubscribeToDeviceEventAsync(installedApp,
                                                                                   dynDevice);


            var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            dynamic result = JObject.Parse(responseText);

            Assert.Equal(expectedResult, result);
        }
        public async Task DeviceCommandAsync_ShouldNotError(InstalledAppInstance installedApp)
        {
            var doorLock = new DoorLock()
            {
                CurrentState = LockState.Locked,
                Id           = Guid.NewGuid().ToString(),
                Label        = "Front door lock"
            };

            _mockHttpMessageHandler.SetupRequest(HttpMethod.Post,
                                                 $"{BaseUri}/devices/{doorLock.Id}/commands")
            .ReturnsResponse("ok");

            await _smartThingsAPIHelper.DeviceCommandAsync(installedApp,
                                                           doorLock.Id,
                                                           DoorLock.GetDeviceCommand(false));
        }
        public void AirQualitySensor_FromDynamic_ShouldReturnExpectedResult(InstalledAppInstance installedApp)
        {
            var expectedResult = new AirQualitySensor()
            {
                Id           = Guid.NewGuid().ToString(),
                Label        = "MyDevice",
                CurrentValue = 1
            };

            var deviceJson = $@"{{
                ""deviceId"": ""{expectedResult.Id}"",
                ""name"": ""NAME"",
                ""label"": ""{expectedResult.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}""
            }}";

            var statusJson = $@"{{
                ""deviceId"": ""{expectedResult.Id}"",
                ""name"": ""NAME"",
                ""label"": ""{expectedResult.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}"",
                ""components"" : {{
                    ""main"": {{
                        ""airQualitySensor"": {{
                            ""airQuality"": {{
                                ""value"": ""1""
                            }}
                        }}
                    }}
                }}
            }}";

            dynamic device = JObject.Parse(deviceJson);
            dynamic status = JObject.Parse(statusJson);

            var result = AirQualitySensor.AirQualitySensorFromDynamic(device,
                                                                      status);

            Assert.Equal(expectedResult, result);

            _ = expectedResult.GetHashCode();
            _ = expectedResult.ToJson();
        }
        public virtual async Task StoreInstalledAppAsync(InstalledAppInstance installedApp)
        {
            _ = installedApp ?? throw new ArgumentNullException(nameof(installedApp));
            _ = installedApp.InstalledAppId ??
                throw new ArgumentException($"installedApp.InstalledAppId is null",
                                            nameof(installedApp));
            _ = installedApp.AccessToken ??
                throw new ArgumentException($"installedApp.AccessToken is null",
                                            nameof(installedApp));

            await LoadCacheAsync().ConfigureAwait(false);

            _logger.LogDebug($"Adding installedApp to cache: {installedApp.InstalledAppId}...");

            InstalledAppCache.Remove(installedApp.InstalledAppId);
            InstalledAppCache.Add(installedApp.InstalledAppId, installedApp);

            await PersistCacheAsync().ConfigureAwait(false);
        }
        private async Task SubscribeToDeviceEventAsync(InstalledAppInstance installedApp,
                                                       dynamic deviceConfig)
        {
            _ = installedApp ??
                throw new ArgumentNullException(nameof(installedApp));
            _ = deviceConfig ??
                throw new ArgumentNullException(nameof(deviceConfig));

            var resp = await SmartThingsAPIHelper.SubscribeToDeviceEventAsync(
                installedApp,
                deviceConfig);

            var body = await resp.Content.ReadAsStringAsync();

            dynamic subscriptionResp = JObject.Parse(body);

            _ = subscriptionResp.id ??
                throw new InvalidOperationException("subscriptionResp.id is null!");
        }
        public async Task GetLocationAsync_ShouldReturnExpectedResult(InstalledAppInstance installedApp)
        {
            _mockHttpMessageHandler.SetupRequest(HttpMethod.Get,
                                                 $"{BaseUri}/locations/{installedApp.InstalledLocation.Id}")
            .ReturnsResponse($@"{{
                    ""locationId"": ""{installedApp.InstalledLocation.Id}"",
                    ""name"": ""{installedApp.InstalledLocation.Label}"",
                    ""latitude"": ""{installedApp.InstalledLocation.Latitude}"",
                    ""longitude"": ""{installedApp.InstalledLocation.Longitude}"",
                    ""regionRadius"": ""{installedApp.InstalledLocation.RegionRadius}"",
                    ""temperatureScale"": ""{installedApp.InstalledLocation.TempScale}"",
                    ""timeZoneId"": ""{installedApp.InstalledLocation.TimeZoneId}"",
                    ""countryCode"": ""{installedApp.InstalledLocation.CountryCode}"",
                    ""locale"": ""{installedApp.InstalledLocation.Locale}""
                }}");

            var result = await _smartThingsAPIHelper.GetLocationAsync(installedApp,
                                                                      installedApp.InstalledLocation.Id);

            Assert.Equal(installedApp.InstalledLocation, result);
        }
        public async virtual Task SendNotificationAsync(InstalledAppInstance installedApp,
                                                        string msg,
                                                        string title = "Automation Message")
        {
            _ = installedApp ?? throw new ArgumentNullException(nameof(installedApp));
            _ = msg ?? throw new ArgumentNullException(nameof(msg));

            await RefreshTokensAsync(installedApp).ConfigureAwait(false);

            _logger.LogDebug($"Sending notification: {msg}...");

            var uri = new Uri($"https://api.smartthings.com/notification");

            using var request = new HttpRequestMessage(HttpMethod.Post, uri);

            request.SetBearerAuthHeader(installedApp.AccessToken.TokenValue);

            var json = $@"{{
                ""type"": ""AUTOMATION_INFO"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}"",
                ""title"": ""{title}"",
			    ""message"": ""{msg}""
            }}";

            dynamic notification = JObject.Parse(json);

            request.Content = ((JObject)notification).ToStringContent();

            var response = await _httpClient.SendAsync(request).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                _logger.LogError($"Error trying to send notification...  Response body: {errorBody}");
            }

            response.EnsureSuccessStatusCode();
        }
        public async virtual Task <HttpResponseMessage> SubscribeToDeviceEventAsync(InstalledAppInstance installedApp,
                                                                                    dynamic device)
        {
            _ = installedApp ?? throw new ArgumentNullException(nameof(installedApp));
            _ = device ?? throw new ArgumentNullException(nameof(device));

            await RefreshTokensAsync(installedApp).ConfigureAwait(false);

            _logger.LogDebug($"Subscribing to device: {device.Id}...");

            var uri = new Uri($"https://api.smartthings.com/installedapps/{installedApp.InstalledAppId}/subscriptions");

            dynamic payload = new JObject();

            payload.sourceType = "DEVICE";
            payload.device     = device;

            using var jsonContent = ((JObject)payload).ToStringContent();

            using var request = new HttpRequestMessage(HttpMethod.Post, uri);

            request.Headers.Authorization =
                new AuthenticationHeaderValue("Bearer", installedApp.AccessToken.TokenValue);

            request.Content = jsonContent;

            var response = await _httpClient.SendAsync(request).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                _logger.LogError($"Error trying to subscribe to device...  Response body: {errorBody}");
            }

            response.EnsureSuccessStatusCode();

            return(response);
        }
        public async virtual Task <Location> GetLocationAsync(InstalledAppInstance installedApp,
                                                              string locationId)
        {
            _ = installedApp ?? throw new ArgumentNullException(nameof(installedApp));
            _ = locationId ?? throw new ArgumentNullException(nameof(locationId));

            await RefreshTokensAsync(installedApp).ConfigureAwait(false);

            _logger.LogDebug($"Getting location: {locationId}...");

            var uri = new Uri($"https://api.smartthings.com/locations/{locationId}");

            using var request = new HttpRequestMessage(HttpMethod.Get, uri);

            request.SetBearerAuthHeader(installedApp.AccessToken.TokenValue);

            var response = await _httpClient.SendAsync(request).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                _logger.LogError($"Error trying to get location...  Response body: {errorBody}");
            }

            response.EnsureSuccessStatusCode();

            var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

            if (string.IsNullOrWhiteSpace(body))
            {
                throw new InvalidOperationException("ST locations api returned an empty response");
            }

            return(Location.LocationFromDynamic(JObject.Parse(body)));
        }
 public abstract Task HandleEventDataAsync(InstalledAppInstance installedApp, dynamic eventData);
        public void WaterSensor_FromDynamic_ShouldReturnExpectedResult(InstalledAppInstance installedApp)
        {
            var expectedResult = new WaterSensor()
            {
                Id           = Guid.NewGuid().ToString(),
                Label        = "MyDevice",
                CurrentState = WaterState.Dry
            };

            var deviceJson = $@"{{
                ""deviceId"": ""{expectedResult.Id}"",
                ""name"": ""NAME"",
                ""label"": ""{expectedResult.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}""
            }}";

            var statusJson = $@"{{
                ""deviceId"": ""{expectedResult.Id}"",
                ""name"": ""NAME"",
                ""label"": ""{expectedResult.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}"",
                ""components"" : {{
                    ""main"": {{
                        ""waterSensor"": {{
                            ""water"": {{
                                ""value"": ""dry""
                            }}
                        }}
                    }}
                }}
            }}";

            dynamic device = JObject.Parse(deviceJson);
            dynamic status = JObject.Parse(statusJson);

            var result = WaterSensor.WaterSensorFromDynamic(device,
                                                            status);

            Assert.Equal(expectedResult, result);

            expectedResult.CurrentState = WaterState.Wet;
            statusJson = $@"{{
                ""deviceId"": ""{expectedResult.Id}"",
                ""name"": ""NAME"",
                ""label"": ""{expectedResult.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}"",
                ""components"" : {{
                    ""main"": {{
                        ""waterSensor"": {{
                            ""water"": {{
                                ""value"": ""wet""
                            }}
                        }}
                    }}
                }}
            }}";

            status = JObject.Parse(statusJson);

            result = WaterSensor.WaterSensorFromDynamic(device,
                                                        status);

            Assert.Equal(expectedResult, result);

            expectedResult.CurrentState = WaterState.Unknown;
            statusJson = $@"{{
                ""deviceId"": ""{expectedResult.Id}"",
                ""name"": ""NAME"",
                ""label"": ""{expectedResult.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}"",
                ""components"" : {{
                    ""main"": {{
                        ""waterSensor"": {{
                            ""water"": {{
                                ""value"": ""unknown""
                            }}
                        }}
                    }}
                }}
            }}";

            status = JObject.Parse(statusJson);

            result = WaterSensor.WaterSensorFromDynamic(device,
                                                        status);

            Assert.Equal(expectedResult, result);

            _ = expectedResult.GetHashCode();
            _ = expectedResult.ToJson();
        }
        public void LightSwitch_FromDynamic_ShouldReturnExpectedResult(InstalledAppInstance installedApp)
        {
            var expectedResult = new LightSwitch()
            {
                Id           = Guid.NewGuid().ToString(),
                Label        = "MyDevice",
                CurrentState = SwitchState.Off
            };

            var deviceJson = $@"{{
                ""deviceId"": ""{expectedResult.Id}"",
                ""name"": ""NAME"",
                ""label"": ""{expectedResult.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}""
            }}";

            var statusJson = $@"{{
                ""deviceId"": ""{expectedResult.Id}"",
                ""name"": ""NAME"",
                ""label"": ""{expectedResult.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}"",
                ""components"" : {{
                    ""main"": {{
                        ""switch"": {{
                            ""switch"": {{
                                ""value"": ""off""
                            }}
                        }}
                    }}
                }}
            }}";

            dynamic device = JObject.Parse(deviceJson);
            dynamic status = JObject.Parse(statusJson);

            var result = LightSwitch.SwitchFromDynamic(device,
                                                       status);

            Assert.Equal(expectedResult, result);

            expectedResult.CurrentState = SwitchState.On;
            statusJson = $@"{{
                ""deviceId"": ""{expectedResult.Id}"",
                ""name"": ""NAME"",
                ""label"": ""{expectedResult.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}"",
                ""components"" : {{
                    ""main"": {{
                        ""switch"": {{
                            ""switch"": {{
                                ""value"": ""on""
                            }}
                        }}
                    }}
                }}
            }}";

            status = JObject.Parse(statusJson);

            result = LightSwitch.SwitchFromDynamic(device,
                                                   status);

            Assert.Equal(expectedResult, result);

            expectedResult.CurrentState = SwitchState.Unknown;
            statusJson = $@"{{
                ""deviceId"": ""{expectedResult.Id}"",
                ""name"": ""NAME"",
                ""label"": ""{expectedResult.Label}"",
                ""locationId"": ""{installedApp.InstalledLocation.Id}"",
                ""components"" : {{
                    ""main"": {{
                        ""switch"": {{
                            ""switch"": {{
                                ""value"": ""unknown""
                            }}
                        }}
                    }}
                }}
            }}";

            status = JObject.Parse(statusJson);

            result = LightSwitch.SwitchFromDynamic(device,
                                                   status);

            Assert.Equal(expectedResult, result);

            _ = expectedResult.GetHashCode();
            _ = expectedResult.ToJson();
        }