HomeServer/src/tasmota.rs
2023-09-21 07:30:39 +02:00

209 lines
4.2 KiB
Rust

use anyhow::Result;
use serde::Deserialize;
use serde_json::from_str;
#[derive(Debug, Clone)]
pub struct Tasmota {
device: String,
}
impl Tasmota {
pub fn new(device: impl ToString) -> Self {
Self {
device: device.to_string(),
}
}
pub fn name(&self) -> &str {
&self.device
}
fn command<'a>(&self, command: impl IntoIterator<Item = &'a str>) -> String {
let mut str = String::new();
for s in command.into_iter() {
str += s;
str += "%20";
}
format!("http://{}/cm?cmnd={}", self.device, str)
}
async fn post<'a>(&self, command: impl IntoIterator<Item = &'a str>) -> Result<String> {
Ok(reqwest::Client::new()
.post(&self.command(command))
.send()
.await?
.text()
.await?)
}
async fn get<'a>(&self, command: impl IntoIterator<Item = &'a str>) -> Result<String> {
Ok(reqwest::Client::new()
.get(&self.command(command))
.send()
.await?
.text()
.await?)
}
pub async fn turn_on_led(&self) -> Result<()> {
self.post(["LedPower", "1"]).await?;
Ok(())
}
pub async fn turn_off_led(&self) -> Result<()> {
self.post(["LedPower", "0"]).await?;
Ok(())
}
pub async fn switch_on(&self) -> Result<()> {
self.post(["Power0", "1"]).await?;
Ok(())
}
pub async fn switch_off(&self) -> Result<()> {
self.post(["Power0", "0"]).await?;
Ok(())
}
pub async fn power_state(&self) -> Result<bool> {
let res = self.get(["Power0"]).await?;
let state = PowerState::from_str(&res)?;
Ok(state.is_on())
}
pub async fn read_power_usage(&self) -> Result<f32> {
let res = self.get(["Status", "8"]).await?;
let status = Status::from_str(&res)?;
Ok(status.StatusSNS.ENERGY.Power)
}
pub async fn led_state(&self) -> Result<bool> {
let res = self.get(["LedState"]).await?;
let state = LedState::from_str(&res)?;
Ok(state.LedState != 0)
}
}
#[cfg(test)]
mod test {
use std::{thread, time::Duration};
use super::*;
use anyhow::Result;
#[tokio::test]
async fn test_connection() -> Result<()> {
let dev = Tasmota::new("Tasmota-Plug-1");
let power = dev.read_power_usage().await?;
println!("{power}");
Ok(())
}
#[tokio::test]
async fn test_toggle() -> Result<()> {
let dev = Tasmota::new("Tasmota-Plug-4");
dev.switch_off().await?;
assert_eq!(dev.power_state().await?, false);
thread::sleep(Duration::from_secs(5));
dev.switch_on().await?;
assert_eq!(dev.power_state().await?, true);
Ok(())
}
#[tokio::test]
async fn test_led() -> Result<()> {
let dev = Tasmota::new("Tasmota-Plug-4");
dev.turn_off_led().await?;
assert_eq!(dev.led_state().await?, false);
thread::sleep(Duration::from_secs(5));
dev.turn_on_led().await?;
assert_eq!(dev.led_state().await?, true);
Ok(())
}
}
#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
pub struct Status {
pub StatusSNS: StatusSNS,
}
impl Status {
fn from_str(s: &str) -> Result<Self> {
Ok(from_str(s)?)
}
}
#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
pub struct StatusSNS {
pub Time: String,
pub ENERGY: Energy,
}
#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
pub struct Energy {
pub TotalStartTime: String,
pub Total: f32,
pub Yesterday: f32,
pub Today: f32,
pub Power: f32,
pub ApparentPower: u32,
pub ReactivePower: u32,
pub Factor: f32,
pub Voltage: u32,
pub Current: f32,
}
#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
pub struct LedState {
LedState: u8,
}
impl LedState {
fn from_str(s: &str) -> Result<Self> {
Ok(from_str(s)?)
}
}
#[allow(non_snake_case)]
#[derive(Deserialize, Debug)]
pub struct PowerState {
POWER: String,
}
impl PowerState {
fn from_str(s: &str) -> Result<Self> {
Ok(from_str(s)?)
}
pub fn is_on(&self) -> bool {
self.POWER == "ON"
}
}