Beispiel #1
0
        public async Task <HassEntities> GetEntities()
        {
            HassMessage servicesResult = await SendCommandAndWaitForResponse(new GetEntitiesCommand()).ConfigureAwait(false);

            var resultMessage =
                servicesResult.Result as HassEntities
                ?? throw new ApplicationException("Unexpected response from GetServices command");

            return(resultMessage);
        }
Beispiel #2
0
        internal virtual async ValueTask <HassMessage> SendCommandAndWaitForResponse(CommandMessage message, bool waitForResponse = true)
        {
            using var timerTokenSource = new CancellationTokenSource(SocketTimeout);
            // Make a combined token source with timer and the general cancel token source
            // The operations will cancel from ether one
            using var sendCommandTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
                      timerTokenSource.Token, CancelSource.Token);

            try
            {
                await SendMessage(message, waitForResponse).ConfigureAwait(false);

                if (!waitForResponse)
                {
                    return new HassMessage {
                               Success = true
                    }
                }
                ;

                while (true)
                {
                    HassMessage result = await _messageChannel.Reader.ReadAsync(sendCommandTokenSource.Token).ConfigureAwait(false);

                    if (result.Id == message.Id)
                    {
                        return(result);
                    }

                    // Not the response, push message back
                    bool res = _messageChannel.Writer.TryWrite(result);

                    if (!res)
                    {
                        throw new ApplicationException("Failed to write to message channel!");
                    }

                    // Delay for a short period to let the message arrive we are searching for
                    await Task.Delay(10).ConfigureAwait(false);
                }
            }
            catch (OperationCanceledException)
            {
                _logger.LogError($"Fail to send command {message.Type} and receive correct command within timeout. ");
                throw;
            }
            catch (Exception e)
            {
                _logger.LogError($"Fail to send command {message.Type}. ");
                _logger.LogDebug(e, "Fail to send command.");
                throw;
            }
        }
Beispiel #3
0
        /// <summary>
        ///     Gets the configuration of the connected Home Assistant instance
        /// </summary>
        public async Task <HassConfig> GetConfig()
        {
            HassMessage hassResult = await SendCommandAndWaitForResponse(new GetConfigCommand()).ConfigureAwait(false);

            object resultMessage =
                hassResult.Result ?? throw new ApplicationException("Unexpected response from command");

            var result = resultMessage as HassConfig;

            if (result != null)
            {
                return(result);
            }

            throw new ApplicationException($"The result not expected! {resultMessage}");
        }
Beispiel #4
0
        /// <summary>
        ///     Pings Home Assistant to check if connection is alive
        /// </summary>
        /// <param name="timeout">The timeout to wait for Home Assistant to return pong message</param>
        /// <returns>True if connection is alive.</returns>
        public async Task <bool> PingAsync(int timeout)
        {
            using var timerTokenSource = new CancellationTokenSource(timeout);
            // Make a combined token source with timer and the general cancel token source
            // The operations will cancel from ether one
            using var pingTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
                      timerTokenSource.Token, CancelSource.Token);

            try
            {
                await SendMessage(new HassPingCommand()).ConfigureAwait(false);

                HassMessage result = await _messageChannel.Reader.ReadAsync(pingTokenSource.Token).ConfigureAwait(false);

                if (result.Type == "pong")
                {
                    return(true);
                }
            }
            catch (OperationCanceledException) { } // Do nothing

            return(false);
        }
Beispiel #5
0
        /// <summary>
        ///     Calls a service to home assistant
        /// </summary>
        /// <param name="domain">The domain for the servie, example "light"</param>
        /// <param name="service">The service to call, example "turn_on"</param>
        /// <param name="serviceData">The service data, use anonymous types, se example</param>
        /// <param name="waitForResponse">If true, it wait for the response from Hass else just ignore</param>
        /// <example>
        ///     Following example turn on light
        ///     <code>
        /// var client = new HassClient();
        /// await client.ConnectAsync("192.168.1.2", 8123, false);
        /// await client.CallService("light", "turn_on", new {entity_id="light.myawesomelight"});
        /// await client.CloseAsync();
        /// </code>
        /// </example>
        /// <returns>True if successfully called service</returns>
        public async Task <bool> CallService(string domain, string service, object serviceData, bool waitForResponse = true)
        {
            try
            {
                HassMessage result = await SendCommandAndWaitForResponse(new CallServiceCommand
                {
                    Domain      = domain,
                    Service     = service,
                    ServiceData = serviceData
                }, waitForResponse).ConfigureAwait(false);

                return(result.Success ?? false);
            }
            catch (OperationCanceledException)
            {
                if (CancelSource.IsCancellationRequested)
                {
                    throw;
                }

                return(false); // Just timeout not canceled
            }
        }
Beispiel #6
0
        /// <summary>
        ///     Connect to Home Assistant
        /// </summary>
        /// <param name="url">The uri of the websocket</param>
        /// <param name="token">Authtoken from Home Assistant for access</param>
        /// <param name="getStatesOnConnect">Reads all states initially, this is the default behaviour</param>
        /// <returns>Returns true if successfully connected</returns>
        public async Task <bool> ConnectAsync(Uri url, string token,
                                              bool getStatesOnConnect = true)
        {
            if (url == null)
            {
                throw new ArgumentNullException(nameof(url), "Expected url to be provided");
            }

            var httpScheme = (url.Scheme == "ws") ? "http" : "https";

            if (url.Host == "supervisor")
            {
                // Todo: DO NOT HARD CODE URLs NOOB!
                _apiUrl = "http://supervisor/core/api";
            }
            else
            {
                _apiUrl = $"{httpScheme}://{url.Host}:{url.Port}/api";
            }


            // Check if we already have a websocket running
            if (_ws != null)
            {
                throw new InvalidOperationException("Already connected to the remote websocket.");
            }

            // Setup default headers for httpclient
            if (_httpClient != null)
            {
                _httpClient.DefaultRequestHeaders.Clear();
                _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
            }

            try
            {
                IClientWebSocket ws = _wsFactory.New() ?? throw new NullReferenceException("Websocket cant be null!");
                using var timerTokenSource = new CancellationTokenSource(SocketTimeout);
                // Make a combined token source with timer and the general cancel token source
                // The operations will cancel from ether one
                using var connectTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
                          timerTokenSource.Token, CancelSource.Token);

                await ws.ConnectAsync(url, connectTokenSource.Token).ConfigureAwait(false);

                if (ws.State == WebSocketState.Open)
                {
                    // Initialize the correct states when successfully connecting to the websocket
                    InitStatesOnConnect(ws);

                    // Do the authenticate and get the auhtorization response
                    HassMessage result = await HandleConnectAndAuthenticate(token, connectTokenSource).ConfigureAwait(false);

                    switch (result.Type)
                    {
                    case "auth_ok":
                        if (getStatesOnConnect)
                        {
                            var currentStates = await GetAllStates(connectTokenSource.Token).ConfigureAwait(false);

                            foreach (var state in currentStates)
                            {
                                States[state.EntityId] = state;
                            }
                        }

                        _logger.LogTrace($"Connected to websocket ({url}) on host {url.Host} and the api ({_apiUrl})");
                        return(true);

                    case "auth_invalid":
                        _logger.LogError($"Failed to authenticate ({result.Message})");
                        return(false);

                    default:
                        _logger.LogError($"Unexpected response ({result.Type})");
                        return(false);
                    }
                }

                _logger.LogDebug($"Failed to connect to websocket socket state: {ws.State}");


                return(false);
            }
            catch (Exception e)
            {
                _logger.LogError($"Failed to connect to Home Assistant on {url}");
                _logger.LogDebug(e, $"Failed to connect to Home Assistant on {url}");
            }

            return(false);
        }