Test tasmota interface
This commit is contained in:
parent
254806a1ae
commit
4ae0c30171
3 changed files with 170 additions and 13 deletions
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal 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}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"plugs": [
|
"plugs": [
|
||||||
"Dev1",
|
"Tasmota-Plug-1",
|
||||||
"Dev2"
|
"Tasmota-Plug-2",
|
||||||
|
"Tasmota-Plug-3",
|
||||||
|
"Tasmota-Plug-4"
|
||||||
]
|
]
|
||||||
}
|
}
|
161
src/tasmota.rs
161
src/tasmota.rs
|
@ -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";
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("http://{}/cm?cmnd={}", self.device, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post(&self, command: &str) -> Result<String> {
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue