Skip to content

Commit

Permalink
several changes to lib docs
Browse files Browse the repository at this point in the history
  • Loading branch information
danrusei committed Feb 16, 2024
1 parent 6d35f57 commit 65aa718
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 86 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[package]
name = "hass-rs"
version = "0.2.0"
version = "0.3.0"
description = "An async websocket client for Home Assistant"
keywords = ["hass", "homeassistant", "tokio", "async-std"]
authors = ["Dan Rusei <[email protected]>"]
repository = "https://github.com/danrusei/hass-rs"
documentation = "https://docs.rs/hass-rs"
readme = "README.md"
license = "MIT"
edition = "2018"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down
170 changes: 96 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,106 +1,128 @@
# Hass-Rs

A simple async Rust client library for interacting with Home Assistant websocket API.
A simple async Rust client library for interacting with Home Assistant **websocket** API.

## Configure:

hass-rs supports tokio and async-std runtimes, by default it uses async-std, to use tokio change the feature flags in Cargo.toml
## Test environment

with [async-std](https://async.rs/) support
Connect to your Home Assistant server, or follow the instructions from the [Instalation Guide](https://www.home-assistant.io/installation/).
For development, [docker](https://www.home-assistant.io/installation/linux#install-home-assistant-container) can be used to easily bootstrap a test environment.

```toml
[dependencies]
hass-rs = { version = "0.2", features = ["async-std-runtime"] }
```
Steps to run the provided Examples:

with [tokio](https://tokio.rs/) support
* clone the hass_rs github repository
* run the homeassistant server in a docker container

```toml
[dependencies]
hass-rs = { version = "0.2", features = ["tokio-runtime"] }
```bash
docker run -d --name="home-assistant" -v /PATH_TO_YOUR_CONFIG:/config -v /etc/localtime:/etc/localtime:ro --net=host homeassistant/home-assistant:stable
```

* login to Home Assistant web interface: <http://localhost:8123/>
* go to `Profile` --> `Long-Lived Access Tokens` and create a token to be used by hass_rs client
* set the environment variable ***export HASS_TOKEN=<YOUR_TOKEN_HERE>***
* run the example scripts: `cargo run --example get_cmds` or `cargo run --example call_service` or `cargo run --example subscribe_event`

## Example usage

Check the [Example folder](https://github.com/danrusei/hass-rs/tree/master/examples) for additional details on how to use various hass-rs functions.

```rust
use env_logger;
use hass_rs::client;
use hass_rs::client::HassClient;

use async_tungstenite::tungstenite::{Error, Message};
use futures_util::{
stream::{SplitSink, SplitStream},
SinkExt, StreamExt,
};
use lazy_static::lazy_static;
use std::env::var;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::sync::{mpsc, mpsc::Receiver, mpsc::Sender};
use tokio_tungstenite::{connect_async, WebSocketStream};

lazy_static! {
static ref TOKEN: String =
var("HASS_TOKEN").expect("please set up the HASS_TOKEN env variable before running this");
}

#[cfg_attr(feature = "async-std-runtime", async_std::main)]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();

println!("Creating the Websocket Client and Authenticate the session");
let mut client = client::connect("localhost", 8123).await?;

client.auth_with_longlivedtoken(&*TOKEN).await?;
println!("WebSocket connection and authethication works");
async fn ws_incoming_messages(
mut stream: SplitStream<WebSocketStream<impl AsyncRead + AsyncWrite + Unpin>>,
to_user: Sender<Result<Message, Error>>,
) {
loop {
while let Some(message) = stream.next().await {
let _ = to_user.send(message).await;
}
}
}

println!("Get Hass Config");
match client.get_config().await {
Ok(v) => println!("{:?}", v),
Err(err) => println!("Oh no, an error: {}", err),
async fn ws_outgoing_messages(
mut sink: SplitSink<WebSocketStream<impl AsyncRead + AsyncWrite + Unpin>, Message>,
mut from_user: Receiver<Message>,
) {
loop {
match from_user.recv().await {
Some(msg) => sink.send(msg).await.expect("Failed to send message"),
None => continue,
}
}
Ok(())
}
```

## Test environment
#[tokio::main]
async fn main() {
let url = "ws://localhost:8123/api/websocket";

Use docker to easily bootstrap a test environment.
println!("Connecting to - {}", url);
let (wsclient, _) = connect_async(url).await.expect("Failed to connect");
let (sink, stream) = wsclient.split();

Steps:
//Channels to recieve the Client Command and send it over to Websocket server
let (to_gateway, from_user) = mpsc::channel::<Message>(20);
//Channels to receive the Response from the Websocket server and send it over to Client
let (to_user, from_gateway) = mpsc::channel::<Result<Message, Error>>(20);

* clone the hass_rs github repository
* execute the below command, which runs the docker homeassistant container in background
// Handle incoming messages in a separate task
let read_handle = tokio::spawn(ws_incoming_messages(stream, to_user));

```bash
$ docker run -d --name="home-assistant" -v /PATH_TO_YOUR_CONFIG:/config -v /etc/localtime:/etc/localtime:ro --net=host homeassistant/home-assistant:stable
```
// Read from command line and send messages
let write_handle = tokio::spawn(ws_outgoing_messages(sink, from_user));

* login to Home Assistant web interface: http://localhost:8123/
* go to `Profile` --> `Long-Lived Access Tokens` and create a token to be used by hass_rs client
* set the environment variable ***export HASS_TOKEN=<YOUR_TOKEN_HERE>***
* run the tests with `cargo test`
* run the example scripts: `cargo run --example ping` or `cargo run --example fetch_items` or `cargo run --example subscribe_event`

## API reference

* **client::connect(host, port)** - establish the websocket connection to Home Assistant server.
* **client::connect_to_url(url)** - connect using a given URL (use this if you are using a WSS connection)
* **client.auth_with_longlivedtoken(token)** - authenticate the session using a long-lived access token.
* **client.ping()** - can serve as a heartbeat, to ensure the connection is still alive.
* **client.subscribe_event(event_name, callback)** - subscribe the client to the event bus. the callback is a closure, of type Fn(WSEvent), which is executed every time when a specific event is received.
* **client.unsubscribe_event(subscription_id)** - unsubscribe the client from the event bus.
* **client.get_config()** - it gets a dump of the current config in Home Assistant.
* **client.get_states()** - it gets a dump of all the current states in Home Assistant.
* **client.get_services()** - it gets a dump of the current services in Home Assistant.
* **client.call_service(domain, service, service_data)** - it calls call a service in Home Assistant.
let mut client = HassClient::new(to_gateway, from_gateway);

## Development status
client
.auth_with_longlivedtoken(&*TOKEN)
.await
.expect("Not able to autheticate");

- [x] Create the client
- [ ] Automatic reconnection (TBD)
- [x] Authenticate using long-lived access tokens
- [ ] Authenticate using OAuth2 (TBD)
- [x] Call a service
- [x] Subscribe
- [x] Events
- [ ] Config (you need this?, raise an Issue)
- [ ] Services (you need this?, raise an Issue)
- [x] UnSubscribe
- [x] Fetch Commands
- [x] Fetching states
- [x] Fetching config
- [x] Fetching services
- [x] Fetching panels
- [ ] Fetching media player thumbnails (you need this?, raise an Issue)
- [x] Ping - Pong
println!("WebSocket connection and authethication works\n");

println!("Getting the Config:\n");
let cmd2 = client
.get_config()
.await
.expect("Unable to retrieve the Config");
println!("config: {}\n", cmd2);

// Await both tasks (optional, depending on your use case)
let _ = tokio::try_join!(read_handle, write_handle);
}
```

## Development status

* [x] Create the client
* [ ] Automatic reconnection (TBD)
* [x] Authenticate using long-lived access tokens
* [ ] Authenticate using OAuth2 (TBD)
* [x] Call a service
* [x] Subscribe
* [x] Events
* [ ] Config (you need this?, raise an Issue)
* [ ] Services (you need this?, raise an Issue)
* [x] UnSubscribe
* [x] Fetch Commands
* [x] Fetching states
* [x] Fetching config
* [x] Fetching services
* [x] Fetching panels
* [ ] Fetching media player thumbnails (you need this?, raise an Issue)
* [ ] Ping - Pong
22 changes: 12 additions & 10 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
//! Home Assistant client implementation
//!
//! Provides an async connect and methods for issuing the supported commands.
use crate::types::{
Ask, Auth, CallService, Command, HassConfig, HassEntity, HassPanels, HassServices, Response,
Expand All @@ -20,8 +18,8 @@ use std::sync::{
};
use tokio::sync::mpsc::{Receiver, Sender};

/// HassClient sits between the communication between user and HomeAssistant Web Socket Server
/// and creates some convenient functions to call the server
/// HassClient is a library that is meant to simplify the conversation with HomeAssistant Web Socket Server
/// it provides a number of convenient functions that creates the requests and read the messages from server
#[derive(Debug)]
pub struct HassClient {
// holds the id of the WS message
Expand Down Expand Up @@ -86,7 +84,7 @@ impl HassClient {
}
}

///The API supports receiving a ping from the client and returning a pong.
/// The API supports receiving a ping from the client and returning a pong.
/// This serves as a heartbeat to ensure the connection is still alive.
pub async fn ping(&mut self) -> HassResult<String> {
Expand All @@ -108,7 +106,7 @@ impl HassClient {
}
}

///This will get a dump of the current config in Home Assistant.
/// This will get the current config of the Home Assistant.
///
/// The server will respond with a result message containing the config.
Expand Down Expand Up @@ -136,7 +134,7 @@ impl HassClient {
}
}

///This will get a dump of all the current states in Home Assistant.
/// This will get all the current states from Home Assistant.
///
/// The server will respond with a result message containing the states.
Expand All @@ -163,7 +161,7 @@ impl HassClient {
}
}

///This will get a dump of the current services in Home Assistant.
/// This will get all the services from Home Assistant.
///
/// The server will respond with a result message containing the services.
Expand All @@ -190,7 +188,7 @@ impl HassClient {
}
}

///This will get a dump of the current registered panels in Home Assistant.
/// This will get all the registered panels from Home Assistant.
///
/// The server will respond with a result message containing the current registered panels.
Expand Down Expand Up @@ -251,7 +249,7 @@ impl HassClient {
}
}

///The command subscribe_event will subscribe your client to the event bus.
/// The command subscribe_event will subscribe your client to the event bus.
///
/// You can either listen to all events or to a specific event type.
/// If you want to listen to multiple event types, you will have to send multiple subscribe_events commands.
Expand Down Expand Up @@ -315,6 +313,7 @@ impl HassClient {
}

//used to send commands and receive responses from the gateway

pub(crate) async fn command(&mut self, cmd: Command) -> HassResult<Response> {
//transform to TungsteniteMessage to be sent to WebSocket
let cmd_tungstenite = cmd.to_tungstenite_message();
Expand Down Expand Up @@ -348,6 +347,9 @@ impl HassClient {
}
}

/// convenient function that validates if the message received is an Event
/// the Events should be processed by used in a separate async task
pub fn check_if_event(message: &Result<TungsteniteMessage, Error>) -> HassResult<WSEvent> {
match message {
Ok(TungsteniteMessage::Text(data)) => {
Expand Down

0 comments on commit 65aa718

Please sign in to comment.