airtouch5 0.2.0

A library for communicating with AirTouch 5 air conditioning system control consoles
Documentation
//! Watch the status of an AirTouch 5 controller.
//!
//! Usage:
//!     `watch <ip>`
//!
//! Watches for changes in the status of the AirTouch 5 controller at `ip`, and
//! prints them.

use std::{net::IpAddr, str::FromStr, sync::LazyLock};

use airtouch5::{types::status::StatusSet, AirTouch5};
use simplelog::TermLogger;

const USAGE_STR: &str = "Usage: status <ip>";
static UNNAMED_ZONE: LazyLock<String> = LazyLock::new(|| "<unknown>".to_string());

/// Clear the terminal and position the cursor in the top left.
///
/// You *are* using a terminal emulator that supports CSI escape codes,
/// arent't you?
fn cls() {
    print!("\x1b[2J\x1b[;H");
}

/// On the initial connection, the status will be empty until status messages
/// are received to fill it. Call `ac_status()` and `zone_status()` to ensure
/// the state is filled (ignoring their results, which we will see later in the
/// watch channel).
async fn prefill(controller: &AirTouch5) {
    tokio::try_join!(
        // prefill the AC unit status
        controller.ac_status(),
        // prefill the current zone status
        controller.zone_status(),
    )
    .expect("could not prefill status");
}

#[tokio::main(flavor = "current_thread")]
pub async fn main() {
    // log to console
    TermLogger::init(
        log::LevelFilter::Info,
        simplelog::Config::default(),
        simplelog::TerminalMode::Mixed,
        simplelog::ColorChoice::Auto,
    )
    .expect("could non init logger");

    // connect to the given addess
    let addr = IpAddr::from_str(&std::env::args().nth(1).expect(USAGE_STR)).expect(USAGE_STR);
    let controller = AirTouch5::with_ipaddr(addr)
        .await
        .expect("could not connect");

    // fetch the list of zone names
    let names = controller
        .zone_names()
        .await
        .expect("could not get zone names");

    // find the longest zone name
    let w = names.by_index().map(|(_, z)| z.len()).max().unwrap_or(0);

    prefill(&controller).await;

    // subscribe to the current status
    let mut watch = controller.subscribe_status().expect("could not subscribe");
    loop {
        // fetch the current status
        let status = watch.borrow_and_update();

        // and print it
        cls();
        for ac in status.acs().values() {
            println!("{}", ac);
        }
        println!();
        for (idx, zone) in status.zones().iter() {
            println!(
                "{:>w$}: {}",
                names.zones.get(idx).unwrap_or(&UNNAMED_ZONE),
                zone
            );
        }

        // then wait for the status to change
        drop(status);
        watch.changed().await.expect("sender was dropped");
    }
}