Test tasmota interface

This commit is contained in:
hodasemi 2023-09-20 14:58:58 +02:00
parent 254806a1ae
commit 4ae0c30171
3 changed files with 170 additions and 13 deletions

16
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/<executable file>",
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

View file

@ -1,6 +1,8 @@
{ {
"plugs": [ "plugs": [
"Dev1", "Tasmota-Plug-1",
"Dev2" "Tasmota-Plug-2",
"Tasmota-Plug-3",
"Tasmota-Plug-4"
] ]
} }

View file

@ -1,4 +1,6 @@
use anyhow::Result; use anyhow::Result;
use serde::Deserialize;
use serde_json::from_str;
pub struct Tasmota { pub struct Tasmota {
device: String, device: String,
@ -15,11 +17,18 @@ impl Tasmota {
&self.device &self.device
} }
fn command(&self, command: &str) -> String { fn command<'a>(&self, command: impl IntoIterator<Item = &'a str>) -> String {
format!("http://{}/cm?cmnd={}", self.device, command) let mut str = String::new();
for s in command.into_iter() {
str += s;
str += "%20";
} }
async fn post(&self, command: &str) -> Result<String> { format!("http://{}/cm?cmnd={}", self.device, str)
}
async fn post<'a>(&self, command: impl IntoIterator<Item = &'a str>) -> Result<String> {
Ok(reqwest::Client::new() Ok(reqwest::Client::new()
.post(&self.command(command)) .post(&self.command(command))
.send() .send()
@ -28,7 +37,7 @@ impl Tasmota {
.await?) .await?)
} }
async fn get(&self, command: &str) -> Result<String> { async fn get<'a>(&self, command: impl IntoIterator<Item = &'a str>) -> Result<String> {
Ok(reqwest::Client::new() Ok(reqwest::Client::new()
.get(&self.command(command)) .get(&self.command(command))
.send() .send()
@ -38,32 +47,162 @@ impl Tasmota {
} }
pub async fn turn_on_led(&self) -> Result<()> { pub async fn turn_on_led(&self) -> Result<()> {
self.post("LedPower=1").await?; self.post(["LedPower", "1"]).await?;
Ok(()) Ok(())
} }
pub async fn turn_off_led(&self) -> Result<()> { pub async fn turn_off_led(&self) -> Result<()> {
self.post("LedPower=2").await?; self.post(["LedPower", "0"]).await?;
Ok(()) Ok(())
} }
pub async fn switch_on(&self) -> Result<()> { pub async fn switch_on(&self) -> Result<()> {
self.post("Power0=1").await?; self.post(["Power0", "1"]).await?;
Ok(()) Ok(())
} }
pub async fn switch_off(&self) -> Result<()> { pub async fn switch_off(&self) -> Result<()> {
self.post("Power0=0").await?; self.post(["Power0", "0"]).await?;
Ok(()) Ok(())
} }
pub async fn read_power_usage(&self) -> Result<f32> { pub async fn power_state(&self) -> Result<bool> {
let res = self.get("Status=8").await?; let res = self.get(["Power0"]).await?;
Ok(res.parse()?) 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"
} }
} }