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); }
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; } }
/// <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}"); }
/// <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); }
/// <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 } }
/// <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); }