feat(tapo-agent): add schedule/countdown timer API support
- Fork tapo crate to add missing schedule/timer APIs - Add get_countdown_rules, get_schedule_rules, get_next_event methods - New readings: countdown_active, countdown_remain, schedule_count, schedule_active_count, next_event_time - Add local compilation to build script alongside cross-compilation - Implement offline polling - device collection continues when server disconnects - Add more device readings: on_time, signal_level, rssi, runtime_today/month, energy_month Vendored tapo fork in tapo-fork/ with minimal changes to add schedule APIs.
This commit is contained in:
3
agents/tapo/.gitignore
vendored
Normal file
3
agents/tapo/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
target/
|
||||||
|
target-local/
|
||||||
|
config.toml
|
||||||
@@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
description = "Tapo smart plug sensor data collection agent"
|
description = "Tapo smart plug sensor data collection agent"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tapo = "0.8"
|
tapo = { path = "./tapo-fork/tapo" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-tungstenite = { version = "0.24", default-features = false, features = ["connect", "rustls-tls-native-roots"] }
|
tokio-tungstenite = { version = "0.24", default-features = false, features = ["connect", "rustls-tls-native-roots"] }
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
|
|||||||
@@ -114,11 +114,38 @@ declare -A TARGETS=(
|
|||||||
echo -e "${BLUE}Starting builds...${NC}"
|
echo -e "${BLUE}Starting builds...${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Local/Native Build
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo -e "${GREEN}Building for local/native target...${NC}"
|
||||||
|
|
||||||
|
# Get host target
|
||||||
|
HOST_TARGET=$(rustc -vV | grep host | cut -d' ' -f2)
|
||||||
|
|
||||||
|
# Use separate target dir for local builds to avoid GLIBC conflicts with cross builds
|
||||||
|
CARGO_TARGET_DIR=target-local cargo build --release -j $(nproc)
|
||||||
|
|
||||||
|
# Copy binary to dist folder
|
||||||
|
cp "target-local/release/tapo-agent" "dist/tapo-agent-local-${HOST_TARGET}"
|
||||||
|
|
||||||
|
# Get binary size
|
||||||
|
size=$(du -h "dist/tapo-agent-local-${HOST_TARGET}" | cut -f1)
|
||||||
|
echo -e " → ${GREEN}dist/tapo-agent-local-${HOST_TARGET}${NC} ($size)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Cross-Compilation Builds
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
echo -e "${BLUE}Starting cross-compilation builds...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
for target in "${!TARGETS[@]}"; do
|
for target in "${!TARGETS[@]}"; do
|
||||||
name="${TARGETS[$target]}"
|
name="${TARGETS[$target]}"
|
||||||
echo -e "${GREEN}Building for $target ($name)...${NC}"
|
echo -e "${GREEN}Building for $target ($name)...${NC}"
|
||||||
|
|
||||||
cross build --release --target "$target"
|
cross build --release --target "$target" -j $(nproc)
|
||||||
|
|
||||||
# Copy binary to dist folder with descriptive name
|
# Copy binary to dist folder with descriptive name
|
||||||
cp "target/$target/release/tapo-agent" "dist/tapo-agent-$name"
|
cp "target/$target/release/tapo-agent" "dist/tapo-agent-$name"
|
||||||
|
|||||||
@@ -197,8 +197,27 @@ async fn collect_device_data(device: &DeviceConfig) -> Vec<Reading> {
|
|||||||
channel: "state".to_string(),
|
channel: "state".to_string(),
|
||||||
value: if info.device_on { 1.0 } else { 0.0 },
|
value: if info.device_on { 1.0 } else { 0.0 },
|
||||||
});
|
});
|
||||||
|
// Time device has been ON since last state change (seconds)
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "on_time".to_string(),
|
||||||
|
value: info.on_time as f64,
|
||||||
|
});
|
||||||
|
// WiFi signal level (0-3)
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "signal_level".to_string(),
|
||||||
|
value: info.signal_level as f64,
|
||||||
|
});
|
||||||
|
// WiFi RSSI (dBm, negative value)
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "rssi".to_string(),
|
||||||
|
value: info.rssi as f64,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Current power in watts (API returns milliwatts)
|
||||||
if let Ok(energy) = plug.get_current_power().await {
|
if let Ok(energy) = plug.get_current_power().await {
|
||||||
readings.push(Reading {
|
readings.push(Reading {
|
||||||
device: device.name.clone(),
|
device: device.name.clone(),
|
||||||
@@ -208,11 +227,74 @@ async fn collect_device_data(device: &DeviceConfig) -> Vec<Reading> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(usage) = plug.get_energy_usage().await {
|
if let Ok(usage) = plug.get_energy_usage().await {
|
||||||
|
// Today's energy in Wh
|
||||||
readings.push(Reading {
|
readings.push(Reading {
|
||||||
device: device.name.clone(),
|
device: device.name.clone(),
|
||||||
channel: "energy_today".to_string(),
|
channel: "energy_today".to_string(),
|
||||||
value: usage.today_energy as f64,
|
value: usage.today_energy as f64,
|
||||||
});
|
});
|
||||||
|
// Today's runtime in minutes
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "runtime_today".to_string(),
|
||||||
|
value: usage.today_runtime as f64,
|
||||||
|
});
|
||||||
|
// This month's energy in Wh
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "energy_month".to_string(),
|
||||||
|
value: usage.month_energy as f64,
|
||||||
|
});
|
||||||
|
// This month's runtime in minutes
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "runtime_month".to_string(),
|
||||||
|
value: usage.month_runtime as f64,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Countdown timer status
|
||||||
|
if let Ok(countdown) = plug.get_countdown_rules().await {
|
||||||
|
let active_countdown = countdown.rules.iter().find(|r| r.enable);
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "countdown_active".to_string(),
|
||||||
|
value: if active_countdown.is_some() { 1.0 } else { 0.0 },
|
||||||
|
});
|
||||||
|
if let Some(rule) = active_countdown {
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "countdown_remain".to_string(),
|
||||||
|
value: rule.remain as f64,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule rules count
|
||||||
|
if let Ok(schedules) = plug.get_schedule_rules().await {
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "schedule_count".to_string(),
|
||||||
|
value: schedules.rules.len() as f64,
|
||||||
|
});
|
||||||
|
// Count active schedules
|
||||||
|
let active_count = schedules.rules.iter().filter(|r| r.enable).count();
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "schedule_active_count".to_string(),
|
||||||
|
value: active_count as f64,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next scheduled event
|
||||||
|
if let Ok(next) = plug.get_next_event().await {
|
||||||
|
if let Some(ts) = next.timestamp {
|
||||||
|
readings.push(Reading {
|
||||||
|
device: device.name.clone(),
|
||||||
|
channel: "next_event_time".to_string(),
|
||||||
|
value: ts as f64,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => error!("Failed to connect to P110 {}: {}", device.name, e),
|
Err(e) => error!("Failed to connect to P110 {}: {}", device.name, e),
|
||||||
@@ -241,6 +323,38 @@ async fn collect_device_data(device: &DeviceConfig) -> Vec<Reading> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run_agent(config: Config) -> Result<(), Box<dyn std::error::Error>> {
|
async fn run_agent(config: Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
// Channel for readings from poller to sender
|
||||||
|
let (tx, mut rx) = mpsc::channel::<Vec<Reading>>(100);
|
||||||
|
|
||||||
|
// Spawn device polling task - runs continuously regardless of connection
|
||||||
|
let poll_interval_secs = config.poll_interval_secs;
|
||||||
|
let devices = config.devices.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut poll_interval = interval(Duration::from_secs(poll_interval_secs));
|
||||||
|
loop {
|
||||||
|
poll_interval.tick().await;
|
||||||
|
|
||||||
|
let mut all_readings = Vec::new();
|
||||||
|
for device in &devices {
|
||||||
|
let readings = collect_device_data(device).await;
|
||||||
|
all_readings.extend(readings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !all_readings.is_empty() {
|
||||||
|
info!("Collected {} readings from devices", all_readings.len());
|
||||||
|
// Log readings even if not connected
|
||||||
|
for reading in &all_readings {
|
||||||
|
info!(" {} {} = {}", reading.device, reading.channel, reading.value);
|
||||||
|
}
|
||||||
|
// Try to send to connection task, drop if channel full
|
||||||
|
let _ = tx.try_send(all_readings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connection and sending loop
|
||||||
let mut reconnect_delay = Duration::from_secs(1);
|
let mut reconnect_delay = Duration::from_secs(1);
|
||||||
let max_reconnect_delay = Duration::from_secs(60);
|
let max_reconnect_delay = Duration::from_secs(60);
|
||||||
|
|
||||||
@@ -283,50 +397,43 @@ async fn run_agent(config: Config) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut poll_interval = interval(Duration::from_secs(config.poll_interval_secs));
|
// Main send loop - receive readings from channel and send to server
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
poll_interval.tick().await;
|
tokio::select! {
|
||||||
|
// Receive readings from polling task
|
||||||
|
Some(readings) = rx.recv() => {
|
||||||
|
info!("Sending {} readings to server", readings.len());
|
||||||
|
let data = DataMessage {
|
||||||
|
msg_type: "data".to_string(),
|
||||||
|
readings,
|
||||||
|
};
|
||||||
|
let data_json = serde_json::to_string(&data)?;
|
||||||
|
|
||||||
let mut all_readings = Vec::new();
|
if let Err(e) = write.send(Message::Text(data_json)).await {
|
||||||
for device in &config.devices {
|
error!("Failed to send data: {}", e);
|
||||||
let readings = collect_device_data(device).await;
|
break;
|
||||||
all_readings.extend(readings);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !all_readings.is_empty() {
|
|
||||||
info!("Sending {} readings", all_readings.len());
|
|
||||||
let data = DataMessage {
|
|
||||||
msg_type: "data".to_string(),
|
|
||||||
readings: all_readings,
|
|
||||||
};
|
|
||||||
let data_json = serde_json::to_string(&data)?;
|
|
||||||
|
|
||||||
if let Err(e) = write.send(Message::Text(data_json)).await {
|
|
||||||
error!("Failed to send data: {}", e);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
// Handle incoming WebSocket messages
|
||||||
|
msg = read.next() => {
|
||||||
while let Ok(Some(msg)) = tokio::time::timeout(
|
match msg {
|
||||||
Duration::from_millis(100),
|
Some(Ok(Message::Ping(data))) => {
|
||||||
read.next(),
|
let _ = write.send(Message::Pong(data)).await;
|
||||||
)
|
}
|
||||||
.await
|
Some(Ok(Message::Close(_))) => {
|
||||||
{
|
info!("Server closed connection");
|
||||||
match msg {
|
break;
|
||||||
Ok(Message::Ping(data)) => {
|
}
|
||||||
let _ = write.send(Message::Pong(data)).await;
|
Some(Err(e)) => {
|
||||||
|
error!("WebSocket error: {}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
info!("Connection closed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
Ok(Message::Close(_)) => {
|
|
||||||
info!("Server closed connection");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("WebSocket error: {}", e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,12 +470,19 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
Some(Commands::Run) | None => {
|
Some(Commands::Run) | None => {
|
||||||
let config_path = &cli.config;
|
let config_path = &cli.config;
|
||||||
|
|
||||||
let config_content = std::fs::read_to_string(config_path).map_err(|e| {
|
let config_content = match std::fs::read_to_string(config_path) {
|
||||||
format!(
|
Ok(content) => content,
|
||||||
"Failed to read config file {}: {}\n\nCreate config with device discovery:\n ./tapo-agent init --server ws://SERVER:8080 --key YOUR_KEY --email tapo@email.com --password tapopass\n\nOr specify broadcast address:\n ./tapo-agent init --server ws://SERVER:8080 --key YOUR_KEY --email tapo@email.com --password tapopass --broadcast 192.168.0.255",
|
Err(e) => {
|
||||||
config_path, e
|
eprintln!("Failed to read config file {}: {}", config_path, e);
|
||||||
)
|
eprintln!();
|
||||||
})?;
|
eprintln!("Create config with device discovery:");
|
||||||
|
eprintln!(" ./tapo-agent init --server ws://SERVER:8080 --key YOUR_KEY --email tapo@email.com --password tapopass");
|
||||||
|
eprintln!();
|
||||||
|
eprintln!("Or specify broadcast address:");
|
||||||
|
eprintln!(" ./tapo-agent init --server ws://SERVER:8080 --key YOUR_KEY --email tapo@email.com --password tapopass --broadcast 192.168.0.255");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let config: Config = toml::from_str(&config_content)
|
let config: Config = toml::from_str(&config_content)
|
||||||
.map_err(|e| format!("Failed to parse config: {}", e))?;
|
.map_err(|e| format!("Failed to parse config: {}", e))?;
|
||||||
|
|||||||
5
agents/tapo/tapo-fork/.cargo/audit.toml
Normal file
5
agents/tapo/tapo-fork/.cargo/audit.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[advisories]
|
||||||
|
ignore = [
|
||||||
|
# The Marvin Attack poses minimal risk to the use cases of this library
|
||||||
|
"RUSTSEC-2023-0071",
|
||||||
|
]
|
||||||
1
agents/tapo/tapo-fork/.github/FUNDING.yml
vendored
Normal file
1
agents/tapo/tapo-fork/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
github: mihai-dinculescu
|
||||||
29
agents/tapo/tapo-fork/.github/dependabot.yml
vendored
Normal file
29
agents/tapo/tapo-fork/.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "cargo"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
commit-message:
|
||||||
|
prefix: "chore"
|
||||||
|
prefix-development: "chore"
|
||||||
|
include: "scope"
|
||||||
|
- package-ecosystem: pip
|
||||||
|
directory: "/tapo-py"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
commit-message:
|
||||||
|
prefix: "chore"
|
||||||
|
prefix-development: "chore"
|
||||||
|
include: "scope"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
commit-message:
|
||||||
|
prefix: "chore"
|
||||||
|
prefix-development: "chore"
|
||||||
|
include: "scope"
|
||||||
184
agents/tapo/tapo-fork/.github/workflows/py-ci.yml
vendored
Normal file
184
agents/tapo/tapo-fork/.github/workflows/py-ci.yml
vendored
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# This file is autogenerated by maturin v1.10.2
|
||||||
|
# To update, run
|
||||||
|
#
|
||||||
|
# maturin generate-ci github
|
||||||
|
#
|
||||||
|
name: Python
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
linux:
|
||||||
|
runs-on: ${{ matrix.platform.runner }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- runner: ubuntu-22.04
|
||||||
|
target: x86_64
|
||||||
|
- runner: ubuntu-22.04
|
||||||
|
target: x86
|
||||||
|
- runner: ubuntu-22.04
|
||||||
|
target: aarch64
|
||||||
|
- runner: ubuntu-22.04
|
||||||
|
target: armv7
|
||||||
|
- runner: ubuntu-22.04
|
||||||
|
target: s390x
|
||||||
|
- runner: ubuntu-22.04
|
||||||
|
target: ppc64le
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
- name: Build wheels
|
||||||
|
uses: PyO3/maturin-action@v1
|
||||||
|
with:
|
||||||
|
target: ${{ matrix.platform.target }}
|
||||||
|
args: --release --out dist --find-interpreter --manifest-path ./tapo-py/Cargo.toml
|
||||||
|
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
manylinux: auto
|
||||||
|
- name: Upload wheels
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: wheels-linux-${{ matrix.platform.target }}
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
musllinux:
|
||||||
|
runs-on: ${{ matrix.platform.runner }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- runner: ubuntu-22.04
|
||||||
|
target: x86_64
|
||||||
|
- runner: ubuntu-22.04
|
||||||
|
target: x86
|
||||||
|
- runner: ubuntu-22.04
|
||||||
|
target: aarch64
|
||||||
|
- runner: ubuntu-22.04
|
||||||
|
target: armv7
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
- name: Build wheels
|
||||||
|
uses: PyO3/maturin-action@v1
|
||||||
|
with:
|
||||||
|
target: ${{ matrix.platform.target }}
|
||||||
|
args: --release --out dist --find-interpreter --manifest-path ./tapo-py/Cargo.toml
|
||||||
|
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
manylinux: musllinux_1_2
|
||||||
|
- name: Upload wheels
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: wheels-musllinux-${{ matrix.platform.target }}
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
windows:
|
||||||
|
runs-on: ${{ matrix.platform.runner }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- runner: windows-latest
|
||||||
|
target: x64
|
||||||
|
- runner: windows-latest
|
||||||
|
target: x86
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
architecture: ${{ matrix.platform.target }}
|
||||||
|
- name: Build wheels
|
||||||
|
uses: PyO3/maturin-action@v1
|
||||||
|
with:
|
||||||
|
target: ${{ matrix.platform.target }}
|
||||||
|
args: --release --out dist --find-interpreter --manifest-path ./tapo-py/Cargo.toml
|
||||||
|
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
- name: Upload wheels
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: wheels-windows-${{ matrix.platform.target }}
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
macos:
|
||||||
|
runs-on: ${{ matrix.platform.runner }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- runner: macos-15-intel
|
||||||
|
target: x86_64
|
||||||
|
- runner: macos-latest
|
||||||
|
target: aarch64
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: 3.x
|
||||||
|
- name: Build wheels
|
||||||
|
uses: PyO3/maturin-action@v1
|
||||||
|
with:
|
||||||
|
target: ${{ matrix.platform.target }}
|
||||||
|
args: --release --out dist --find-interpreter --manifest-path ./tapo-py/Cargo.toml
|
||||||
|
sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
- name: Upload wheels
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: wheels-macos-${{ matrix.platform.target }}
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
sdist:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- name: Build sdist
|
||||||
|
uses: PyO3/maturin-action@v1
|
||||||
|
with:
|
||||||
|
command: sdist
|
||||||
|
args: --out dist --manifest-path ./tapo-py/Cargo.toml
|
||||||
|
- name: Test sdist
|
||||||
|
run: |
|
||||||
|
pip install --force-reinstall --verbose dist/*.tar.gz
|
||||||
|
python -c 'from tapo import ApiClient'
|
||||||
|
- name: Upload sdist
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: wheels-sdist
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
|
||||||
|
needs: [linux, musllinux, windows, macos, sdist]
|
||||||
|
permissions:
|
||||||
|
# Use to sign the release artifacts
|
||||||
|
id-token: write
|
||||||
|
# Used to upload release artifacts
|
||||||
|
contents: write
|
||||||
|
# Used to generate artifact attestation
|
||||||
|
attestations: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v6
|
||||||
|
- name: Generate artifact attestation
|
||||||
|
uses: actions/attest-build-provenance@v3
|
||||||
|
with:
|
||||||
|
subject-path: "wheels-*/*"
|
||||||
|
- name: Publish to PyPI
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
uses: PyO3/maturin-action@v1
|
||||||
|
env:
|
||||||
|
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
|
with:
|
||||||
|
command: upload
|
||||||
|
args: --non-interactive --skip-existing wheels-*/*
|
||||||
64
agents/tapo/tapo-fork/.github/workflows/rs-ci.yml
vendored
Normal file
64
agents/tapo/tapo-fork/.github/workflows/rs-ci.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
name: Rust
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
checks:
|
||||||
|
name: Rust checks
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: clippy, rustfmt
|
||||||
|
- uses: davidB/rust-cargo-make@v1
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- name: Run format
|
||||||
|
run: cargo make format
|
||||||
|
- name: Run check
|
||||||
|
run: cargo make check
|
||||||
|
- name: Run check doc
|
||||||
|
run: cargo make check-doc
|
||||||
|
- name: Run clippy
|
||||||
|
run: cargo make clippy
|
||||||
|
- name: Run test
|
||||||
|
run: cargo make test
|
||||||
|
checks_min_ver:
|
||||||
|
name: Rust checks on the minimum supported version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
toolchain: 1.88.0
|
||||||
|
- uses: davidB/rust-cargo-make@v1
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- name: Run check on the minimum supported version
|
||||||
|
run: cargo +1.88.0 make check
|
||||||
|
publish:
|
||||||
|
name: Publish
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
|
||||||
|
needs: [checks, checks_min_ver]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Run cargo login
|
||||||
|
run: cargo login ${CRATES_IO_TOKEN}
|
||||||
|
env:
|
||||||
|
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||||
|
- name: Run build
|
||||||
|
run: cargo build --package tapo --release --verbose
|
||||||
|
- name: Run cargo publish
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
run: cargo publish --package tapo
|
||||||
26
agents/tapo/tapo-fork/.github/workflows/rs-security-audit.yml
vendored
Normal file
26
agents/tapo/tapo-fork/.github/workflows/rs-security-audit.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Security
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "**/Cargo.toml"
|
||||||
|
- "**/Cargo.lock"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "**/Cargo.toml"
|
||||||
|
- "**/Cargo.lock"
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
jobs:
|
||||||
|
security_audit:
|
||||||
|
name: Audit
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: actions-rs/audit-check@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
3
agents/tapo/tapo-fork/.gitignore
vendored
Normal file
3
agents/tapo/tapo-fork/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
.vscode
|
||||||
|
/.idea
|
||||||
800
agents/tapo/tapo-fork/CHANGELOG.md
Normal file
800
agents/tapo/tapo-fork/CHANGELOG.md
Normal file
@@ -0,0 +1,800 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this
|
||||||
|
file. This change log follows the conventions of
|
||||||
|
[keepachangelog.com][keepachangelog].
|
||||||
|
|
||||||
|
## [Rust Unreleased][Unreleased]
|
||||||
|
|
||||||
|
## [Python Unreleased][Unreleased]
|
||||||
|
|
||||||
|
## [Rust v0.8.8][v0.8.8] - 2025-11-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `TapoResponseError`: added `Forbidden` variant to represent authentication failures when Third-Party Compatibility is disabled in the Tapo app.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `TapoResponseError`: renamed variant `InvalidCredentials` to `Unauthorized` and updated the variant to include `code` and `description` for improved error context.
|
||||||
|
|
||||||
|
## [Python v0.8.8][v0.8.8] - 2025-11-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `Tapo` Exception: added `Forbidden` variant to represent authentication failures when Third-Party Compatibility is disabled in the Tapo app.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `Tapo` Exception: renamed variant `InvalidCredentials` to `Unauthorized` and updated the variant to include `code` and `description` for improved error context.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Dropped support for Python 3.9 and 3.10 (both no longer supported upstream). The minimum required version is now Python 3.11.
|
||||||
|
|
||||||
|
## [Rust v0.8.7][v0.8.7] - 2025-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `HubHandler`: added the `device_reboot` and `device_reset` methods.
|
||||||
|
- `PowerStripEnergyMonitoringHandler`: added the `device_reboot` and `device_reset` methods.
|
||||||
|
- `PowerStripHandler`: added the `device_reboot` and `device_reset` methods.
|
||||||
|
- `ColorLightHandler`: added the `device_reboot` method.
|
||||||
|
- `LightHandler`: added the `device_reboot` method.
|
||||||
|
- `PlugEnergyMonitoringHandler`: added the `device_reboot` method.
|
||||||
|
- `PlugHandler`: added the `device_reboot` method.
|
||||||
|
- `RgbicLightStripHandler`: added the `device_reboot` method.
|
||||||
|
- `RgbLightStripHandler`: added the `device_reboot` method.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `device_reset`: now requires the `DeviceManagementExt` trait to be in scope. The newly added `device_reboot` method also requires this trait.
|
||||||
|
|
||||||
|
## [Python v0.8.7][v0.8.7] - 2025-11-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `tapo`: added support for Python 3.14.
|
||||||
|
- `HubHandler`: added the `device_reboot` and `device_reset` methods.
|
||||||
|
- `PowerStripEnergyMonitoringHandler`: added the `device_reboot` and `device_reset` methods.
|
||||||
|
- `PowerStripHandler`: added the `device_reboot` and `device_reset` methods.
|
||||||
|
- `ColorLightHandler`: added the `device_reboot` method.
|
||||||
|
- `LightHandler`: added the `device_reboot` method.
|
||||||
|
- `PlugEnergyMonitoringHandler`: added the `device_reboot` method.
|
||||||
|
- `PlugHandler`: added the `device_reboot` method.
|
||||||
|
- `RgbicLightStripHandler`: added the `device_reboot` method.
|
||||||
|
- `RgbLightStripHandler`: added the `device_reboot` method.
|
||||||
|
|
||||||
|
## [Rust v0.8.6][v0.8.6] - 2025-09-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `PlugEnergyMonitoringHandler`: added `get_power_data` method to retrieve historical power data (every 5 minutes & hourly) for energy-monitoring plugs (P110, P110M, P115). The `PowerDataInterval` enum allows specifying the desired interval.
|
||||||
|
- `PowerStripPlugEnergyMonitoringHandler`: added the following energy monitoring methods: `get_current_power`, `get_device_usage`, `get_energy_usage`, `get_energy_data`, `get_power_data`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `PlugEnergyMonitoringHandler`: `EnergyDataResult` has been redesigned to provide better ergonomics by attaching a start date time to each interval entry.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- `EnergyUsageResult`: the `current_power` field has been removed from the struct because not all energy-monitoring plugs provide this data. Instead, use the `get_current_power` method to retrieve the current power.
|
||||||
|
|
||||||
|
## [Python v0.8.6][v0.8.6] - 2025-09-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `PlugEnergyMonitoringHandler`: added `get_power_data` method to retrieve historical power data (every 5 minutes & hourly) for energy-monitoring plugs (P110, P110M, P115). The `PowerDataInterval` enum allows specifying the desired interval.
|
||||||
|
- `PowerStripPlugEnergyMonitoringHandler`: added the following energy monitoring methods: `get_current_power`, `get_device_usage`, `get_energy_usage`, `get_energy_data`, `get_power_data`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `PlugEnergyMonitoringHandler`: `EnergyDataResult` has been redesigned to provide better ergonomics by attaching a start date time to each interval entry.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- `EnergyUsageResult`: the `current_power` field has been removed from the class because not all energy-monitoring plugs provide this data. Instead, use the `get_current_power` method to retrieve the current power.
|
||||||
|
|
||||||
|
## [Rust v0.8.5][v0.8.5] - 2025-09-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `ApiClient`: added `discover_devices` method to discover all Tapo devices on the local network. This works even with dynamic or unknown IPs, but is slower since it scans the entire network and waits for device responses.
|
||||||
|
- `PowerStripPlugHandler`: added support for P306 power strips.
|
||||||
|
- Added `PowerStripEnergyMonitoringHandler`, `PowerStripPlugEnergyMonitoringHandler`, and `PowerStripPlugEnergyMonitoringResult` to support energy-monitoring power strips (P304M, P316M). Non-monitoring models (P300, P306) will continue using the pre-existing `PowerStripPlugHandler`.
|
||||||
|
- `PowerStripPlugResult`: added `default_states` field.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `DeviceInfoPowerStripResult`: changed `time_diff` from `Option<i64>` to `i64`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `PowerStripPlugResult`: removed `charging_status`, `overcurrent_status`, and `power_protection_status` (not returned by P300/P306).
|
||||||
|
|
||||||
|
## [Python v0.8.5][v0.8.5] - 2025-09-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `ApiClient`: added `discover_devices` method to discover all Tapo devices on the local network. This works even with dynamic or unknown IPs, but is slower since it scans the entire network and waits for device responses.
|
||||||
|
- `PowerStripPlugHandler`: added support for P306 power strips.
|
||||||
|
- Added `PowerStripEnergyMonitoringHandler`, `PowerStripPlugEnergyMonitoringHandler`, and `PowerStripPlugEnergyMonitoringResult` to support energy-monitoring power strips (P304M, P316M). Non-monitoring models (P300, P306) will continue using the pre-existing `PowerStripPlugHandler`.
|
||||||
|
- `PowerStripPlugResult`: added `default_states` field.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `DeviceInfoPowerStripResult`: changed `time_diff` from `Optional[int]` to `int`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `PowerStripPlugResult`: removed `charging_status`, `overcurrent_status`, and `power_protection_status` (not returned by P300/P306).
|
||||||
|
|
||||||
|
## [Rust v0.8.4][v0.8.4] - 2025-08-11
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The `default_states` property has been added to the `DeviceInfoPlugResult` struct to provide a more comprehensive overview of the plug's state.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The `default_states` property value of the `DeviceInfoPlugEnergyMonitoringResult` struct has been changed from a struct to an enum to better reflect the actual response from the device.
|
||||||
|
|
||||||
|
## [Python v0.8.4][v0.8.4] - 2025-08-11
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The `default_states` property has been added to the `DeviceInfoPlugResult` class to provide a more comprehensive overview of the plug's state.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The `default_states` property value of the `DeviceInfoPlugEnergyMonitoringResult` class has been changed from a class to an enum to better reflect the actual response from the device.
|
||||||
|
|
||||||
|
## [Rust v0.8.3][v0.8.3] - 2025-07-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the P316M power strips.
|
||||||
|
- The `charging_status`, `overcurrent_status`, and `power_protection_status` fields have been added to `PowerStripPlugResult`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Enhanced the `InvalidCredentials` error with clearer guidance on common causes and how to address them.
|
||||||
|
- The `overheat_status` field in `PowerStripPlugResult` is now optional to support devices that omit this field.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed `nickname` from `DeviceInfoPowerStripResult` because it is not present in the response.
|
||||||
|
|
||||||
|
## [Python v0.8.3][v0.8.3] - 2025-07-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the P316M power strips.
|
||||||
|
- The `charging_status`, `overcurrent_status`, and `power_protection_status` fields have been added to `PowerStripPlugResult`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Enhanced the `InvalidCredentials` error with clearer guidance on common causes and how to address them.
|
||||||
|
- The `overheat_status` field in `PowerStripPlugResult` is now optional to support devices that omit this field.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed `nickname` from `DeviceInfoPowerStripResult` because it is not present in the response.
|
||||||
|
|
||||||
|
## [Rust v0.8.2][v0.8.2] - 2025-05-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The `charging_status` field has been added to `DeviceInfoPlugEnergyMonitoringResult`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The `overheat_status` field in `DeviceInfoPlugEnergyMonitoringResult` is now optional to support devices that omit this field after the latest firmware update (1.3.4 Build 250403 Rel.150504).
|
||||||
|
|
||||||
|
## [Python v0.8.2][v0.8.2] - 2025-05-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The `charging_status` field has been added to `DeviceInfoPlugEnergyMonitoringResult`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The `overheat_status` field in `DeviceInfoPlugEnergyMonitoringResult` is now optional to support devices that omit this field after the latest firmware update (1.3.4 Build 250403 Rel.150504).
|
||||||
|
|
||||||
|
## [Rust v0.8.1][v0.8.1] - 2025-02-10
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added functionality for controlling the alarm on the H100 hub via the `play_alarm` and `stop_alarm` methods in the `H100Handler`. Additionally, `get_supported_ringtone_list` is available to retrieve the list of supported ringtones for debugging purposes. (thanks to @kay)
|
||||||
|
- Added the ability to retrieve the color configuration (`hue`, `saturation`, `color_temperature`) for the `Color` enum values through the `get_color_config` method. (thanks to @WhySoBad)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The internal implementation of `H100Handler`'s `get_child_device_list` has been updated to fetch all pages, not just the first one.
|
||||||
|
- `H100Handler`'s `get_child_device_list_json` now includes a `start_index` parameter to fetch child devices starting from a specific index.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Resolved an issue that caused the passthrough protocol test to incorrectly indicate support when it was not actually supported. (thanks to @WhySoBad)
|
||||||
|
|
||||||
|
## [Python v0.8.1][v0.8.1] - 2025-02-10
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added functionality for controlling the alarm on the H100 hub via the `play_alarm` and `stop_alarm` methods in the `H100Handler`. Additionally, `get_supported_ringtone_list` is available to retrieve the list of supported ringtones for debugging purposes.
|
||||||
|
- Added the ability to retrieve the color configuration (`hue`, `saturation`, `color_temperature`) for the `Color` enum values through the `get_color_config` method. (thanks to @WhySoBad)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The internal implementation of `H100Handler`'s `get_child_device_list` has been updated to fetch all pages, not just the first one.
|
||||||
|
- `H100Handler`'s `get_child_device_list_json` now includes a `start_index` parameter to fetch child devices starting from a specific index.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Resolved an issue that caused the passthrough protocol test to incorrectly indicate support when it was not actually supported. (thanks to @WhySoBad)
|
||||||
|
|
||||||
|
## [Rust v0.8.0][v0.8.0] - 2024-12-07
|
||||||
|
|
||||||
|
This marks the first unified release of the Rust and Python libraries. Moving forward, both libraries will be released simultaneously and will share the same version number.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added an example for the L900 light strips.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `LightingEffect`'s `fadeoff` field has been renamed to `fade_off`, and its `with_fadeoff` method has been renamed to `with_fade_off`.
|
||||||
|
- `LightingEffect`'s `new_with_random_id` function has been removed. The `new` function now creates a `LightingEffect` instance with a random ID by default.
|
||||||
|
|
||||||
|
## [Python v0.8.0][v0.8.0] - 2024-12-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the L900 light strips.
|
||||||
|
- Added support for the L920 and L930 light strips.
|
||||||
|
- Added support for Python 3.13.
|
||||||
|
|
||||||
|
## [Python v0.7.0][py-v0.7.0] - 2024-11-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the KE100 thermostatic radiator valve (TRV).
|
||||||
|
|
||||||
|
## [Rust v0.7.17][v0.7.17] - 2024-10-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the P304 power strip.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The `openssl` dependency has been replaced with native Rust alternatives to expand cross-compilation options, such as for Android, and to decrease build times (thanks to @rbock44).
|
||||||
|
- `PlugPowerStripHandler` has been renamed to `PowerStripPlugHandler` to be consistent with the rest of the library.
|
||||||
|
- `PlugPowerStripResult` has been renamed to `PowerStripPlugResult` to be consistent with the rest of the library.
|
||||||
|
- The `UsageByPeriodResult` fields `today`, `past7`, and `past30` have been updated to `Option<u64>` to handle cases where the API returns negative values, which will be represented as `None`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Updated all comments referencing Watts to confirm the correct units are specified.
|
||||||
|
|
||||||
|
## [Python v0.6.0][py-v0.6.0] - 2024-10-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the P300 and P304 power strips.
|
||||||
|
- Python logs can now capture entries from the underlying Rust library.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The `openssl` dependency has been replaced with native Rust alternatives to expand cross-compilation options, such as for Android, and to decrease build times (thanks to @rbock44).
|
||||||
|
- The `UsageByPeriodResult` fields `today`, `past7`, and `past30` have been updated to `Optional[int]` to handle cases where the API returns negative values, which will be represented as `null`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Updated all comments referencing Watts to confirm the correct units are specified.
|
||||||
|
|
||||||
|
## [Rust v0.7.16][v0.7.16] - 2024-09-27
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the L535 light bulbs.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue that prevented the color from being set properly for the L535 light bulbs.
|
||||||
|
|
||||||
|
## [Python v0.5.1][py-v0.5.1] - 2024-09-27
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the L535 light bulbs.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue that prevented the color from being set properly for the L535 light bulbs.
|
||||||
|
|
||||||
|
## [Rust v0.7.15][v0.7.15] - 2024-09-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The `LowBattery` variant has been added to the `S200BLog` enum.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The `t310` and `t315` methods of `HubHandler` can now create `T31XHandler` handlers for either of the two device types.
|
||||||
|
- The child device handlers for the H100 hub and the P300 power strip have been redesigned to eliminate the use of lifetimes, to facilitate FFI integrations.
|
||||||
|
- The comments of `start_timestamp` and `end_timestamp` fields in `EnergyDataResult` have been updated to better describe their purpose.
|
||||||
|
- `S200BRotationParams`'s field `degrees` has been renamed to `rotation_degrees`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue with the `Color` presets that triggered a validation error when attempting to set the `color` to `DarkRed`.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- The deprecated `past24h`, `past7d`, `past30d` and `past1y` fields have been removed from `EnergyUsageResult`. This data is now available exclusively through `get_energy_data`'s `EnergyDataResult` response.
|
||||||
|
|
||||||
|
## [Python v0.5.0][py-v0.5.0] - 2024-09-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added full support for the S200B switches through the `S200BHandler` handler.
|
||||||
|
- Added full support for the T100 sensors through the `T100Handler` handler.
|
||||||
|
- Added full support for the T110 sensors through the `T110Handler` handler.
|
||||||
|
- Added full support for the T300 sensors through the `T300Handler` handler.
|
||||||
|
- Added full support for the T310 and T315 sensors through the `T31XHandler` handler.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The comments of `start_timestamp` and `end_timestamp` fields in `EnergyDataResult` have been updated to better describe their purpose.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue with the `Color` presets that triggered a validation error when attempting to set the `color` to `DarkRed`.
|
||||||
|
|
||||||
|
## [Rust v0.7.14][v0.7.14] - 2024-08-31
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `DeviceInfoPlugEnergyMonitoringResult` has been added to support the P110 and P115 devices, which have different responses compared to the P100 and P105 devices.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `DeviceInfoPlugResult` has been updated to correctly support the P100 and P105 devices.
|
||||||
|
|
||||||
|
## [Python v0.4.0][py-v0.4.0] - 2024-08-31
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `DeviceInfoPlugEnergyMonitoringResult` has been added to support the P110 and P115 devices, which have different responses compared to the P100 and P105 devices.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Resolved an issue that led to unrecoverable process hangs when a device request timed out.
|
||||||
|
- The concurrency of device handlers has been significantly enhanced by replacing all `Mutex` instances with `RwLock`.
|
||||||
|
- `DeviceInfoPlugResult` has been updated to correctly support the P100 and P105 devices.
|
||||||
|
|
||||||
|
## [Rust v0.7.13][v0.7.13] - 2024-08-26
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- To align with the latest API updates, the `overheated` field for plugs has been replaced by three enums: `overcurrent_status`, `overheat_status`, and `power_protection_status` (thanks to @padenot).
|
||||||
|
|
||||||
|
## [Python v0.3.2][py-v0.3.2] - 2024-08-26
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- To align with the latest API updates, the `overheated` field for plugs has been replaced by three enums: `overcurrent_status`, `overheat_status`, and `power_protection_status`.
|
||||||
|
|
||||||
|
## [Rust v0.7.12][v0.7.12] - 2024-06-27
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- H100's create child device handler methods now take a `HubDevice` `enum` instead of a `String` and are now `async` to allow for more flexibility. This enables the caller to find child devices by either device ID or nickname.
|
||||||
|
- `PlugIdentifier` has been renamed to `Plug`.
|
||||||
|
- `Plug::ByDeviceId` now verifies that the provided device ID is found and returns an `Error::DeviceNotFound` error when it's not.
|
||||||
|
- `HubDevice` variants now take a `String` instead of a `&str` to allow for more flexibility.
|
||||||
|
- `Plug` variants now take a `String` instead of a `&str` to allow for more flexibility.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `ColorLightSetDeviceInfoParams` `hue` field validation has been changed from `between 1 and 360` to `between 0 and 360` to match the device's expected range.
|
||||||
|
- Fixed an issue where the `EnergyDataResult`'s `start_timestamp` and `end_timestamp` did not correctly adjust for timezone offsets.
|
||||||
|
- The `chrono` dependency has been updated to `0.4.34` to fix the minimum version requirement.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- The `overheated` property has been removed from `DeviceInfoGenericResult` because it's not present in the response of all devices.
|
||||||
|
|
||||||
|
## [Python v0.3.1][py-v0.3.1] - 2024-06-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `ColorLightSetDeviceInfoParams` `hue` field validation has been changed from `between 1 and 360` to `between 0 and 360` to match the device's expected range.
|
||||||
|
- Fixed an issue where the `EnergyDataResult`'s `start_timestamp` and `end_timestamp` did not correctly adjust for timezone offsets.
|
||||||
|
- All handlers are now correctly exported and can be imported from the `tapo` module.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- The `overheated` property has been removed from `DeviceInfoGenericResult` because it's not present in the response of all devices.
|
||||||
|
|
||||||
|
## [Rust v0.7.11][v0.7.11] - 2024-05-04
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the P300 power strip (thanks to @Michal-Szczepaniak).
|
||||||
|
- `RgbLightStripHandler` and `DeviceInfoRgbLightStripResult` have been added to support the L900 devices separately from the L530 and L630 devices.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `ChildDeviceResult` has been renamed to `ChildDeviceHubResult` to facilitate adding support for other devices with children.
|
||||||
|
- `ColorLightStripHandler` has been renamed to `RgbicLightStripHandler` to better reflect its purpose.
|
||||||
|
- `DeviceInfoColorLightStripResult` has been renamed to `DeviceInfoRgbicLightStripResult` to better reflect its purpose.
|
||||||
|
|
||||||
|
## [Python v0.3.0][py-v0.3.0] - 2024-05-04
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added partial support for the H100 hub and its child devices. Currently, only the `get_device_info` function is supported for the child devices through the hub's `get_child_device_list` method.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- A large number of types have been reorganized to me more in line with the Rust library. This includes moving many of them under the `requests` and `responses` sub modules.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- `l900` has been removed from the `ApiClient` until proper support is added.
|
||||||
|
|
||||||
|
## [Rust v0.7.10][v0.7.10] - 2024-04-05
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The implementation of `ApiClient::new` has been improved to allow for the return of `ApiClient` instead of `Result<ApiClient, Error>`.
|
||||||
|
- The default timeout for all requests has been reduced to 30 seconds from 300 seconds.
|
||||||
|
- `ApiClient::with_timeout` has been added to allow for the setting of a custom timeout for all requests (thanks to @skoky).
|
||||||
|
|
||||||
|
## [Python v0.2.1][py-v0.2.1] - 2024-04-05
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The default timeout for all requests has been reduced to 30 seconds from 300 seconds.
|
||||||
|
- The `timeout_s` optional parameter has been added to the `ApiClient` constructor to allow for the setting of a custom timeout for all requests (thanks to @skoky).
|
||||||
|
|
||||||
|
## [Rust v0.7.9][v0.7.9] - 2024-01-27
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The `send()` method of the `.set()` API now takes a reference to the device handler in order to allow for better ergonomics.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The device info response for the L510, L520, and L610 devices has been fixed.
|
||||||
|
|
||||||
|
## [Python v0.2.0][py-v0.2.0] - 2024-01-27
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the L530, L630, and L900 color light bulbs.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed a misconfiguration that was preventing the sdist package from working properly.
|
||||||
|
- The device info response for the L510, L520, and L610 devices has been fixed.
|
||||||
|
|
||||||
|
## [Rust v0.7.8][v0.7.8] - 2024-01-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the `device_reset` method to all plugs and lights.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The device info response for the L510, L520, and L610 devices has been fixed to have the `re_power_type` field as optional.
|
||||||
|
|
||||||
|
## [Python v0.1.5][py-v0.1.5] - 2024-01-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the `device_reset` method to all plugs and lights.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The device info response for the L510, L520, and L610 devices has been fixed to have the `re_power_type` field as optional.
|
||||||
|
|
||||||
|
## [Rust v0.7.7][v0.7.7] - 2024-01-13
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The `anyhow::anyhow!("Local hash does not match server hash")` error has been replaced with the more specific `tapo::TapoResponseError::InvalidCredentials` error.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The `default_states` field that's part of the device info response has been changed for the L510, L520, and L610 devices to match the actual response from the device.
|
||||||
|
- A handful of edge cases around the Klap Protocol that were causing panics have been fixed to return `tapo::TapoResponseError::SessionTimeout` or `tapo::TapoResponseError::InvalidResponse` errors instead.
|
||||||
|
|
||||||
|
## [Python v0.1.4][py-v0.1.4] - 2024-01-13
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The "Local hash does not match server hash" error has been replaced with the more specific `tapo::TapoResponseError::InvalidCredentials` error.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The `default_states` field that's part of the device info response has been changed for the L510, L520, and L610 devices to match the actual response from the device.
|
||||||
|
- A handful of edge cases around the Klap Protocol that were causing panics have been fixed to return `tapo::TapoResponseError::SessionTimeout` or `tapo::TapoResponseError::InvalidResponse` errors instead.
|
||||||
|
|
||||||
|
## [Rust v0.7.6][v0.7.6] - 2023-11-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the KE100 thermostatic radiator valve (TRV) devices (thanks to @pwoerndle).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue that was preventing the `nickname` field from being decoded in the `get_device_info` results of child devices of the H100 hub.
|
||||||
|
|
||||||
|
## [Rust v0.7.5][v0.7.5] - 2023-11-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the T300 water sensor.
|
||||||
|
- Added a dedicated handler for the L520 devices.
|
||||||
|
|
||||||
|
## [Python v0.1.3][py-v0.1.3] - 2023-11-04
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the L510, L520 and L610 light bulbs.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The minimum required version of Python has been changed to 3.8, up from 3.7.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue that was preventing `get_device_info_json` from working on the plug devices.
|
||||||
|
|
||||||
|
## [Python v0.1.2][py-v0.1.2] - 2023-10-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for generic devices.
|
||||||
|
- Added `get_device_info_json` to all currently supported devices.
|
||||||
|
|
||||||
|
## [Python v0.1.1][py-v0.1.1] - 2023-10-01
|
||||||
|
|
||||||
|
This is the first version of the Python wrapper library.
|
||||||
|
It supports the plug devices P100, P105, P110 and P115.
|
||||||
|
|
||||||
|
## [v0.7.4] - 2023-09-15
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the minimum version of the chrono dependency by setting it to 0.4.25.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- `DeviceUsageResult` has been split into `DeviceUsageResult` and `DeviceUsageEnergyMonitoringResult`. The former is now returned for the P100 and P105 devices while the latter is returned for all the other devices that support energy monitoring.
|
||||||
|
- `EnergyMonitoringPlugHandler` has been renamed to `PlugEnergyMonitoringHandler`.
|
||||||
|
- All `___DeviceInfoResult` structs have been renamed to `DeviceInfo___Result`.
|
||||||
|
- All `___DefaultState` structs have been renamed to `Default___State`.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- `get_device_usage` has been removed from the `GenericDeviceHandler` because it is not supported by all devices.
|
||||||
|
|
||||||
|
## [v0.7.3] - 2023-09-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the newly introduced KLAP protocol, which is required to interact with the latest firmware version of multiple devices.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- All uses of `time` have been replaced with `chrono`:
|
||||||
|
- `EnergyDataInterval`'s `time::OffsetDateTime` and `time::Date` fields have been replaced with `chrono::NaiveDate`.
|
||||||
|
- `EnergyUsageResult::local_time` field is now `chrono::NaiveDateTime` instead of `time::OffsetDateTime`.
|
||||||
|
- `EnergyDataResult::local_time` field is now `chrono::NaiveDateTime` instead of `time::OffsetDateTime`.
|
||||||
|
- `TemperatureHumidityRecords`'s and `TemperatureHumidityRecord` `datetime` fields are now `chrono::DateTime<chrono::Utc>` instead of `time::OffsetDateTime`.
|
||||||
|
- `EnergyDataInterval::Hourly::start_datetime` and `EnergyDataInterval::Hourly::end_datetime` have been renamed to `start_date` and `end_date` because the time component is not required.
|
||||||
|
- The `login` function on all handlers has been renamed to `refresh_session` to better reflect its purpose and it now takes and returns a `&mut self` instead of `self`.
|
||||||
|
- `L510DeviceInfoResult` has been renamed to `LightDeviceInfoResult` to better reflect its purpose when used for L510 and L610 devices.
|
||||||
|
- `L530DeviceInfoResult` has been renamed to `ColorLightDeviceInfoResult` to better reflect its purpose when used for L530, L630 and L900 devices.
|
||||||
|
- `L930DeviceInfoResult` has been renamed to `ColorLightStripDeviceInfoResult` to better reflect its purpose when used for L920 and L930 devices.
|
||||||
|
- The `default_states` field of `LightDeviceInfoResult`, `ColorLightDeviceInfoResult`, `ColorLightStripDeviceInfoResult` and `PlugDeviceInfoResult` is now a struct instead of an enum.
|
||||||
|
|
||||||
|
## [v0.7.2] - 2023-08-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `get_current_power` to the `P110` and `P115` plugs. (thanks to @Michal-Szczepaniak)
|
||||||
|
|
||||||
|
## [v0.7.1] - 2023-05-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `get_temperature_humidity_records` to the `T310` and `T315` sensors.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The creation of device handlers has been simplified.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// old
|
||||||
|
let device = ApiClient::new(ip_address, tapo_username, tapo_password)?
|
||||||
|
.l530()
|
||||||
|
.login()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// new
|
||||||
|
let device = ApiClient::new(tapo_username, tapo_password)?
|
||||||
|
.l530(ip_address)
|
||||||
|
.await?;
|
||||||
|
```
|
||||||
|
|
||||||
|
- The creation of child device handlers has been reworked so that they can be created without requiring a call to `get_child_device_list` when the child Device ID is known.
|
||||||
|
- `ApiClient` now implements `Clone` to allow for a cheaper duplication of the client.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- The `L510` and `L610` devices no longer expose the `set()` API because changing multiple properties simultaneously does not make sense for these devices.
|
||||||
|
|
||||||
|
## [v0.7.0] - 2023-05-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added initial support for the H100 device, the S200B switch and the T100, T110, T310, T315 sensors. The child devices currently support `get_device_info` and `get_trigger_logs`.
|
||||||
|
- All responses now derive `serde::Serialize` to allow for more straightforward consumer serialisation. (thanks to @ClementNerma)
|
||||||
|
- `ApiClient` has been marked as both `Send` and `Sync` to allow for sharing between threads. (thanks to @ClementNerma)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `GenericDeviceInfoResult`'s `device_on` property has been made optional to accommodate devices that do not provide this field.
|
||||||
|
|
||||||
|
## [v0.6.0] - 2023-05-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the L920 and L930 light strips. The highlight is the `tapo::ColorLightStripHandler::set_lighting_effect` function, which supports all the effects that the Tapo app contains alongside user-defined effects.
|
||||||
|
- Added support for the L900 light strips.
|
||||||
|
- Each supported device now has it's own handler creator.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `set_*` functions like `tapo::requests::ColorLightSetDeviceInfoParams::set_brightness` now return `Self` instead of `Result<Self, Error>` to allow for better ergonomics. The validations will now run when `tapo::requests::ColorLightSetDeviceInfoParams::send` is called.
|
||||||
|
- `tapo::requests::L510SetDeviceInfoParams` has been renamed to `tapo::requests::LightSetDeviceInfoParams` to better reflect its purpose when used for L510, L610, and L900 devices.
|
||||||
|
- `tapo::requests::L530SetDeviceInfoParams` has been renamed to `tapo::requests::ColorLightSetDeviceInfoParams` to better reflect its purpose when used for L530, L630, L920 and L930 devices.
|
||||||
|
- `tapo::P100Handler` has been renamed to `tapo::PlugHandler`.
|
||||||
|
- `tapo::P110Handler` has been renamed to `tapo::EnergyMonitoringPlugHandler`.
|
||||||
|
- `tapo::L510Handler` has been renamed to `tapo::LightHandler`.
|
||||||
|
- `tapo::L530Handler` has been renamed to `tapo::ColorLightHandler`.
|
||||||
|
- `tapo::L930Handler` has been renamed to `tapo::ColorLightStripHandler`.
|
||||||
|
|
||||||
|
## [v0.5.0] - 2023-04-16
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The creation of an API Client for a specific device is now done through handler methods on the `ApiClient` struct. This allows for a more ergonomic API. (thanks to [Octocrab](https://github.com/XAMPPRocky/octocrab) for inspirations)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// old
|
||||||
|
let device = ApiClient::<L530>::new(ip_address, tapo_username, tapo_password, true).await?;
|
||||||
|
|
||||||
|
// new
|
||||||
|
let device = ApiClient::new(ip_address, tapo_username, tapo_password)?
|
||||||
|
.l530()
|
||||||
|
.login()
|
||||||
|
.await?;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `ApiClient::new` parameters are now `impl Into<String>` instead of `String` to allow for more flexibility.
|
||||||
|
- Error handling has been reworked. All functions that could error now return a `Result<..., tapo::Error>`.
|
||||||
|
|
||||||
|
## [v0.4.0] - 2023-02-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `get_energy_data` is now available for the *P110* devices. (thanks to @kuhschnappel)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `EnergyUsageResult`'s `past24h`, `past7d`, `past30d` and `past1y` fields are now deprecated. `get_energy_data` should be used instead. (thanks to @felixhauptmann)
|
||||||
|
|
||||||
|
## [v0.3.1] - 2023-02-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `examples/tapo_generic_device_toggle.rs` demonstrates how `device_info` can be used to assess the current status of a generic device and toggle it.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `on_time` is now optional for the `L510` and `L530` devices because the v2 hardware no longer returns it.
|
||||||
|
|
||||||
|
## [v0.3.0] - 2022-11-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- The `set` API allows multiple properties to be set in a single request for the _L510_ and _L530_ devices.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `tapo::Color` has been moved to `tapo::requests::Color`.
|
||||||
|
- `GenericDeviceInfoResult::on_time` has been changed from `u64` to `Option<u64>` because some devices (like *L930*) do not provide this field.
|
||||||
|
- All response structs have been moved under `tapo::responses`.
|
||||||
|
- The docs have been improved.
|
||||||
|
|
||||||
|
## [v0.2.1] - 2022-08-07
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `latitude` and `longitude` in `GenericDeviceInfoResult`, `L510DeviceInfoResult`, `L530DeviceInfoResult` and `PlugDeviceInfoResult` are now signed integers to accommodate for incoming responses with negative numbers. (thanks to @JPablomr)
|
||||||
|
|
||||||
|
## [v0.2.0] - 2022-06-13
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Generic Device example.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `get_device_usage` has been moved to the base implementation so that all devices have access to it.
|
||||||
|
- `Color` now implements `serde::Serialize` and `serde::Deserialize`.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- `TapoDeviceExt` is no longer has `Default` and `serde::Serialize` as supersets.
|
||||||
|
|
||||||
|
## [v0.1.0] - 2022-06-07
|
||||||
|
|
||||||
|
### Initial Release of Tapo
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/mihai-dinculescu/tapo
|
||||||
|
[v0.8.7]: https://github.com/mihai-dinculescu/tapo/tree/v0.8.7
|
||||||
|
[v0.8.6]: https://github.com/mihai-dinculescu/tapo/tree/v0.8.6
|
||||||
|
[v0.8.5]: https://github.com/mihai-dinculescu/tapo/tree/v0.8.5
|
||||||
|
[v0.8.4]: https://github.com/mihai-dinculescu/tapo/tree/v0.8.4
|
||||||
|
[v0.8.3]: https://github.com/mihai-dinculescu/tapo/tree/v0.8.3
|
||||||
|
[v0.8.2]: https://github.com/mihai-dinculescu/tapo/tree/v0.8.2
|
||||||
|
[v0.8.1]: https://github.com/mihai-dinculescu/tapo/tree/v0.8.1
|
||||||
|
[v0.8.0]: https://github.com/mihai-dinculescu/tapo/tree/v0.8.0
|
||||||
|
[py-v0.7.0]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.7.0
|
||||||
|
[v0.7.17]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.17
|
||||||
|
[py-v0.6.0]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.6.0
|
||||||
|
[v0.7.16]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.16
|
||||||
|
[py-v0.5.1]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.5.1
|
||||||
|
[v0.7.15]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.15
|
||||||
|
[py-v0.5.0]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.5.0
|
||||||
|
[v0.7.14]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.14
|
||||||
|
[py-v0.4.0]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.4.0
|
||||||
|
[v0.7.13]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.13
|
||||||
|
[py-v0.3.2]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.3.2
|
||||||
|
[v0.7.12]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.12
|
||||||
|
[py-v0.3.1]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.3.1
|
||||||
|
[v0.7.11]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.11
|
||||||
|
[py-v0.3.0]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.3.0
|
||||||
|
[v0.7.10]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.10
|
||||||
|
[py-v0.2.1]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.2.1
|
||||||
|
[v0.7.9]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.9
|
||||||
|
[py-v0.2.0]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.2.0
|
||||||
|
[v0.7.8]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.8
|
||||||
|
[py-v0.1.5]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.1.5
|
||||||
|
[v0.7.7]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.7
|
||||||
|
[py-v0.1.4]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.1.4
|
||||||
|
[v0.7.6]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.6
|
||||||
|
[v0.7.5]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.5
|
||||||
|
[py-v0.1.3]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.1.3
|
||||||
|
[py-v0.1.2]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.1.2
|
||||||
|
[py-v0.1.1]: https://github.com/mihai-dinculescu/tapo/tree/py-v0.1.1
|
||||||
|
[v0.7.4]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.4
|
||||||
|
[v0.7.3]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.3
|
||||||
|
[v0.7.2]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.2
|
||||||
|
[v0.7.1]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.1
|
||||||
|
[v0.7.0]: https://github.com/mihai-dinculescu/tapo/tree/v0.7.0
|
||||||
|
[v0.6.0]: https://github.com/mihai-dinculescu/tapo/tree/v0.6.0
|
||||||
|
[v0.5.0]: https://github.com/mihai-dinculescu/tapo/tree/v0.5.0
|
||||||
|
[v0.4.0]: https://github.com/mihai-dinculescu/tapo/tree/v0.4.0
|
||||||
|
[v0.3.1]: https://github.com/mihai-dinculescu/tapo/tree/v0.3.1
|
||||||
|
[v0.3.0]: https://github.com/mihai-dinculescu/tapo/tree/v0.3.0
|
||||||
|
[v0.2.1]: https://github.com/mihai-dinculescu/tapo/tree/v0.2.1
|
||||||
|
[v0.2.0]: https://github.com/mihai-dinculescu/tapo/tree/v0.2.0
|
||||||
|
[v0.1.0]: https://github.com/mihai-dinculescu/tapo/tree/v0.1.0
|
||||||
|
[keepachangelog]: https://keepachangelog.com
|
||||||
28
agents/tapo/tapo-fork/CONTRIBUTING.md
Normal file
28
agents/tapo/tapo-fork/CONTRIBUTING.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Contributions are welcome and encouraged! See [/issues][issues] for ideas, or suggest your own!
|
||||||
|
If you're thinking to create a PR with large feature/change, please first discuss it in an issue.
|
||||||
|
|
||||||
|
[issues]: https://github.com/mihai-dinculescu/tapo/issues
|
||||||
|
|
||||||
|
## Releasing new versions
|
||||||
|
|
||||||
|
- Update version in `tapo/Cargo.toml`
|
||||||
|
- Update version in `tapo-py/pyproject.toml` (two places)
|
||||||
|
- Update CHANGELOG.md
|
||||||
|
- Commit
|
||||||
|
- Add tag
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag -a vX.X.X -m "vX.X.X"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Push
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push --follow-tags
|
||||||
|
```
|
||||||
|
|
||||||
|
- Create the [release][release].
|
||||||
|
|
||||||
|
[releases]: https://github.com/mihai-dinculescu/tapo/releases
|
||||||
13
agents/tapo/tapo-fork/Cargo.toml
Normal file
13
agents/tapo/tapo-fork/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[workspace]
|
||||||
|
resolver = "3"
|
||||||
|
|
||||||
|
members = ["tapo", "tapo-py"]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
|
chrono = { version = "0.4.34", default-features = false }
|
||||||
|
log = "0.4"
|
||||||
|
pyo3 = { version = "0.27" }
|
||||||
|
serde = { version = "1.0" }
|
||||||
|
serde_json = { version = "1.0" }
|
||||||
|
tokio = { version = "1.48", default-features = false }
|
||||||
21
agents/tapo/tapo-fork/LICENSE
Normal file
21
agents/tapo/tapo-fork/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022-2025 Mihai Dinculescu
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
29
agents/tapo/tapo-fork/Makefile.toml
Normal file
29
agents/tapo/tapo-fork/Makefile.toml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[env]
|
||||||
|
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||||
|
|
||||||
|
[config]
|
||||||
|
skip_core_tasks = true
|
||||||
|
|
||||||
|
[tasks.format]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["fmt", "--verbose", "--", "--check"]
|
||||||
|
|
||||||
|
[tasks.check]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["check", "--verbose"]
|
||||||
|
|
||||||
|
[tasks.check-doc]
|
||||||
|
env = { "RUSTDOCFLAGS" = "-D warnings" }
|
||||||
|
command = "cargo"
|
||||||
|
args = ["doc", "--no-deps"]
|
||||||
|
|
||||||
|
[tasks.clippy]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["clippy", "--all-targets", "--all-features", "--verbose", "--", "-D", "warnings"]
|
||||||
|
|
||||||
|
[tasks.test]
|
||||||
|
command = "cargo"
|
||||||
|
args = ["test", "--verbose"]
|
||||||
|
|
||||||
|
[tasks.ci-flow]
|
||||||
|
dependencies = ["format", "check", "check-doc", "clippy", "test"]
|
||||||
111
agents/tapo/tapo-fork/README.md
Normal file
111
agents/tapo/tapo-fork/README.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Tapo
|
||||||
|
|
||||||
|
|
||||||
|
[![License][license_badge]][license]
|
||||||
|
[![Crates][crates_badge]][crates]
|
||||||
|
[![Documentation][crates_documentation_badge]][crates_documentation]
|
||||||
|
[![Crates.io][crates_downloads_badge]][crates]
|
||||||
|
[![PyPI][pypi_badge]][pypi]
|
||||||
|
[![Python][pypi_versions_badge]][pypi]
|
||||||
|
[![PyPI][pypi_downloads_badge]][pypi]\
|
||||||
|
Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L535, L610, L630), light strips (L900, L920, L930), plugs (P100, P105, P110, P110M, P115), power strips (P300, P304M, P306, P316M), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315).
|
||||||
|
|
||||||
|
[license_badge]: https://img.shields.io/crates/l/tapo.svg
|
||||||
|
[license]: https://github.com/mihai-dinculescu/tapo/blob/main/LICENSE
|
||||||
|
[crates_badge]: https://img.shields.io/crates/v/tapo.svg?logo=rust&color=F75101
|
||||||
|
[crates]: https://crates.io/crates/tapo
|
||||||
|
[crates_documentation_badge]: https://img.shields.io/docsrs/tapo.svg?logo=rust&color=F75101
|
||||||
|
[crates_documentation]: https://docs.rs/tapo
|
||||||
|
[crates_downloads_badge]: https://img.shields.io/crates/d/tapo?logo=rust&label=downloads&color=F75101
|
||||||
|
|
||||||
|
[pypi_badge]: https://img.shields.io/pypi/v/tapo.svg?logo=pypi&color=00ADD4
|
||||||
|
[pypi]: https://pypi.org/project/tapo
|
||||||
|
[pypi_versions_badge]: https://img.shields.io/pypi/pyversions/tapo.svg?logo=python&color=00ADD4
|
||||||
|
[pypi_downloads_badge]: https://img.shields.io/pypi/dm/tapo?logo=python&color=00ADD4
|
||||||
|
|
||||||
|
## Supported Devices
|
||||||
|
|
||||||
|
See [/SUPPORTED_DEVICES.md][supported_devices] for the supported devices and feature matrix.
|
||||||
|
|
||||||
|
## Rust
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
> Cargo.toml
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
tapo = "0.8"
|
||||||
|
```
|
||||||
|
|
||||||
|
> main.rs
|
||||||
|
```rust
|
||||||
|
let device = ApiClient::new("<tapo-username>", "tapo-password")
|
||||||
|
.p110("<device ip address>")
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
device.on().await?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export TAPO_USERNAME=
|
||||||
|
export TAPO_PASSWORD=
|
||||||
|
export IP_ADDRESS=
|
||||||
|
|
||||||
|
cargo run --example tapo_l530
|
||||||
|
```
|
||||||
|
|
||||||
|
See all examples in [/tapo/examples][examples].
|
||||||
|
|
||||||
|
### Wrapper REST API
|
||||||
|
[tapo-rest][tapo_rest] is a REST wrapper of this library that can be deployed as a service or serve as an advanced example.
|
||||||
|
|
||||||
|
## Python
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install tapo
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
client = ApiClient("<tapo-username>", "tapo-password")
|
||||||
|
device = await client.p110("<device ip address>")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tapo-py
|
||||||
|
poetry install # On the initial run
|
||||||
|
poetry shell
|
||||||
|
maturin develop # On the initial run and whenever the Rust code is modified
|
||||||
|
|
||||||
|
export TAPO_USERNAME=
|
||||||
|
export TAPO_PASSWORD=
|
||||||
|
export IP_ADDRESS=
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/tapo_p110.py
|
||||||
|
```
|
||||||
|
|
||||||
|
See all examples in [/tapo-py/examples][examples-py].
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome and encouraged! See [/CONTRIBUTING.md][contributing].
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Inspired by [petretiandrea/plugp100][inspired_by].
|
||||||
|
|
||||||
|
[supported_devices]: https://github.com/mihai-dinculescu/tapo/blob/main/SUPPORTED_DEVICES.md
|
||||||
|
[examples]: https://github.com/mihai-dinculescu/tapo/tree/main/tapo/examples
|
||||||
|
[examples-py]: https://github.com/mihai-dinculescu/tapo/tree/main/tapo-py/examples
|
||||||
|
[tapo_rest]: https://github.com/ClementNerma/tapo-rest
|
||||||
|
[contributing]: https://github.com/mihai-dinculescu/tapo/blob/main/CONTRIBUTING.md
|
||||||
|
[inspired_by]: https://github.com/petretiandrea/plugp100
|
||||||
74
agents/tapo/tapo-fork/SUPPORTED_DEVICES.md
Normal file
74
agents/tapo/tapo-fork/SUPPORTED_DEVICES.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
|
||||||
|
# Supported devices
|
||||||
|
|
||||||
|
✓ - Rust only\
|
||||||
|
✅ - Rust and Python
|
||||||
|
|
||||||
|
| Feature<br/><br/><br/> | GenericDevice<br/><br/><br/> | L510<br/>L520<br/>L610<br/> | L530<br/>L535<br/>L630<br/> | L900<br/><br/><br/> | L920<br/>L930<br/><br/> | P100<br/>P105<br/><br/> | P110<br/>P110M<br/>P115<br/> | P300<br/>P306<br/><br/> | P304M<br/>P316M<br/><br/> | H100<br/><br/><br/> |
|
||||||
|
| ------------------------------------ | :--------------------------- | :-------------------------- | :-------------------------- | :------------------ | :---------------------- | :---------------------- | :--------------------------- | :---------------------- | :------------------------ | :------------------ |
|
||||||
|
| device_reboot | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| device_reset | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| get_child_device_component_list_json | | | | | | | | ✅ | ✅ | ✅ |
|
||||||
|
| get_child_device_list | | | | | | | | ✅ | ✅ | ✅ |
|
||||||
|
| get_child_device_list_json | | | | | | | | ✅ | ✅ | ✅ |
|
||||||
|
| get_current_power | | | | | | | ✅ | | | |
|
||||||
|
| get_device_info | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| get_device_info_json | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| get_device_usage | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | |
|
||||||
|
| get_energy_data | | | | | | | ✅ | | | |
|
||||||
|
| get_energy_usage | | | | | | | ✅ | | | |
|
||||||
|
| get_power_data | | | | | | | ✅ | | | |
|
||||||
|
| get_supported_ringtone_list | | | | | | | | | | ✅ |
|
||||||
|
| off | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | |
|
||||||
|
| on | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | |
|
||||||
|
| play_alarm | | | | | | | | | | ✅ |
|
||||||
|
| refresh_session | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| set_brightness | | ✅ | ✅ | ✅ | ✅ | | | | | |
|
||||||
|
| set_color | | | ✅ | ✅ | ✅ | | | | | |
|
||||||
|
| set_color_temperature | | | ✅ | ✅ | ✅ | | | | | |
|
||||||
|
| set_hue_saturation | | | ✅ | ✅ | ✅ | | | | | |
|
||||||
|
| set_lighting_effect | | | | | ✅ | | | | | |
|
||||||
|
| set() API \* | | | ✅ | ✅ | ✅ | | | | | |
|
||||||
|
| stop_alarm | | | | | | | | | | ✅ |
|
||||||
|
|
||||||
|
|
||||||
|
\* The `set()` API allows multiple properties to be set in a single request.
|
||||||
|
|
||||||
|
## Hub (H100) Child Devices
|
||||||
|
|
||||||
|
✓ - Rust only\
|
||||||
|
✅ - Rust and Python
|
||||||
|
|
||||||
|
| Feature<br/><br/> | KE100<br/><br/> | S200B<br/><br/> | T100<br/><br/> | T110<br/><br/> | T300<br/><br/> | T310<br/>T315 |
|
||||||
|
| -------------------------------- | :-------------- | :-------------- | :------------- | :------------- | :------------- | :------------ |
|
||||||
|
| get_device_info \* | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| get_device_info_json | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| get_temperature_humidity_records | | | | | | ✅ |
|
||||||
|
| get_trigger_logs | | ✅ | ✅ | ✅ | ✅ | |
|
||||||
|
| set_child_protection | ✅ | | | | | |
|
||||||
|
| set_frost_protection | ✅ | | | | | |
|
||||||
|
| set_max_control_temperature | ✅ | | | | | |
|
||||||
|
| set_min_control_temperature | ✅ | | | | | |
|
||||||
|
| set_target_temperature | ✅ | | | | | |
|
||||||
|
| set_temperature_offset | ✅ | | | | | |
|
||||||
|
|
||||||
|
\* Obtained by calling `get_child_device_list` on the hub device or `get_device_info` on a child device handler.
|
||||||
|
|
||||||
|
## Power Strips Child Devices
|
||||||
|
|
||||||
|
✓ - Rust only\
|
||||||
|
✅ - Rust and Python
|
||||||
|
|
||||||
|
| Feature<br/><br/> | P300<br/>P306<br/> | P304M<br/>P316M<br/> |
|
||||||
|
| -------------------- | :----------------- | :------------------- |
|
||||||
|
| get_current_power | | ✅ |
|
||||||
|
| get_device_info \* | ✅ | ✅ |
|
||||||
|
| get_device_info_json | ✅ | ✅ |
|
||||||
|
| get_device_usage | | ✅ |
|
||||||
|
| get_energy_data | | ✅ |
|
||||||
|
| get_energy_usage | | ✅ |
|
||||||
|
| get_power_data | | ✅ |
|
||||||
|
| off | ✅ | ✅ |
|
||||||
|
| on | ✅ | ✅ |
|
||||||
|
|
||||||
|
\* Obtained by calling `get_child_device_list` on the hub device or `get_device_info` on a child device handler.
|
||||||
2
agents/tapo/tapo-fork/rustfmt.toml
Normal file
2
agents/tapo/tapo-fork/rustfmt.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
max_width = 100
|
||||||
|
style_edition = "2024"
|
||||||
2
agents/tapo/tapo-fork/taplo.toml
Normal file
2
agents/tapo/tapo-fork/taplo.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[formatting]
|
||||||
|
column_width = 100
|
||||||
4
agents/tapo/tapo-fork/tapo-py/.gitignore
vendored
Normal file
4
agents/tapo/tapo-fork/tapo-py/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
__pycache__
|
||||||
|
.pytest_cache
|
||||||
|
**/*.so
|
||||||
|
**/*.pyd
|
||||||
1
agents/tapo/tapo-fork/tapo-py/CHANGELOG.md
Symbolic link
1
agents/tapo/tapo-fork/tapo-py/CHANGELOG.md
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../CHANGELOG.md
|
||||||
32
agents/tapo/tapo-fork/tapo-py/Cargo.toml
Normal file
32
agents/tapo/tapo-fork/tapo-py/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
[package]
|
||||||
|
name = "tapo-py"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
rust-version = "1.88"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "tapo"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
chrono = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
|
pyo3 = { workspace = true, features = [
|
||||||
|
"chrono",
|
||||||
|
"experimental-async",
|
||||||
|
"extension-module",
|
||||||
|
"py-clone",
|
||||||
|
] }
|
||||||
|
pyo3-async-runtimes = { version = "0.27", features = ["attributes", "tokio-runtime"] }
|
||||||
|
pyo3-log = { version = "0.13" }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
tokio = { workspace = true, features = ["rt-multi-thread", "sync"] }
|
||||||
|
|
||||||
|
tapo = { path = "../tapo", features = ["python"] }
|
||||||
1
agents/tapo/tapo-fork/tapo-py/LICENSE
Symbolic link
1
agents/tapo/tapo-fork/tapo-py/LICENSE
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../LICENSE
|
||||||
1
agents/tapo/tapo-fork/tapo-py/README.md
Symbolic link
1
agents/tapo/tapo-fork/tapo-py/README.md
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../README.md
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
"""Discover devices on the local network Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient, DiscoveryResult
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
target = os.getenv("TARGET", "192.168.1.255")
|
||||||
|
timeout_s = int(os.getenv("TIMEOUT", 10))
|
||||||
|
|
||||||
|
print(f"Discovering Tapo devices on target: {target} for {timeout_s} seconds...")
|
||||||
|
|
||||||
|
api_client = ApiClient(tapo_username, tapo_password)
|
||||||
|
discovery = await api_client.discover_devices(target, timeout_s)
|
||||||
|
|
||||||
|
async for discovery_result in discovery:
|
||||||
|
try:
|
||||||
|
device = discovery_result.get()
|
||||||
|
|
||||||
|
match device:
|
||||||
|
case DiscoveryResult.GenericDevice(device_info, _handler):
|
||||||
|
print(
|
||||||
|
f"Found Unsupported Device '{device_info.nickname}' of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
case DiscoveryResult.Light(device_info, _handler):
|
||||||
|
print(
|
||||||
|
f"Found '{device_info.nickname}' of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
case DiscoveryResult.ColorLight(device_info, _handler):
|
||||||
|
print(
|
||||||
|
f"Found '{device_info.nickname}' of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
case DiscoveryResult.RgbLightStrip(device_info, _handler):
|
||||||
|
print(
|
||||||
|
f"Found '{device_info.nickname}' of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
case DiscoveryResult.RgbicLightStrip(device_info, _handler):
|
||||||
|
print(
|
||||||
|
f"Found '{device_info.nickname}' of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
case DiscoveryResult.Plug(device_info, _handler):
|
||||||
|
print(
|
||||||
|
f"Found '{device_info.nickname}' of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
case DiscoveryResult.PlugEnergyMonitoring(device_info, _handler):
|
||||||
|
print(
|
||||||
|
f"Found '{device_info.nickname}' of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
case DiscoveryResult.PowerStrip(device_info, _handler):
|
||||||
|
print(
|
||||||
|
f"Found Power Strip of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
case DiscoveryResult.PowerStripEnergyMonitoring(device_info, _handler):
|
||||||
|
print(
|
||||||
|
f"Found Power Strip with Energy Monitoring of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
case DiscoveryResult.Hub(device_info, _handler):
|
||||||
|
print(
|
||||||
|
f"Found '{device_info.nickname}' of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error discovering device: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"""Generic Device Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
device = await client.generic_device(ip_address)
|
||||||
|
|
||||||
|
print("Turning device on...")
|
||||||
|
await device.on()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Turning device off...")
|
||||||
|
await device.off()
|
||||||
|
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"""Toggle Generic Device Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
device = await client.generic_device(ip_address)
|
||||||
|
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
|
||||||
|
if device_info.device_on == True:
|
||||||
|
print("Device is on. Turning it off...")
|
||||||
|
await device.off()
|
||||||
|
elif device_info.device_on == False:
|
||||||
|
print("Device is off. Turning it on...")
|
||||||
|
await device.on()
|
||||||
|
else:
|
||||||
|
print("This device does not support on/off functionality.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
120
agents/tapo/tapo-fork/tapo-py/examples/tapo_h100.py
Normal file
120
agents/tapo/tapo-fork/tapo-py/examples/tapo_h100.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
"""H100 Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
from tapo.requests import AlarmRingtone, AlarmVolume, AlarmDuration
|
||||||
|
from tapo.responses import KE100Result, S200BResult, T100Result, T110Result, T300Result, T31XResult
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
hub = await client.h100(ip_address)
|
||||||
|
|
||||||
|
device_info = await hub.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
child_device_list = await hub.get_child_device_list()
|
||||||
|
|
||||||
|
for child in child_device_list:
|
||||||
|
if child is None:
|
||||||
|
print("Found unsupported device.")
|
||||||
|
elif isinstance(child, KE100Result):
|
||||||
|
print(
|
||||||
|
"Found KE100 child device with nickname: {}, id: {}, current temperature: {:.2f} {} and target temperature: {:.2f} {}.".format(
|
||||||
|
child.nickname,
|
||||||
|
child.device_id,
|
||||||
|
child.current_temperature,
|
||||||
|
child.temperature_unit,
|
||||||
|
child.target_temperature,
|
||||||
|
child.temperature_unit,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif isinstance(child, S200BResult):
|
||||||
|
s200b = await hub.s200b(device_id=child.device_id)
|
||||||
|
trigger_logs = await s200b.get_trigger_logs(5, 0)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Found S200B child device with nickname: {}, id: {}, last 5 trigger logs: {}.".format(
|
||||||
|
child.nickname,
|
||||||
|
child.device_id,
|
||||||
|
[log.to_dict() for log in trigger_logs.logs],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif isinstance(child, T100Result):
|
||||||
|
t100 = await hub.t100(device_id=child.device_id)
|
||||||
|
trigger_logs = await t100.get_trigger_logs(5, 0)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Found T100 child device with nickname: {}, id: {}, detected: {}, last 5 trigger logs: {}.".format(
|
||||||
|
child.nickname,
|
||||||
|
child.device_id,
|
||||||
|
child.detected,
|
||||||
|
[log.to_dict() for log in trigger_logs.logs],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif isinstance(child, T110Result):
|
||||||
|
t110 = await hub.t110(device_id=child.device_id)
|
||||||
|
trigger_logs = await t110.get_trigger_logs(5, 0)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Found T110 child device with nickname: {}, id: {}, open: {}, last 5 trigger logs: {}.".format(
|
||||||
|
child.nickname,
|
||||||
|
child.device_id,
|
||||||
|
child.open,
|
||||||
|
[log.to_dict() for log in trigger_logs.logs],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif isinstance(child, T300Result):
|
||||||
|
t300 = await hub.t300(device_id=child.device_id)
|
||||||
|
trigger_logs = await t300.get_trigger_logs(5, 0)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Found T300 child device with nickname: {}, id: {}, in_alarm: {}, water_leak_status: {}, last 5 trigger logs: {}.".format(
|
||||||
|
child.nickname,
|
||||||
|
child.device_id,
|
||||||
|
child.in_alarm,
|
||||||
|
child.water_leak_status,
|
||||||
|
[log.to_dict() for log in trigger_logs.logs],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif isinstance(child, T31XResult):
|
||||||
|
t31x = await hub.t315(device_id=child.device_id)
|
||||||
|
temperature_humidity_records = await t31x.get_temperature_humidity_records()
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Found T31X child device with nickname: {}, id: {}, temperature: {:.2f} {}, humidity: {}%, earliest temperature and humidity record available: {}.".format(
|
||||||
|
child.nickname,
|
||||||
|
child.device_id,
|
||||||
|
child.current_temperature,
|
||||||
|
child.temperature_unit,
|
||||||
|
child.current_humidity,
|
||||||
|
(
|
||||||
|
temperature_humidity_records.records[0].to_dict()
|
||||||
|
if temperature_humidity_records.records
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Triggering the alarm ringtone 'Alarm 1' at a 'Low' volume for '3 Seconds'...")
|
||||||
|
await hub.play_alarm(AlarmRingtone.Alarm1, AlarmVolume.Low, AlarmDuration.Seconds, seconds=3)
|
||||||
|
|
||||||
|
device_info = await hub.get_device_info()
|
||||||
|
print(f"Is device ringing?: {device_info.in_alarm}")
|
||||||
|
|
||||||
|
print("Stopping the alarm after 1 Second...")
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
await hub.stop_alarm()
|
||||||
|
|
||||||
|
device_info = await hub.get_device_info()
|
||||||
|
print(f"Is device ringing?: {device_info.in_alarm}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
40
agents/tapo/tapo-fork/tapo-py/examples/tapo_ke100.py
Normal file
40
agents/tapo/tapo-fork/tapo-py/examples/tapo_ke100.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"""KE100 TRV Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
from tapo.requests import TemperatureUnitKE100
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
# Name of the KE100 device.
|
||||||
|
# Can be obtained from the Tapo App or by executing `get_child_device_component_list()` on the hub device.
|
||||||
|
device_name = os.getenv("DEVICE_NAME")
|
||||||
|
target_temperature = int(os.getenv("TARGET_TEMPERATURE"))
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
hub = await client.h100(ip_address)
|
||||||
|
|
||||||
|
# Get a handler for the child device
|
||||||
|
device = await hub.ke100(nickname=device_name)
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
# Set target temperature.
|
||||||
|
# KE100 currently only supports Celsius as temperature unit.
|
||||||
|
print(f"Setting target temperature to {target_temperature} degrees Celsius...")
|
||||||
|
await device.set_target_temperature(target_temperature, TemperatureUnitKE100.Celsius)
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
40
agents/tapo/tapo-fork/tapo-py/examples/tapo_l510.py
Normal file
40
agents/tapo/tapo-fork/tapo-py/examples/tapo_l510.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"""L510, L520 and L610 Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
device = await client.l510(ip_address)
|
||||||
|
|
||||||
|
print("Turning device on...")
|
||||||
|
await device.on()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting the brightness to 30%...")
|
||||||
|
await device.set_brightness(30)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Turning device off...")
|
||||||
|
await device.off()
|
||||||
|
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
device_usage = await device.get_device_usage()
|
||||||
|
print(f"Device usage: {device_usage.to_dict()}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
62
agents/tapo/tapo-fork/tapo-py/examples/tapo_l530.py
Normal file
62
agents/tapo/tapo-fork/tapo-py/examples/tapo_l530.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
"""L530, L535 and L630 Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
from tapo.requests import Color
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
device = await client.l530(ip_address)
|
||||||
|
|
||||||
|
print("Turning device on...")
|
||||||
|
await device.on()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting the brightness to 30%...")
|
||||||
|
await device.set_brightness(30)
|
||||||
|
|
||||||
|
print("Setting the color to `Chocolate`...")
|
||||||
|
await device.set_color(Color.Chocolate)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting the color to `Deep Sky Blue` using the `hue` and `saturation`...")
|
||||||
|
await device.set_hue_saturation(195, 100)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting the color to `Incandescent` using the `color temperature`...")
|
||||||
|
await device.set_color_temperature(2700)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Using the `set` API to set multiple properties in a single request...")
|
||||||
|
await device.set().brightness(50).color(Color.HotPink).send(device)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Turning device off...")
|
||||||
|
await device.off()
|
||||||
|
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
device_usage = await device.get_device_usage()
|
||||||
|
print(f"Device usage: {device_usage.to_dict()}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
62
agents/tapo/tapo-fork/tapo-py/examples/tapo_l900.py
Normal file
62
agents/tapo/tapo-fork/tapo-py/examples/tapo_l900.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
"""L900 Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
from tapo.requests import Color
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
device = await client.l900(ip_address)
|
||||||
|
|
||||||
|
print("Turning device on...")
|
||||||
|
await device.on()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting the brightness to 30%...")
|
||||||
|
await device.set_brightness(30)
|
||||||
|
|
||||||
|
print("Setting the color to `Chocolate`...")
|
||||||
|
await device.set_color(Color.Chocolate)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting the color to `Deep Sky Blue` using the `hue` and `saturation`...")
|
||||||
|
await device.set_hue_saturation(195, 100)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting the color to `Incandescent` using the `color temperature`...")
|
||||||
|
await device.set_color_temperature(2700)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Using the `set` API to set multiple properties in a single request...")
|
||||||
|
await device.set().brightness(50).color(Color.HotPink).send(device)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Turning device off...")
|
||||||
|
await device.off()
|
||||||
|
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
device_usage = await device.get_device_usage()
|
||||||
|
print(f"Device usage: {device_usage.to_dict()}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
103
agents/tapo/tapo-fork/tapo-py/examples/tapo_l930.py
Normal file
103
agents/tapo/tapo-fork/tapo-py/examples/tapo_l930.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"""L920 and L930 Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
from tapo.requests import Color, LightingEffect, LightingEffectPreset, LightingEffectType
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
device = await client.l930(ip_address)
|
||||||
|
|
||||||
|
print("Turning device on...")
|
||||||
|
await device.on()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting the brightness to 30%...")
|
||||||
|
await device.set_brightness(30)
|
||||||
|
|
||||||
|
print("Setting the color to `Chocolate`...")
|
||||||
|
await device.set_color(Color.Chocolate)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting the color to `Deep Sky Blue` using the `hue` and `saturation`...")
|
||||||
|
await device.set_hue_saturation(195, 100)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting the color to `Incandescent` using the `color temperature`...")
|
||||||
|
await device.set_color_temperature(2700)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Using the `set` API to set multiple properties in a single request...")
|
||||||
|
await device.set().brightness(50).color(Color.HotPink).send(device)
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Setting a preset Lighting effect...")
|
||||||
|
await device.set_lighting_effect(LightingEffectPreset.BubblingCauldron)
|
||||||
|
|
||||||
|
print("Waiting 10 seconds...")
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
|
||||||
|
print("Setting a custom static Lighting effect...")
|
||||||
|
custom_effect = (
|
||||||
|
LightingEffect(
|
||||||
|
"My Custom Static Effect", LightingEffectType.Static, True, True, 100, [(359, 85, 100)]
|
||||||
|
)
|
||||||
|
.with_expansion_strategy(1)
|
||||||
|
.with_segments([0, 1, 2])
|
||||||
|
.with_sequence([(359, 85, 100), (0, 0, 100), (236, 72, 100)])
|
||||||
|
)
|
||||||
|
await device.set_lighting_effect(custom_effect)
|
||||||
|
|
||||||
|
print("Waiting 10 seconds...")
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
|
||||||
|
print("Setting a custom sequence Lighting effect...")
|
||||||
|
custom_effect = (
|
||||||
|
LightingEffect(
|
||||||
|
"My Custom Sequence Effect",
|
||||||
|
LightingEffectType.Sequence,
|
||||||
|
True,
|
||||||
|
True,
|
||||||
|
100,
|
||||||
|
[(359, 85, 100)],
|
||||||
|
)
|
||||||
|
.with_expansion_strategy(1)
|
||||||
|
.with_segments([0, 1, 2])
|
||||||
|
.with_sequence([(359, 85, 100), (0, 0, 100), (236, 72, 100)])
|
||||||
|
.with_direction(1)
|
||||||
|
.with_duration(50)
|
||||||
|
)
|
||||||
|
await device.set_lighting_effect(custom_effect)
|
||||||
|
|
||||||
|
print("Waiting 10 seconds...")
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
|
||||||
|
print("Turning device off...")
|
||||||
|
await device.off()
|
||||||
|
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
device_usage = await device.get_device_usage()
|
||||||
|
print(f"Device usage: {device_usage.to_dict()}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
34
agents/tapo/tapo-fork/tapo-py/examples/tapo_p100.py
Normal file
34
agents/tapo/tapo-fork/tapo-py/examples/tapo_p100.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"""P100 and P105 Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
device = await client.p100(ip_address)
|
||||||
|
|
||||||
|
print("Turning device on...")
|
||||||
|
await device.on()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Turning device off...")
|
||||||
|
await device.off()
|
||||||
|
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
device_usage = await device.get_device_usage()
|
||||||
|
print(f"Device usage: {device_usage.to_dict()}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
118
agents/tapo/tapo-fork/tapo-py/examples/tapo_p110.py
Normal file
118
agents/tapo/tapo-fork/tapo-py/examples/tapo_p110.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
"""P110, P110M and P115 Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
from tapo.requests import EnergyDataInterval, PowerDataInterval
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
device = await client.p110(ip_address)
|
||||||
|
|
||||||
|
print("Turning device on...")
|
||||||
|
await device.on()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Turning device off...")
|
||||||
|
await device.off()
|
||||||
|
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
current_power = await device.get_current_power()
|
||||||
|
print(f"Current power: {current_power.to_dict()}")
|
||||||
|
|
||||||
|
device_usage = await device.get_device_usage()
|
||||||
|
print(f"Device usage: {device_usage.to_dict()}")
|
||||||
|
|
||||||
|
energy_usage = await device.get_energy_usage()
|
||||||
|
print(f"Energy usage: {energy_usage.to_dict()}")
|
||||||
|
|
||||||
|
today = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
# Energy data - Hourly interval
|
||||||
|
# `start_date` and `end_date` are an inclusive interval that must not be greater than 8 days.
|
||||||
|
energy_data_hourly = await device.get_energy_data(EnergyDataInterval.Hourly, today)
|
||||||
|
print(
|
||||||
|
"Energy data (hourly): "
|
||||||
|
f"Start date time '{energy_data_hourly.start_date_time}', "
|
||||||
|
f"Entries {len(energy_data_hourly.entries)}, "
|
||||||
|
f"First entry: {energy_data_hourly.entries[0].to_dict() if energy_data_hourly.entries else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Energy data - Daily interval
|
||||||
|
# `start_date` must be the first day of a quarter.
|
||||||
|
energy_data_daily = await device.get_energy_data(
|
||||||
|
EnergyDataInterval.Daily,
|
||||||
|
datetime(today.year, get_quarter_start_month(today), 1),
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Energy data (daily): "
|
||||||
|
f"Start date time '{energy_data_daily.start_date_time}', "
|
||||||
|
f"Entries {len(energy_data_daily.entries)}, "
|
||||||
|
f"First entry: {energy_data_daily.entries[0].to_dict() if energy_data_daily.entries else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Energy data - Monthly interval
|
||||||
|
# `start_date` must be the first day of a year.
|
||||||
|
energy_data_monthly = await device.get_energy_data(
|
||||||
|
EnergyDataInterval.Monthly,
|
||||||
|
datetime(today.year, 1, 1),
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Energy data (monthly): "
|
||||||
|
f"Start date time '{energy_data_monthly.start_date_time}', "
|
||||||
|
f"Entries {len(energy_data_monthly.entries)}, "
|
||||||
|
f"First entry: {energy_data_monthly.entries[0].to_dict() if energy_data_monthly.entries else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Power data - Every 5 minutes interval
|
||||||
|
# `start_date_time` and `end_date_time` describe an exclusive interval.
|
||||||
|
# If the result would yield more than 144 entries (i.e. 12 hours),
|
||||||
|
# the `end_date_time` will be adjusted to an earlier date and time.
|
||||||
|
power_data_every_5_minutes = await device.get_power_data(
|
||||||
|
PowerDataInterval.Every5Minutes,
|
||||||
|
today - timedelta(hours=12),
|
||||||
|
today,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Power data (every 5 minutes): "
|
||||||
|
f"Start date time '{power_data_every_5_minutes.start_date_time}', "
|
||||||
|
f"End date time '{power_data_every_5_minutes.end_date_time}', "
|
||||||
|
f"Entries {len(power_data_every_5_minutes.entries)}, "
|
||||||
|
f"First entry: {power_data_every_5_minutes.entries[0].to_dict() if power_data_every_5_minutes.entries else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Power data - Hourly interval
|
||||||
|
# `start_date_time` and `end_date_time` describe an exclusive interval.
|
||||||
|
# If the result would yield more than 144 entries (i.e. 6 days),
|
||||||
|
# the `end_date_time` will be adjusted to an earlier date and time.
|
||||||
|
power_data_hourly = await device.get_power_data(
|
||||||
|
PowerDataInterval.Hourly,
|
||||||
|
today - timedelta(days=3),
|
||||||
|
today,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Power data (hourly): "
|
||||||
|
f"Start date time '{power_data_hourly.start_date_time}', "
|
||||||
|
f"End date time '{power_data_hourly.end_date_time}', "
|
||||||
|
f"Entries {len(power_data_hourly.entries)}, "
|
||||||
|
f"First entry: {power_data_hourly.entries[0].to_dict() if power_data_hourly.entries else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_quarter_start_month(today: datetime) -> int:
|
||||||
|
return ((today.month - 1) // 3) * 3 + 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
45
agents/tapo/tapo-fork/tapo-py/examples/tapo_p300.py
Normal file
45
agents/tapo/tapo-fork/tapo-py/examples/tapo_p300.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"""P300 and P306 Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
power_strip = await client.p300(ip_address)
|
||||||
|
|
||||||
|
device_info = await power_strip.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
print("Getting child devices...")
|
||||||
|
child_device_list = await power_strip.get_child_device_list()
|
||||||
|
print(f"Found {len(child_device_list)} plugs")
|
||||||
|
|
||||||
|
for index, child in enumerate(child_device_list):
|
||||||
|
print(f"=== ({index + 1}) {child.nickname} ===")
|
||||||
|
print(f"Device ID: {child.device_id}")
|
||||||
|
print(f"State: {child.device_on}")
|
||||||
|
|
||||||
|
plug = await power_strip.plug(device_id=child.device_id)
|
||||||
|
|
||||||
|
print("Turning device on...")
|
||||||
|
await plug.on()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Turning device off...")
|
||||||
|
await plug.off()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
132
agents/tapo/tapo-fork/tapo-py/examples/tapo_p304.py
Normal file
132
agents/tapo/tapo-fork/tapo-py/examples/tapo_p304.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
"""P304M and P316M Example"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tapo import ApiClient
|
||||||
|
from tapo.requests import EnergyDataInterval, PowerDataInterval
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
tapo_username = os.getenv("TAPO_USERNAME")
|
||||||
|
tapo_password = os.getenv("TAPO_PASSWORD")
|
||||||
|
ip_address = os.getenv("IP_ADDRESS")
|
||||||
|
|
||||||
|
client = ApiClient(tapo_username, tapo_password)
|
||||||
|
power_strip = await client.p304(ip_address)
|
||||||
|
|
||||||
|
device_info = await power_strip.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
|
||||||
|
print("Getting child devices...")
|
||||||
|
child_device_list = await power_strip.get_child_device_list()
|
||||||
|
print(f"Found {len(child_device_list)} plugs")
|
||||||
|
|
||||||
|
for index, child in enumerate(child_device_list):
|
||||||
|
print(f"=== ({index + 1}) {child.nickname} ===")
|
||||||
|
print(f"Device ID: {child.device_id}")
|
||||||
|
print(f"State: {child.device_on}")
|
||||||
|
|
||||||
|
plug = await power_strip.plug(device_id=child.device_id)
|
||||||
|
|
||||||
|
print("Turning device on...")
|
||||||
|
await plug.on()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
print("Turning device off...")
|
||||||
|
await plug.off()
|
||||||
|
|
||||||
|
print("Waiting 2 seconds...")
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
current_power = await plug.get_current_power()
|
||||||
|
print(f"Current power: {current_power.to_dict()}")
|
||||||
|
|
||||||
|
device_usage = await plug.get_device_usage()
|
||||||
|
print(f"Device usage: {device_usage.to_dict()}")
|
||||||
|
|
||||||
|
energy_usage = await plug.get_energy_usage()
|
||||||
|
print(f"Energy usage: {energy_usage.to_dict()}")
|
||||||
|
|
||||||
|
today = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
# Energy data - Hourly interval
|
||||||
|
# `start_date` and `end_date` are an inclusive interval that must not be greater than 8 days.
|
||||||
|
energy_data_hourly = await plug.get_energy_data(EnergyDataInterval.Hourly, today)
|
||||||
|
print(
|
||||||
|
"Energy data (hourly): "
|
||||||
|
f"Start date time '{energy_data_hourly.start_date_time}', "
|
||||||
|
f"Entries {len(energy_data_hourly.entries)}, "
|
||||||
|
f"First entry: {energy_data_hourly.entries[0].to_dict() if energy_data_hourly.entries else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Energy data - Daily interval
|
||||||
|
# `start_date` must be the first day of a quarter.
|
||||||
|
energy_data_daily = await plug.get_energy_data(
|
||||||
|
EnergyDataInterval.Daily,
|
||||||
|
datetime(today.year, get_quarter_start_month(today), 1),
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Energy data (daily): "
|
||||||
|
f"Start date time '{energy_data_daily.start_date_time}', "
|
||||||
|
f"Entries {len(energy_data_daily.entries)}, "
|
||||||
|
f"First entry: {energy_data_daily.entries[0].to_dict() if energy_data_daily.entries else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Energy data - Monthly interval
|
||||||
|
# `start_date` must be the first day of a year.
|
||||||
|
energy_data_monthly = await plug.get_energy_data(
|
||||||
|
EnergyDataInterval.Monthly,
|
||||||
|
datetime(today.year, 1, 1),
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Energy data (monthly): "
|
||||||
|
f"Start date time '{energy_data_monthly.start_date_time}', "
|
||||||
|
f"Entries {len(energy_data_monthly.entries)}, "
|
||||||
|
f"First entry: {energy_data_monthly.entries[0].to_dict() if energy_data_monthly.entries else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Power data - Every 5 minutes interval
|
||||||
|
# `start_date_time` and `end_date_time` describe an exclusive interval.
|
||||||
|
# If the result would yield more than 144 entries (i.e. 12 hours),
|
||||||
|
# the `end_date_time` will be adjusted to an earlier date and time.
|
||||||
|
power_data_every_5_minutes = await plug.get_power_data(
|
||||||
|
PowerDataInterval.Every5Minutes,
|
||||||
|
today - timedelta(hours=12),
|
||||||
|
today,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Power data (every 5 minutes): "
|
||||||
|
f"Start date time '{power_data_every_5_minutes.start_date_time}', "
|
||||||
|
f"End date time '{power_data_every_5_minutes.end_date_time}', "
|
||||||
|
f"Entries {len(power_data_every_5_minutes.entries)}, "
|
||||||
|
f"First entry: {power_data_every_5_minutes.entries[0].to_dict() if power_data_every_5_minutes.entries else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Power data - Hourly interval
|
||||||
|
# `start_date_time` and `end_date_time` describe an exclusive interval.
|
||||||
|
# If the result would yield more than 144 entries (i.e. 6 days),
|
||||||
|
# the `end_date_time` will be adjusted to an earlier date and time.
|
||||||
|
power_data_hourly = await plug.get_power_data(
|
||||||
|
PowerDataInterval.Hourly,
|
||||||
|
today - timedelta(days=3),
|
||||||
|
today,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Power data (hourly): "
|
||||||
|
f"Start date time '{power_data_hourly.start_date_time}', "
|
||||||
|
f"End date time '{power_data_hourly.end_date_time}', "
|
||||||
|
f"Entries {len(power_data_hourly.entries)}, "
|
||||||
|
f"First entry: {power_data_hourly.entries[0].to_dict() if power_data_hourly.entries else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_quarter_start_month(today: datetime) -> int:
|
||||||
|
return ((today.month - 1) // 3) * 3 + 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
180
agents/tapo/tapo-fork/tapo-py/poetry.lock
generated
Normal file
180
agents/tapo/tapo-fork/tapo-py/poetry.lock
generated
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "black"
|
||||||
|
version = "25.11.0"
|
||||||
|
description = "The uncompromising code formatter."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e"},
|
||||||
|
{file = "black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0"},
|
||||||
|
{file = "black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37"},
|
||||||
|
{file = "black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03"},
|
||||||
|
{file = "black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a"},
|
||||||
|
{file = "black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170"},
|
||||||
|
{file = "black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc"},
|
||||||
|
{file = "black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e"},
|
||||||
|
{file = "black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac"},
|
||||||
|
{file = "black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96"},
|
||||||
|
{file = "black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd"},
|
||||||
|
{file = "black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409"},
|
||||||
|
{file = "black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b"},
|
||||||
|
{file = "black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd"},
|
||||||
|
{file = "black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993"},
|
||||||
|
{file = "black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c"},
|
||||||
|
{file = "black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170"},
|
||||||
|
{file = "black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545"},
|
||||||
|
{file = "black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda"},
|
||||||
|
{file = "black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664"},
|
||||||
|
{file = "black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06"},
|
||||||
|
{file = "black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2"},
|
||||||
|
{file = "black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc"},
|
||||||
|
{file = "black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc"},
|
||||||
|
{file = "black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b"},
|
||||||
|
{file = "black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
click = ">=8.0.0"
|
||||||
|
mypy-extensions = ">=0.4.3"
|
||||||
|
packaging = ">=22.0"
|
||||||
|
pathspec = ">=0.9.0"
|
||||||
|
platformdirs = ">=2"
|
||||||
|
pytokens = ">=0.3.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
colorama = ["colorama (>=0.4.3)"]
|
||||||
|
d = ["aiohttp (>=3.10)"]
|
||||||
|
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||||
|
uvloop = ["uvloop (>=0.15.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.3.1"
|
||||||
|
description = "Composable command line interface toolkit"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"},
|
||||||
|
{file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
groups = ["dev"]
|
||||||
|
markers = "platform_system == \"Windows\""
|
||||||
|
files = [
|
||||||
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maturin"
|
||||||
|
version = "1.10.2"
|
||||||
|
description = "Build and publish crates with pyo3, cffi and uniffi bindings as well as rust binaries as python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "maturin-1.10.2-py3-none-linux_armv6l.whl", hash = "sha256:11c73815f21a755d2129c410e6cb19dbfacbc0155bfc46c706b69930c2eb794b"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7fbd997c5347649ee7987bd05a92bd5b8b07efa4ac3f8bcbf6196e07eb573d89"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3ce9b2ad4fb9c341f450a6d32dc3edb409a2d582a81bc46ba55f6e3b6196b22"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:f0d1b7b5f73c8d30a7e71cd2a2189a7f0126a3a3cd8b3d6843e7e1d4db50f759"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:efcd496a3202ffe0d0489df1f83d08b91399782fb2dd545d5a1e7bf6fd81af39"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a41ec70d99e27c05377be90f8e3c3def2a7bae4d0d9d5ea874aaf2d1da625d5c"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:07a82864352feeaf2167247c8206937ef6c6ae9533025d416b7004ade0ea601d"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:04df81ee295dcda37828bd025a4ac688ea856e3946e4cb300a8f44a448de0069"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96e1d391e4c1fa87edf2a37e4d53d5f2e5f39dd880b9d8306ac9f8eb212d23f8"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a217aa7c42aa332fb8e8377eb07314e1f02cf0fe036f614aca4575121952addd"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-win32.whl", hash = "sha256:da031771d9fb6ddb1d373638ec2556feee29e4507365cd5749a2d354bcadd818"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-win_amd64.whl", hash = "sha256:da777766fd584440dc9fecd30059a94f85e4983f58b09e438ae38ee4b494024c"},
|
||||||
|
{file = "maturin-1.10.2-py3-none-win_arm64.whl", hash = "sha256:a4c29a770ea2c76082e0afc6d4efd8ee94405588bfae00d10828f72e206c739b"},
|
||||||
|
{file = "maturin-1.10.2.tar.gz", hash = "sha256:259292563da89850bf8f7d37aa4ddba22905214c1e180b1c8f55505dfd8c0e81"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
patchelf = ["patchelf"]
|
||||||
|
zig = ["ziglang (>=0.10.0,<0.13.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-extensions"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "Type system extensions for programs checked with the mypy type checker."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
|
||||||
|
{file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "25.0"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
|
||||||
|
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathspec"
|
||||||
|
version = "0.12.1"
|
||||||
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||||
|
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platformdirs"
|
||||||
|
version = "4.5.0"
|
||||||
|
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"},
|
||||||
|
{file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"]
|
||||||
|
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"]
|
||||||
|
type = ["mypy (>=1.18.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytokens"
|
||||||
|
version = "0.3.0"
|
||||||
|
description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3"},
|
||||||
|
{file = "pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "2.1"
|
||||||
|
python-versions = "^3.11"
|
||||||
|
content-hash = "fdca2529430378024bd7592d1cc9a036dce731d069014afa9422296fe281758a"
|
||||||
0
agents/tapo/tapo-fork/tapo-py/py.typed
Normal file
0
agents/tapo/tapo-fork/tapo-py/py.typed
Normal file
55
agents/tapo/tapo-fork/tapo-py/pyproject.toml
Normal file
55
agents/tapo/tapo-fork/tapo-py/pyproject.toml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "tapo"
|
||||||
|
version = "0.8.8"
|
||||||
|
description = "Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L535, L610, L630), light strips (L900, L920, L930), plugs (P100, P105, P110, P110M, P115), power strips (P300, P304M, P306, P316M), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315)."
|
||||||
|
authors = ["Mihai Dinculescu <mihai.dinculescu@outlook.com>"]
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "tapo"
|
||||||
|
version = "0.8.8"
|
||||||
|
description = "Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L535, L610, L630), light strips (L900, L920, L930), plugs (P100, P105, P110, P110M, P115), power strips (P300, P304M, P306, P316M), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315)."
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
|
authors = [{ name = "Mihai Dinculescu", email = "mihai.dinculescu@outlook.com" }]
|
||||||
|
maintainers = [{ name = "Mihai Dinculescu", email = "mihai.dinculescu@outlook.com" }]
|
||||||
|
keywords = ["Tapo", "TP-Link", "Smart Home", "Home Automation", "IoT"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Topic :: Software Development :: Embedded Systems",
|
||||||
|
"Environment :: Console",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Topic :: Home Automation",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Programming Language :: Python :: 3.14",
|
||||||
|
]
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Changelog = 'https://github.com/mihai-dinculescu/tapo/blob/main/CHANGELOG.md'
|
||||||
|
Funding = 'https://github.com/mihai-dinculescu'
|
||||||
|
Homepage = 'https://github.com/mihai-dinculescu/tapo'
|
||||||
|
Source = 'https://github.com/mihai-dinculescu/tapo'
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.11"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
maturin = ">=1.0,<2.0"
|
||||||
|
black = ">=25.0,<26.0"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["maturin>=1.0,<2.0"]
|
||||||
|
build-backend = "maturin"
|
||||||
|
|
||||||
|
[tool.maturin]
|
||||||
|
python-source = "tapo-py"
|
||||||
|
bindings = 'pyo3'
|
||||||
|
features = ["pyo3/extension-module"]
|
||||||
|
include = ["README.md", "CHANGELOG.md", "LICENSE", "tapo-py/tapo-py/tapo/*"]
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 100
|
||||||
29
agents/tapo/tapo-fork/tapo-py/src/api.rs
Normal file
29
agents/tapo/tapo-fork/tapo-py/src/api.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
mod api_client;
|
||||||
|
mod child_devices;
|
||||||
|
mod color_light_handler;
|
||||||
|
mod discovery;
|
||||||
|
mod generic_device_handler;
|
||||||
|
mod hub_handler;
|
||||||
|
mod light_handler;
|
||||||
|
mod plug_energy_monitoring_handler;
|
||||||
|
mod plug_handler;
|
||||||
|
mod power_strip_energy_monitoring_handler;
|
||||||
|
mod power_strip_handler;
|
||||||
|
mod py_handler_ext;
|
||||||
|
mod rgb_light_strip_handler;
|
||||||
|
mod rgbic_light_strip_handler;
|
||||||
|
|
||||||
|
pub use api_client::*;
|
||||||
|
pub use child_devices::*;
|
||||||
|
pub use color_light_handler::*;
|
||||||
|
pub use discovery::*;
|
||||||
|
pub use generic_device_handler::*;
|
||||||
|
pub use hub_handler::*;
|
||||||
|
pub use light_handler::*;
|
||||||
|
pub use plug_energy_monitoring_handler::*;
|
||||||
|
pub use plug_handler::*;
|
||||||
|
pub use power_strip_energy_monitoring_handler::*;
|
||||||
|
pub use power_strip_handler::*;
|
||||||
|
pub use py_handler_ext::*;
|
||||||
|
pub use rgb_light_strip_handler::*;
|
||||||
|
pub use rgbic_light_strip_handler::*;
|
||||||
165
agents/tapo/tapo-fork/tapo-py/src/api/api_client.rs
Normal file
165
agents/tapo/tapo-fork/tapo-py/src/api/api_client.rs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use tapo::{
|
||||||
|
ApiClient, ColorLightHandler, DeviceDiscovery, GenericDeviceHandler, HubHandler, LightHandler,
|
||||||
|
PlugEnergyMonitoringHandler, PlugHandler, PowerStripEnergyMonitoringHandler, PowerStripHandler,
|
||||||
|
RgbLightStripHandler, RgbicLightStripHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::call_handler_constructor;
|
||||||
|
use crate::errors::ErrorWrapper;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
PyColorLightHandler, PyDeviceDiscovery, PyGenericDeviceHandler, PyHubHandler, PyLightHandler,
|
||||||
|
PyPlugEnergyMonitoringHandler, PyPlugHandler, PyPowerStripEnergyMonitoringHandler,
|
||||||
|
PyPowerStripHandler, PyRgbLightStripHandler, PyRgbicLightStripHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[pyclass(name = "ApiClient")]
|
||||||
|
pub struct PyApiClient {
|
||||||
|
client: ApiClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyApiClient {
|
||||||
|
#[new]
|
||||||
|
#[pyo3(signature = (tapo_username, tapo_password, timeout_s=None))]
|
||||||
|
pub fn new(
|
||||||
|
tapo_username: String,
|
||||||
|
tapo_password: String,
|
||||||
|
timeout_s: Option<u64>,
|
||||||
|
) -> Result<Self, ErrorWrapper> {
|
||||||
|
let client = match timeout_s {
|
||||||
|
Some(timeout_s) => ApiClient::new(tapo_username, tapo_password)
|
||||||
|
.with_timeout(Duration::from_secs(timeout_s)),
|
||||||
|
None => ApiClient::new(tapo_username, tapo_password),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self { client })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn discover_devices(
|
||||||
|
&self,
|
||||||
|
target: String,
|
||||||
|
timeout_s: u64,
|
||||||
|
) -> Result<PyDeviceDiscovery, ErrorWrapper> {
|
||||||
|
let discovery: DeviceDiscovery =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::discover_devices, target, timeout_s);
|
||||||
|
Ok(PyDeviceDiscovery::new(discovery))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn generic_device(&self, ip_address: String) -> PyResult<PyGenericDeviceHandler> {
|
||||||
|
let handler: GenericDeviceHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::generic_device, ip_address);
|
||||||
|
Ok(PyGenericDeviceHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn l510(&self, ip_address: String) -> PyResult<PyLightHandler> {
|
||||||
|
let handler: LightHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::l510, ip_address);
|
||||||
|
Ok(PyLightHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn l520(&self, ip_address: String) -> PyResult<PyLightHandler> {
|
||||||
|
let handler: LightHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::l520, ip_address);
|
||||||
|
Ok(PyLightHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn l530(&self, ip_address: String) -> PyResult<PyColorLightHandler> {
|
||||||
|
let handler: ColorLightHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::l530, ip_address);
|
||||||
|
Ok(PyColorLightHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn l535(&self, ip_address: String) -> PyResult<PyColorLightHandler> {
|
||||||
|
let handler: ColorLightHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::l535, ip_address);
|
||||||
|
Ok(PyColorLightHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn l610(&self, ip_address: String) -> PyResult<PyLightHandler> {
|
||||||
|
let handler: LightHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::l610, ip_address);
|
||||||
|
Ok(PyLightHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn l630(&self, ip_address: String) -> PyResult<PyColorLightHandler> {
|
||||||
|
let handler: ColorLightHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::l630, ip_address);
|
||||||
|
Ok(PyColorLightHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn l900(&self, ip_address: String) -> PyResult<PyRgbLightStripHandler> {
|
||||||
|
let handler: RgbLightStripHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::l900, ip_address);
|
||||||
|
Ok(PyRgbLightStripHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn l920(&self, ip_address: String) -> PyResult<PyRgbicLightStripHandler> {
|
||||||
|
let handler: RgbicLightStripHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::l920, ip_address);
|
||||||
|
Ok(PyRgbicLightStripHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn l930(&self, ip_address: String) -> PyResult<PyRgbicLightStripHandler> {
|
||||||
|
let handler: RgbicLightStripHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::l930, ip_address);
|
||||||
|
Ok(PyRgbicLightStripHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn p100(&self, ip_address: String) -> PyResult<PyPlugHandler> {
|
||||||
|
let handler: PlugHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::p100, ip_address);
|
||||||
|
Ok(PyPlugHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn p105(&self, ip_address: String) -> PyResult<PyPlugHandler> {
|
||||||
|
let handler: PlugHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::p105, ip_address);
|
||||||
|
Ok(PyPlugHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn p110(&self, ip_address: String) -> PyResult<PyPlugEnergyMonitoringHandler> {
|
||||||
|
let handler: PlugEnergyMonitoringHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::p110, ip_address);
|
||||||
|
Ok(PyPlugEnergyMonitoringHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn p115(&self, ip_address: String) -> PyResult<PyPlugEnergyMonitoringHandler> {
|
||||||
|
let handler: PlugEnergyMonitoringHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::p115, ip_address);
|
||||||
|
Ok(PyPlugEnergyMonitoringHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn p300(&self, ip_address: String) -> PyResult<PyPowerStripHandler> {
|
||||||
|
let handler: PowerStripHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::p300, ip_address);
|
||||||
|
Ok(PyPowerStripHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn p304(&self, ip_address: String) -> PyResult<PyPowerStripEnergyMonitoringHandler> {
|
||||||
|
let handler: PowerStripEnergyMonitoringHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::p304, ip_address);
|
||||||
|
Ok(PyPowerStripEnergyMonitoringHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn p306(&self, ip_address: String) -> PyResult<PyPowerStripHandler> {
|
||||||
|
let handler: PowerStripHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::p306, ip_address);
|
||||||
|
Ok(PyPowerStripHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn p316(&self, ip_address: String) -> PyResult<PyPowerStripEnergyMonitoringHandler> {
|
||||||
|
let handler: PowerStripEnergyMonitoringHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::p316, ip_address);
|
||||||
|
Ok(PyPowerStripEnergyMonitoringHandler::new(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn h100(&self, ip_address: String) -> PyResult<PyHubHandler> {
|
||||||
|
let handler: HubHandler =
|
||||||
|
call_handler_constructor!(self, tapo::ApiClient::h100, ip_address);
|
||||||
|
Ok(PyHubHandler::new(handler))
|
||||||
|
}
|
||||||
|
}
|
||||||
17
agents/tapo/tapo-fork/tapo-py/src/api/child_devices.rs
Normal file
17
agents/tapo/tapo-fork/tapo-py/src/api/child_devices.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
mod ke100_handler;
|
||||||
|
mod power_strip_plug_energy_monitoring_handler;
|
||||||
|
mod power_strip_plug_handler;
|
||||||
|
mod s200b_handler;
|
||||||
|
mod t100_handler;
|
||||||
|
mod t110_handler;
|
||||||
|
mod t300_handler;
|
||||||
|
mod t31x_handler;
|
||||||
|
|
||||||
|
pub use ke100_handler::*;
|
||||||
|
pub use power_strip_plug_energy_monitoring_handler::*;
|
||||||
|
pub use power_strip_plug_handler::*;
|
||||||
|
pub use s200b_handler::*;
|
||||||
|
pub use t31x_handler::*;
|
||||||
|
pub use t100_handler::*;
|
||||||
|
pub use t110_handler::*;
|
||||||
|
pub use t300_handler::*;
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use pyo3::{prelude::*, types::PyDict};
|
||||||
|
use tapo::KE100Handler;
|
||||||
|
use tapo::responses::{KE100Result, TemperatureUnitKE100};
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "KE100Handler")]
|
||||||
|
pub struct PyKE100Handler {
|
||||||
|
inner: Arc<KE100Handler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyKE100Handler {
|
||||||
|
pub fn new(handler: KE100Handler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyKE100Handler {
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<KE100Result> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), KE100Handler::get_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(handler.deref(), KE100Handler::get_device_info_json)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_child_protection(&self, on: bool) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), KE100Handler::set_child_protection, on)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_frost_protection(&self, on: bool) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), KE100Handler::set_frost_protection, on)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_max_control_temperature(
|
||||||
|
&self,
|
||||||
|
value: u8,
|
||||||
|
unit: TemperatureUnitKE100,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
KE100Handler::set_max_control_temperature,
|
||||||
|
value,
|
||||||
|
unit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_min_control_temperature(
|
||||||
|
&self,
|
||||||
|
value: u8,
|
||||||
|
unit: TemperatureUnitKE100,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
KE100Handler::set_min_control_temperature,
|
||||||
|
value,
|
||||||
|
unit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_target_temperature(
|
||||||
|
&self,
|
||||||
|
value: u8,
|
||||||
|
unit: TemperatureUnitKE100,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
KE100Handler::set_target_temperature,
|
||||||
|
value,
|
||||||
|
unit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_temperature_offset(
|
||||||
|
&self,
|
||||||
|
value: i8,
|
||||||
|
unit: TemperatureUnitKE100,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
KE100Handler::set_temperature_offset,
|
||||||
|
value,
|
||||||
|
unit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use chrono::{DateTime, NaiveDate, Utc};
|
||||||
|
use pyo3::{prelude::*, types::PyDict};
|
||||||
|
use tapo::PowerStripPlugEnergyMonitoringHandler;
|
||||||
|
use tapo::requests::{EnergyDataInterval, PowerDataInterval};
|
||||||
|
use tapo::responses::{
|
||||||
|
CurrentPowerResult, DeviceUsageEnergyMonitoringResult, EnergyDataResult, EnergyUsageResult,
|
||||||
|
PowerDataResult, PowerStripPlugEnergyMonitoringResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::requests::{PyEnergyDataInterval, PyPowerDataInterval};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "PowerStripPlugEnergyMonitoringHandler")]
|
||||||
|
pub struct PyPowerStripPlugEnergyMonitoringHandler {
|
||||||
|
inner: Arc<PowerStripPlugEnergyMonitoringHandler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyPowerStripPlugEnergyMonitoringHandler {
|
||||||
|
pub fn new(handler: PowerStripPlugEnergyMonitoringHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyPowerStripPlugEnergyMonitoringHandler {
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<PowerStripPlugEnergyMonitoringResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
PowerStripPlugEnergyMonitoringHandler::get_device_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
PowerStripPlugEnergyMonitoringHandler::get_device_info_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), PowerStripPlugEnergyMonitoringHandler::on)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn off(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), PowerStripPlugEnergyMonitoringHandler::off)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_current_power(&self) -> PyResult<CurrentPowerResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
PowerStripPlugEnergyMonitoringHandler::get_current_power,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
PowerStripPlugEnergyMonitoringHandler::get_device_usage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_energy_usage(&self) -> PyResult<EnergyUsageResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
PowerStripPlugEnergyMonitoringHandler::get_energy_usage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (interval, start_date, end_date=None))]
|
||||||
|
pub async fn get_energy_data(
|
||||||
|
&self,
|
||||||
|
interval: PyEnergyDataInterval,
|
||||||
|
start_date: NaiveDate,
|
||||||
|
end_date: Option<NaiveDate>,
|
||||||
|
) -> PyResult<EnergyDataResult> {
|
||||||
|
let interval = match interval {
|
||||||
|
PyEnergyDataInterval::Hourly => EnergyDataInterval::Hourly {
|
||||||
|
start_date,
|
||||||
|
end_date: end_date.unwrap_or(start_date),
|
||||||
|
},
|
||||||
|
PyEnergyDataInterval::Daily => EnergyDataInterval::Daily { start_date },
|
||||||
|
PyEnergyDataInterval::Monthly => EnergyDataInterval::Monthly { start_date },
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
PowerStripPlugEnergyMonitoringHandler::get_energy_data,
|
||||||
|
interval
|
||||||
|
)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_power_data(
|
||||||
|
&self,
|
||||||
|
interval: PyPowerDataInterval,
|
||||||
|
start_date_time: DateTime<Utc>,
|
||||||
|
end_date_time: DateTime<Utc>,
|
||||||
|
) -> PyResult<PowerDataResult> {
|
||||||
|
let interval = match interval {
|
||||||
|
PyPowerDataInterval::Every5Minutes => PowerDataInterval::Every5Minutes {
|
||||||
|
start_date_time,
|
||||||
|
end_date_time,
|
||||||
|
},
|
||||||
|
PyPowerDataInterval::Hourly => PowerDataInterval::Hourly {
|
||||||
|
start_date_time,
|
||||||
|
end_date_time,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
PowerStripPlugEnergyMonitoringHandler::get_power_data,
|
||||||
|
interval
|
||||||
|
)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use pyo3::{prelude::*, types::PyDict};
|
||||||
|
use tapo::PowerStripPlugHandler;
|
||||||
|
use tapo::responses::PowerStripPlugResult;
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "PowerStripPlugHandler")]
|
||||||
|
pub struct PyPowerStripPlugHandler {
|
||||||
|
inner: Arc<PowerStripPlugHandler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyPowerStripPlugHandler {
|
||||||
|
pub fn new(handler: PowerStripPlugHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyPowerStripPlugHandler {
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<PowerStripPlugResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), PowerStripPlugHandler::get_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result =
|
||||||
|
call_handler_method!(handler.deref(), PowerStripPlugHandler::get_device_info_json)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), PowerStripPlugHandler::on)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn off(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), PowerStripPlugHandler::off)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use pyo3::{prelude::*, types::PyDict};
|
||||||
|
use tapo::S200BHandler;
|
||||||
|
use tapo::responses::S200BResult;
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::responses::TriggerLogsS200BResult;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "S200BHandler")]
|
||||||
|
pub struct PyS200BHandler {
|
||||||
|
inner: Arc<S200BHandler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyS200BHandler {
|
||||||
|
pub fn new(handler: S200BHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyS200BHandler {
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<S200BResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), S200BHandler::get_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(handler.deref(), S200BHandler::get_device_info_json)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_trigger_logs(
|
||||||
|
&self,
|
||||||
|
page_size: u64,
|
||||||
|
start_id: u64,
|
||||||
|
) -> PyResult<TriggerLogsS200BResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
S200BHandler::get_trigger_logs,
|
||||||
|
page_size,
|
||||||
|
start_id
|
||||||
|
)
|
||||||
|
.map(|result| result.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use pyo3::{prelude::*, types::PyDict};
|
||||||
|
use tapo::{T100Handler, responses::T100Result};
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::responses::TriggerLogsT100Result;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "T100Handler")]
|
||||||
|
pub struct PyT100Handler {
|
||||||
|
inner: Arc<T100Handler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyT100Handler {
|
||||||
|
pub fn new(handler: T100Handler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyT100Handler {
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<T100Result> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), T100Handler::get_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(handler.deref(), T100Handler::get_device_info_json)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_trigger_logs(
|
||||||
|
&self,
|
||||||
|
page_size: u64,
|
||||||
|
start_id: u64,
|
||||||
|
) -> PyResult<TriggerLogsT100Result> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
T100Handler::get_trigger_logs,
|
||||||
|
page_size,
|
||||||
|
start_id
|
||||||
|
)
|
||||||
|
.map(|result| result.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use pyo3::{prelude::*, types::PyDict};
|
||||||
|
use tapo::T110Handler;
|
||||||
|
use tapo::responses::T110Result;
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::responses::TriggerLogsT110Result;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "T110Handler")]
|
||||||
|
pub struct PyT110Handler {
|
||||||
|
inner: Arc<T110Handler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyT110Handler {
|
||||||
|
pub fn new(handler: T110Handler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyT110Handler {
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<T110Result> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), T110Handler::get_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(handler.deref(), T110Handler::get_device_info_json)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_trigger_logs(
|
||||||
|
&self,
|
||||||
|
page_size: u64,
|
||||||
|
start_id: u64,
|
||||||
|
) -> PyResult<TriggerLogsT110Result> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
T110Handler::get_trigger_logs,
|
||||||
|
page_size,
|
||||||
|
start_id
|
||||||
|
)
|
||||||
|
.map(|result| result.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use pyo3::{prelude::*, types::PyDict};
|
||||||
|
use tapo::T300Handler;
|
||||||
|
use tapo::responses::T300Result;
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::responses::TriggerLogsT300Result;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "T300Handler")]
|
||||||
|
pub struct PyT300Handler {
|
||||||
|
inner: Arc<T300Handler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyT300Handler {
|
||||||
|
pub fn new(handler: T300Handler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyT300Handler {
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<T300Result> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), T300Handler::get_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(handler.deref(), T300Handler::get_device_info_json)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_trigger_logs(
|
||||||
|
&self,
|
||||||
|
page_size: u64,
|
||||||
|
start_id: u64,
|
||||||
|
) -> PyResult<TriggerLogsT300Result> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
T300Handler::get_trigger_logs,
|
||||||
|
page_size,
|
||||||
|
start_id
|
||||||
|
)
|
||||||
|
.map(|result| result.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use pyo3::{prelude::*, types::PyDict};
|
||||||
|
use tapo::T31XHandler;
|
||||||
|
use tapo::responses::{T31XResult, TemperatureHumidityRecords};
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "T31XHandler")]
|
||||||
|
pub struct PyT31XHandler {
|
||||||
|
inner: Arc<T31XHandler>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyT31XHandler {
|
||||||
|
pub fn new(handler: T31XHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyT31XHandler {
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<T31XResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.deref(), T31XHandler::get_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(handler.deref(), T31XHandler::get_device_info_json)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_temperature_humidity_records(&self) -> PyResult<TemperatureHumidityRecords> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.deref(),
|
||||||
|
T31XHandler::get_temperature_humidity_records
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
138
agents/tapo/tapo-fork/tapo-py/src/api/color_light_handler.rs
Normal file
138
agents/tapo/tapo-fork/tapo-py/src/api/color_light_handler.rs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
use tapo::requests::Color;
|
||||||
|
use tapo::responses::{DeviceInfoColorLightResult, DeviceUsageEnergyMonitoringResult};
|
||||||
|
use tapo::{ColorLightHandler, DeviceManagementExt as _, HandlerExt};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::api::PyHandlerExt;
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::requests::PyColorLightSetDeviceInfoParams;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "ColorLightHandler")]
|
||||||
|
pub struct PyColorLightHandler {
|
||||||
|
inner: Arc<RwLock<ColorLightHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyColorLightHandler {
|
||||||
|
pub fn new(handler: ColorLightHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(handler)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyHandlerExt for PyColorLightHandler {
|
||||||
|
fn get_inner_handler(&self) -> Arc<RwLock<impl HandlerExt + 'static>> {
|
||||||
|
Arc::clone(&self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyColorLightHandler {
|
||||||
|
pub async fn refresh_session(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.write().await.deref_mut(),
|
||||||
|
ColorLightHandler::refresh_session,
|
||||||
|
discard_result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), ColorLightHandler::on)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn off(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), ColorLightHandler::off)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
ColorLightHandler::device_reboot,
|
||||||
|
delay_s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reset(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
ColorLightHandler::device_reset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<DeviceInfoColorLightResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
ColorLightHandler::get_device_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
ColorLightHandler::get_device_info_json,
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
ColorLightHandler::get_device_usage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self) -> PyColorLightSetDeviceInfoParams {
|
||||||
|
PyColorLightSetDeviceInfoParams::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_brightness(&self, brightness: u8) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
ColorLightHandler::set_brightness,
|
||||||
|
brightness
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_color(&self, color: Color) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
ColorLightHandler::set_color,
|
||||||
|
color
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_hue_saturation(&self, hue: u16, saturation: u8) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
ColorLightHandler::set_hue_saturation,
|
||||||
|
hue,
|
||||||
|
saturation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_color_temperature(&self, color_temperature: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
ColorLightHandler::set_color_temperature,
|
||||||
|
color_temperature
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
5
agents/tapo/tapo-fork/tapo-py/src/api/discovery.rs
Normal file
5
agents/tapo/tapo-fork/tapo-py/src/api/discovery.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod device_discovery;
|
||||||
|
mod discovery_result;
|
||||||
|
|
||||||
|
pub use device_discovery::*;
|
||||||
|
pub use discovery_result::*;
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use tapo::{DeviceDiscovery, StreamExt as _};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use super::{PyMaybeDiscoveryResult, convert_result_to_maybe_py};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "DeviceDiscovery")]
|
||||||
|
pub struct PyDeviceDiscovery {
|
||||||
|
pub inner: Arc<Mutex<DeviceDiscovery>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyDeviceDiscovery {
|
||||||
|
pub fn new(inner: DeviceDiscovery) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(Mutex::new(inner)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyDeviceDiscovery {
|
||||||
|
fn __iter__(slf: PyRef<'_, Self>) -> PyResult<PyDeviceDiscoveryIter> {
|
||||||
|
Ok(PyDeviceDiscoveryIter {
|
||||||
|
inner: (*slf).inner.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn __aiter__(slf: PyRef<'_, Self>) -> PyResult<PyDeviceDiscoveryIter> {
|
||||||
|
Ok(PyDeviceDiscoveryIter {
|
||||||
|
inner: (*slf).inner.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(name = "DeviceDiscoveryIter")]
|
||||||
|
pub struct PyDeviceDiscoveryIter {
|
||||||
|
pub inner: Arc<Mutex<DeviceDiscovery>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyDeviceDiscoveryIter {
|
||||||
|
fn __iter__(slf: Py<Self>) -> Py<Self> {
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __aiter__(slf: Py<Self>) -> Py<Self> {
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __next__(slf: PyRefMut<'_, Self>) -> PyResult<Option<PyMaybeDiscoveryResult>> {
|
||||||
|
let inner = (*slf).inner.clone();
|
||||||
|
|
||||||
|
let result = Python::attach(|py| {
|
||||||
|
py.detach(|| {
|
||||||
|
crate::runtime::tokio().block_on(async {
|
||||||
|
let mut guard = inner.lock().await;
|
||||||
|
guard.next().await
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(result) = result {
|
||||||
|
let result_maybe_py = convert_result_to_maybe_py(result)?;
|
||||||
|
Ok(Some(result_maybe_py))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __anext__<'py>(slf: PyRefMut<'_, Self>, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
||||||
|
let inner = (*slf).inner.clone();
|
||||||
|
|
||||||
|
pyo3_async_runtimes::tokio::future_into_py(py, async move {
|
||||||
|
let mut guard = inner.lock().await;
|
||||||
|
let result = guard.next().await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Some(result) => convert_result_to_maybe_py(result),
|
||||||
|
None => Err(PyErr::new::<pyo3::exceptions::PyStopAsyncIteration, _>(
|
||||||
|
"No more devices found",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
use tapo::responses::{
|
||||||
|
DeviceInfoColorLightResult, DeviceInfoGenericResult, DeviceInfoHubResult,
|
||||||
|
DeviceInfoLightResult, DeviceInfoPlugEnergyMonitoringResult, DeviceInfoPlugResult,
|
||||||
|
DeviceInfoPowerStripResult, DeviceInfoRgbLightStripResult, DeviceInfoRgbicLightStripResult,
|
||||||
|
};
|
||||||
|
use tapo::{DiscoveryResult, Error};
|
||||||
|
|
||||||
|
use crate::api::{
|
||||||
|
PyColorLightHandler, PyGenericDeviceHandler, PyHubHandler, PyLightHandler,
|
||||||
|
PyPlugEnergyMonitoringHandler, PyPlugHandler, PyPowerStripEnergyMonitoringHandler,
|
||||||
|
PyPowerStripHandler, PyRgbLightStripHandler, PyRgbicLightStripHandler,
|
||||||
|
};
|
||||||
|
use crate::errors::ErrorWrapper;
|
||||||
|
|
||||||
|
#[pyclass(name = "DiscoveryResult")]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
pub enum PyDiscoveryResult {
|
||||||
|
GenericDevice {
|
||||||
|
device_info: DeviceInfoGenericResult,
|
||||||
|
handler: PyGenericDeviceHandler,
|
||||||
|
},
|
||||||
|
Light {
|
||||||
|
device_info: DeviceInfoLightResult,
|
||||||
|
handler: PyLightHandler,
|
||||||
|
},
|
||||||
|
ColorLight {
|
||||||
|
device_info: DeviceInfoColorLightResult,
|
||||||
|
handler: PyColorLightHandler,
|
||||||
|
},
|
||||||
|
RgbLightStrip {
|
||||||
|
device_info: DeviceInfoRgbLightStripResult,
|
||||||
|
handler: PyRgbLightStripHandler,
|
||||||
|
},
|
||||||
|
RgbicLightStrip {
|
||||||
|
device_info: DeviceInfoRgbicLightStripResult,
|
||||||
|
handler: PyRgbicLightStripHandler,
|
||||||
|
},
|
||||||
|
Plug {
|
||||||
|
device_info: DeviceInfoPlugResult,
|
||||||
|
handler: PyPlugHandler,
|
||||||
|
},
|
||||||
|
PlugEnergyMonitoring {
|
||||||
|
device_info: DeviceInfoPlugEnergyMonitoringResult,
|
||||||
|
handler: PyPlugEnergyMonitoringHandler,
|
||||||
|
},
|
||||||
|
PowerStrip {
|
||||||
|
device_info: DeviceInfoPowerStripResult,
|
||||||
|
handler: PyPowerStripHandler,
|
||||||
|
},
|
||||||
|
PowerStripEnergyMonitoring {
|
||||||
|
device_info: DeviceInfoPowerStripResult,
|
||||||
|
handler: PyPowerStripEnergyMonitoringHandler,
|
||||||
|
},
|
||||||
|
Hub {
|
||||||
|
device_info: DeviceInfoHubResult,
|
||||||
|
handler: PyHubHandler,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(name = "MaybeDiscoveryResult")]
|
||||||
|
pub struct PyMaybeDiscoveryResult {
|
||||||
|
result: Option<PyDiscoveryResult>,
|
||||||
|
exception: Option<ErrorWrapper>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyMaybeDiscoveryResult {
|
||||||
|
pub fn get(mut slf: PyRefMut<'_, Self>) -> PyResult<PyDiscoveryResult> {
|
||||||
|
if let Some(result) = slf.result.take() {
|
||||||
|
Ok(result)
|
||||||
|
} else if let Some(exception) = slf.exception.take() {
|
||||||
|
Err(exception.into())
|
||||||
|
} else {
|
||||||
|
Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
|
||||||
|
"No result or exception available. `get` can only be called once.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_result_to_maybe_py(
|
||||||
|
result: Result<DiscoveryResult, Error>,
|
||||||
|
) -> PyResult<PyMaybeDiscoveryResult> {
|
||||||
|
match result {
|
||||||
|
Ok(result) => Ok(PyMaybeDiscoveryResult {
|
||||||
|
result: Some(convert_result_to_py(result)),
|
||||||
|
exception: None,
|
||||||
|
}),
|
||||||
|
Err(e) => Ok(PyMaybeDiscoveryResult {
|
||||||
|
result: None,
|
||||||
|
exception: Some(ErrorWrapper::from(e)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_result_to_py(result: DiscoveryResult) -> PyDiscoveryResult {
|
||||||
|
match result {
|
||||||
|
DiscoveryResult::GenericDevice {
|
||||||
|
device_info,
|
||||||
|
handler,
|
||||||
|
} => PyDiscoveryResult::GenericDevice {
|
||||||
|
device_info: *device_info,
|
||||||
|
handler: PyGenericDeviceHandler::new(handler),
|
||||||
|
},
|
||||||
|
DiscoveryResult::Light {
|
||||||
|
device_info,
|
||||||
|
handler,
|
||||||
|
} => PyDiscoveryResult::Light {
|
||||||
|
device_info: *device_info,
|
||||||
|
handler: PyLightHandler::new(handler),
|
||||||
|
},
|
||||||
|
DiscoveryResult::ColorLight {
|
||||||
|
device_info,
|
||||||
|
handler,
|
||||||
|
} => PyDiscoveryResult::ColorLight {
|
||||||
|
device_info: *device_info,
|
||||||
|
handler: PyColorLightHandler::new(handler),
|
||||||
|
},
|
||||||
|
DiscoveryResult::RgbLightStrip {
|
||||||
|
device_info,
|
||||||
|
handler,
|
||||||
|
} => PyDiscoveryResult::RgbLightStrip {
|
||||||
|
device_info: *device_info,
|
||||||
|
handler: PyRgbLightStripHandler::new(handler),
|
||||||
|
},
|
||||||
|
DiscoveryResult::RgbicLightStrip {
|
||||||
|
device_info,
|
||||||
|
handler,
|
||||||
|
} => PyDiscoveryResult::RgbicLightStrip {
|
||||||
|
device_info: *device_info,
|
||||||
|
handler: PyRgbicLightStripHandler::new(handler),
|
||||||
|
},
|
||||||
|
DiscoveryResult::Plug {
|
||||||
|
device_info,
|
||||||
|
handler,
|
||||||
|
} => PyDiscoveryResult::Plug {
|
||||||
|
device_info: *device_info,
|
||||||
|
handler: PyPlugHandler::new(handler),
|
||||||
|
},
|
||||||
|
DiscoveryResult::PlugEnergyMonitoring {
|
||||||
|
device_info,
|
||||||
|
handler,
|
||||||
|
} => PyDiscoveryResult::PlugEnergyMonitoring {
|
||||||
|
device_info: *device_info,
|
||||||
|
handler: PyPlugEnergyMonitoringHandler::new(handler),
|
||||||
|
},
|
||||||
|
DiscoveryResult::PowerStrip {
|
||||||
|
device_info,
|
||||||
|
handler,
|
||||||
|
} => PyDiscoveryResult::PowerStrip {
|
||||||
|
device_info: *device_info,
|
||||||
|
handler: PyPowerStripHandler::new(handler),
|
||||||
|
},
|
||||||
|
DiscoveryResult::PowerStripEnergyMonitoring {
|
||||||
|
device_info,
|
||||||
|
handler,
|
||||||
|
} => PyDiscoveryResult::PowerStripEnergyMonitoring {
|
||||||
|
device_info: *device_info,
|
||||||
|
handler: PyPowerStripEnergyMonitoringHandler::new(handler),
|
||||||
|
},
|
||||||
|
DiscoveryResult::Hub {
|
||||||
|
device_info,
|
||||||
|
handler,
|
||||||
|
} => PyDiscoveryResult::Hub {
|
||||||
|
device_info: *device_info,
|
||||||
|
handler: PyHubHandler::new(handler),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
use tapo::GenericDeviceHandler;
|
||||||
|
use tapo::responses::DeviceInfoGenericResult;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "GenericDeviceHandler")]
|
||||||
|
pub struct PyGenericDeviceHandler {
|
||||||
|
inner: Arc<RwLock<GenericDeviceHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyGenericDeviceHandler {
|
||||||
|
pub fn new(handler: GenericDeviceHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(handler)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyGenericDeviceHandler {
|
||||||
|
pub async fn refresh_session(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.write().await.deref_mut(),
|
||||||
|
GenericDeviceHandler::refresh_session,
|
||||||
|
discard_result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), GenericDeviceHandler::on)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn off(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), GenericDeviceHandler::off)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<DeviceInfoGenericResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
GenericDeviceHandler::get_device_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
GenericDeviceHandler::get_device_info_json,
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
}
|
||||||
291
agents/tapo/tapo-fork/tapo-py/src/api/hub_handler.rs
Normal file
291
agents/tapo/tapo-fork/tapo-py/src/api/hub_handler.rs
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::{PyDict, PyList};
|
||||||
|
use tapo::requests::{AlarmDuration, AlarmRingtone, AlarmVolume};
|
||||||
|
use tapo::responses::{ChildDeviceHubResult, DeviceInfoHubResult};
|
||||||
|
use tapo::{DeviceManagementExt as _, Error, HubDevice, HubHandler};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::api::{
|
||||||
|
PyKE100Handler, PyS200BHandler, PyT31XHandler, PyT100Handler, PyT110Handler, PyT300Handler,
|
||||||
|
};
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::errors::ErrorWrapper;
|
||||||
|
use crate::requests::PyAlarmDuration;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "HubHandler")]
|
||||||
|
pub struct PyHubHandler {
|
||||||
|
inner: Arc<RwLock<HubHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyHubHandler {
|
||||||
|
pub fn new(handler: HubHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(handler)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_identifier(
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
) -> PyResult<HubDevice> {
|
||||||
|
match (device_id, nickname) {
|
||||||
|
(Some(device_id), _) => Ok(HubDevice::ByDeviceId(device_id)),
|
||||||
|
(None, Some(nickname)) => Ok(HubDevice::ByNickname(nickname)),
|
||||||
|
_ => Err(Into::<ErrorWrapper>::into(Error::Validation {
|
||||||
|
field: "identifier".to_string(),
|
||||||
|
message: "Either a device_id or nickname must be provided".to_string(),
|
||||||
|
})
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyHubHandler {
|
||||||
|
pub async fn refresh_session(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.write().await.deref_mut(),
|
||||||
|
HubHandler::refresh_session,
|
||||||
|
discard_result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
HubHandler::device_reboot,
|
||||||
|
delay_s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reset(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), HubHandler::device_reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<DeviceInfoHubResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), HubHandler::get_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
HubHandler::get_device_info_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_child_device_list(&self) -> PyResult<Py<PyList>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let children = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
HubHandler::get_child_device_list
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Python::attach(|py| {
|
||||||
|
let results = PyList::empty(py);
|
||||||
|
|
||||||
|
for child in children {
|
||||||
|
match child {
|
||||||
|
ChildDeviceHubResult::KE100(device) => {
|
||||||
|
results.append(device.into_pyobject(py)?)?;
|
||||||
|
}
|
||||||
|
ChildDeviceHubResult::S200B(device) => {
|
||||||
|
results.append(device.into_pyobject(py)?)?;
|
||||||
|
}
|
||||||
|
ChildDeviceHubResult::T100(device) => {
|
||||||
|
results.append(device.into_pyobject(py)?)?;
|
||||||
|
}
|
||||||
|
ChildDeviceHubResult::T110(device) => {
|
||||||
|
results.append(device.into_pyobject(py)?)?;
|
||||||
|
}
|
||||||
|
ChildDeviceHubResult::T300(device) => {
|
||||||
|
results.append(device.into_pyobject(py)?)?;
|
||||||
|
}
|
||||||
|
ChildDeviceHubResult::T310(device) => {
|
||||||
|
results.append(device.into_pyobject(py)?)?;
|
||||||
|
}
|
||||||
|
ChildDeviceHubResult::T315(device) => {
|
||||||
|
results.append(device.into_pyobject(py)?)?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
results.append(py.None())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results.into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_child_device_list_json(&self, start_index: u64) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
HubHandler::get_child_device_list_json,
|
||||||
|
start_index
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_child_device_component_list_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
HubHandler::get_child_device_component_list_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_supported_ringtone_list(&self) -> PyResult<Vec<String>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
HubHandler::get_supported_ringtone_list
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (ringtone, volume, duration, seconds=None))]
|
||||||
|
pub async fn play_alarm(
|
||||||
|
&self,
|
||||||
|
ringtone: AlarmRingtone,
|
||||||
|
volume: AlarmVolume,
|
||||||
|
duration: PyAlarmDuration,
|
||||||
|
seconds: Option<u32>,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
|
||||||
|
let duration = match duration {
|
||||||
|
PyAlarmDuration::Continuous => AlarmDuration::Continuous,
|
||||||
|
PyAlarmDuration::Once => AlarmDuration::Once,
|
||||||
|
PyAlarmDuration::Seconds => {
|
||||||
|
if let Some(seconds) = seconds {
|
||||||
|
AlarmDuration::Seconds(seconds)
|
||||||
|
} else {
|
||||||
|
return Err(Into::<ErrorWrapper>::into(Error::Validation {
|
||||||
|
field: "seconds".to_string(),
|
||||||
|
message:
|
||||||
|
"A value must be provided for seconds when duration = AlarmDuration.Seconds"
|
||||||
|
.to_string(),
|
||||||
|
})
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
HubHandler::play_alarm,
|
||||||
|
ringtone,
|
||||||
|
volume,
|
||||||
|
duration
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn stop_alarm(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
|
||||||
|
call_handler_method!(handler.read().await.deref(), HubHandler::stop_alarm)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (device_id=None, nickname=None))]
|
||||||
|
pub async fn ke100(
|
||||||
|
&self,
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
) -> PyResult<PyKE100Handler> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
|
||||||
|
|
||||||
|
let child_handler =
|
||||||
|
call_handler_method!(handler.read().await.deref(), HubHandler::ke100, identifier)?;
|
||||||
|
Ok(PyKE100Handler::new(child_handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (device_id=None, nickname=None))]
|
||||||
|
pub async fn s200b(
|
||||||
|
&self,
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
) -> PyResult<PyS200BHandler> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
|
||||||
|
|
||||||
|
let child_handler =
|
||||||
|
call_handler_method!(handler.read().await.deref(), HubHandler::s200b, identifier)?;
|
||||||
|
Ok(PyS200BHandler::new(child_handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (device_id=None, nickname=None))]
|
||||||
|
pub async fn t100(
|
||||||
|
&self,
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
) -> PyResult<PyT100Handler> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
|
||||||
|
|
||||||
|
let child_handler =
|
||||||
|
call_handler_method!(handler.read().await.deref(), HubHandler::t100, identifier)?;
|
||||||
|
Ok(PyT100Handler::new(child_handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (device_id=None, nickname=None))]
|
||||||
|
pub async fn t110(
|
||||||
|
&self,
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
) -> PyResult<PyT110Handler> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
|
||||||
|
|
||||||
|
let child_handler =
|
||||||
|
call_handler_method!(handler.read().await.deref(), HubHandler::t110, identifier)?;
|
||||||
|
Ok(PyT110Handler::new(child_handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (device_id=None, nickname=None))]
|
||||||
|
pub async fn t300(
|
||||||
|
&self,
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
) -> PyResult<PyT300Handler> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
|
||||||
|
|
||||||
|
let child_handler =
|
||||||
|
call_handler_method!(handler.read().await.deref(), HubHandler::t300, identifier)?;
|
||||||
|
Ok(PyT300Handler::new(child_handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (device_id=None, nickname=None))]
|
||||||
|
pub async fn t310(
|
||||||
|
&self,
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
) -> PyResult<PyT31XHandler> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let identifier = PyHubHandler::parse_identifier(device_id, nickname)?;
|
||||||
|
|
||||||
|
let child_handler =
|
||||||
|
call_handler_method!(handler.read().await.deref(), HubHandler::t310, identifier)?;
|
||||||
|
Ok(PyT31XHandler::new(child_handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (device_id=None, nickname=None))]
|
||||||
|
pub async fn t315(
|
||||||
|
&self,
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
) -> PyResult<PyT31XHandler> {
|
||||||
|
self.t310(device_id, nickname).await
|
||||||
|
}
|
||||||
|
}
|
||||||
88
agents/tapo/tapo-fork/tapo-py/src/api/light_handler.rs
Normal file
88
agents/tapo/tapo-fork/tapo-py/src/api/light_handler.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
use tapo::responses::{DeviceInfoLightResult, DeviceUsageEnergyMonitoringResult};
|
||||||
|
use tapo::{DeviceManagementExt as _, LightHandler};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "LightHandler")]
|
||||||
|
pub struct PyLightHandler {
|
||||||
|
inner: Arc<RwLock<LightHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyLightHandler {
|
||||||
|
pub fn new(handler: LightHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(handler)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyLightHandler {
|
||||||
|
pub async fn refresh_session(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.write().await.deref_mut(),
|
||||||
|
LightHandler::refresh_session,
|
||||||
|
discard_result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), LightHandler::on)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn off(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), LightHandler::off)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
LightHandler::device_reboot,
|
||||||
|
delay_s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reset(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), LightHandler::device_reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<DeviceInfoLightResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), LightHandler::get_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
LightHandler::get_device_info_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), LightHandler::get_device_usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_brightness(&self, brightness: u8) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
LightHandler::set_brightness,
|
||||||
|
brightness
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use chrono::{DateTime, NaiveDate, Utc};
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
use tapo::requests::{EnergyDataInterval, PowerDataInterval};
|
||||||
|
use tapo::responses::{
|
||||||
|
CurrentPowerResult, DeviceInfoPlugEnergyMonitoringResult, DeviceUsageEnergyMonitoringResult,
|
||||||
|
EnergyDataResult, EnergyUsageResult, PowerDataResult,
|
||||||
|
};
|
||||||
|
use tapo::{DeviceManagementExt as _, PlugEnergyMonitoringHandler};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::requests::{PyEnergyDataInterval, PyPowerDataInterval};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "PlugEnergyMonitoringHandler")]
|
||||||
|
pub struct PyPlugEnergyMonitoringHandler {
|
||||||
|
inner: Arc<RwLock<PlugEnergyMonitoringHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyPlugEnergyMonitoringHandler {
|
||||||
|
pub fn new(handler: PlugEnergyMonitoringHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(handler)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyPlugEnergyMonitoringHandler {
|
||||||
|
pub async fn refresh_session(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.write().await.deref_mut(),
|
||||||
|
PlugEnergyMonitoringHandler::refresh_session,
|
||||||
|
discard_result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::on
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn off(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::off
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::device_reboot,
|
||||||
|
delay_s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reset(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::device_reset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<DeviceInfoPlugEnergyMonitoringResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::get_device_info,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::get_device_info_json,
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_current_power(&self) -> PyResult<CurrentPowerResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::get_current_power,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::get_device_usage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_energy_usage(&self) -> PyResult<EnergyUsageResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::get_energy_usage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (interval, start_date, end_date=None))]
|
||||||
|
pub async fn get_energy_data(
|
||||||
|
&self,
|
||||||
|
interval: PyEnergyDataInterval,
|
||||||
|
start_date: NaiveDate,
|
||||||
|
end_date: Option<NaiveDate>,
|
||||||
|
) -> PyResult<EnergyDataResult> {
|
||||||
|
let interval = match interval {
|
||||||
|
PyEnergyDataInterval::Hourly => EnergyDataInterval::Hourly {
|
||||||
|
start_date,
|
||||||
|
end_date: end_date.unwrap_or(start_date),
|
||||||
|
},
|
||||||
|
PyEnergyDataInterval::Daily => EnergyDataInterval::Daily { start_date },
|
||||||
|
PyEnergyDataInterval::Monthly => EnergyDataInterval::Monthly { start_date },
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::get_energy_data,
|
||||||
|
interval
|
||||||
|
)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_power_data(
|
||||||
|
&self,
|
||||||
|
interval: PyPowerDataInterval,
|
||||||
|
start_date_time: DateTime<Utc>,
|
||||||
|
end_date_time: DateTime<Utc>,
|
||||||
|
) -> PyResult<PowerDataResult> {
|
||||||
|
let interval = match interval {
|
||||||
|
PyPowerDataInterval::Every5Minutes => PowerDataInterval::Every5Minutes {
|
||||||
|
start_date_time,
|
||||||
|
end_date_time,
|
||||||
|
},
|
||||||
|
PyPowerDataInterval::Hourly => PowerDataInterval::Hourly {
|
||||||
|
start_date_time,
|
||||||
|
end_date_time,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugEnergyMonitoringHandler::get_power_data,
|
||||||
|
interval
|
||||||
|
)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
79
agents/tapo/tapo-fork/tapo-py/src/api/plug_handler.rs
Normal file
79
agents/tapo/tapo-fork/tapo-py/src/api/plug_handler.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
use tapo::responses::{DeviceInfoPlugResult, DeviceUsageResult};
|
||||||
|
use tapo::{DeviceManagementExt as _, PlugHandler};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::call_handler_method;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "PlugHandler")]
|
||||||
|
pub struct PyPlugHandler {
|
||||||
|
inner: Arc<RwLock<PlugHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyPlugHandler {
|
||||||
|
pub fn new(handler: PlugHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(handler)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyPlugHandler {
|
||||||
|
pub async fn refresh_session(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.write().await.deref_mut(),
|
||||||
|
PlugHandler::refresh_session,
|
||||||
|
discard_result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), PlugHandler::on)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn off(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), PlugHandler::off)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugHandler::device_reboot,
|
||||||
|
delay_s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reset(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), PlugHandler::device_reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<DeviceInfoPlugResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), PlugHandler::get_device_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PlugHandler::get_device_info_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), PlugHandler::get_device_usage)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::{PyDict, PyList};
|
||||||
|
use tapo::responses::DeviceInfoPowerStripResult;
|
||||||
|
use tapo::{DeviceManagementExt as _, Error, Plug, PowerStripEnergyMonitoringHandler};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::api::PyPowerStripPlugEnergyMonitoringHandler;
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::errors::ErrorWrapper;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "PowerStripEnergyMonitoringHandler")]
|
||||||
|
pub struct PyPowerStripEnergyMonitoringHandler {
|
||||||
|
inner: Arc<RwLock<PowerStripEnergyMonitoringHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyPowerStripEnergyMonitoringHandler {
|
||||||
|
pub fn new(handler: PowerStripEnergyMonitoringHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(handler)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_identifier(
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
position: Option<u8>,
|
||||||
|
) -> PyResult<Plug> {
|
||||||
|
match (device_id, nickname, position) {
|
||||||
|
(Some(device_id), _, _) => Ok(Plug::ByDeviceId(device_id)),
|
||||||
|
(None, Some(nickname), _) => Ok(Plug::ByNickname(nickname)),
|
||||||
|
(None, None, Some(position)) => Ok(Plug::ByPosition(position)),
|
||||||
|
_ => Err(Into::<ErrorWrapper>::into(Error::Validation {
|
||||||
|
field: "identifier".to_string(),
|
||||||
|
message: "Either a device_id, nickname, or position must be provided".to_string(),
|
||||||
|
})
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyPowerStripEnergyMonitoringHandler {
|
||||||
|
pub async fn refresh_session(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.write().await.deref_mut(),
|
||||||
|
PowerStripEnergyMonitoringHandler::refresh_session,
|
||||||
|
discard_result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripEnergyMonitoringHandler::device_reboot,
|
||||||
|
delay_s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reset(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripEnergyMonitoringHandler::device_reset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<DeviceInfoPowerStripResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripEnergyMonitoringHandler::get_device_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripEnergyMonitoringHandler::get_device_info_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_child_device_list(&self) -> PyResult<Py<PyList>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let children = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripEnergyMonitoringHandler::get_child_device_list
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Python::attach(|py| {
|
||||||
|
let results = PyList::empty(py);
|
||||||
|
|
||||||
|
for child in children {
|
||||||
|
results.append(child.into_pyobject(py)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results.into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_child_device_list_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripEnergyMonitoringHandler::get_child_device_list_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_child_device_component_list_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripEnergyMonitoringHandler::get_child_device_component_list_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (device_id=None, nickname=None, position=None))]
|
||||||
|
pub async fn plug(
|
||||||
|
&self,
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
position: Option<u8>,
|
||||||
|
) -> PyResult<PyPowerStripPlugEnergyMonitoringHandler> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let identifier =
|
||||||
|
PyPowerStripEnergyMonitoringHandler::parse_identifier(device_id, nickname, position)?;
|
||||||
|
|
||||||
|
let child_handler = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripEnergyMonitoringHandler::plug,
|
||||||
|
identifier
|
||||||
|
)?;
|
||||||
|
Ok(PyPowerStripPlugEnergyMonitoringHandler::new(child_handler))
|
||||||
|
}
|
||||||
|
}
|
||||||
143
agents/tapo/tapo-fork/tapo-py/src/api/power_strip_handler.rs
Normal file
143
agents/tapo/tapo-fork/tapo-py/src/api/power_strip_handler.rs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::{PyDict, PyList};
|
||||||
|
use tapo::responses::DeviceInfoPowerStripResult;
|
||||||
|
use tapo::{DeviceManagementExt as _, Error, Plug, PowerStripHandler};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::api::PyPowerStripPlugHandler;
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::errors::ErrorWrapper;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "PowerStripHandler")]
|
||||||
|
pub struct PyPowerStripHandler {
|
||||||
|
inner: Arc<RwLock<PowerStripHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyPowerStripHandler {
|
||||||
|
pub fn new(handler: PowerStripHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(handler)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_identifier(
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
position: Option<u8>,
|
||||||
|
) -> PyResult<Plug> {
|
||||||
|
match (device_id, nickname, position) {
|
||||||
|
(Some(device_id), _, _) => Ok(Plug::ByDeviceId(device_id)),
|
||||||
|
(None, Some(nickname), _) => Ok(Plug::ByNickname(nickname)),
|
||||||
|
(None, None, Some(position)) => Ok(Plug::ByPosition(position)),
|
||||||
|
_ => Err(Into::<ErrorWrapper>::into(Error::Validation {
|
||||||
|
field: "identifier".to_string(),
|
||||||
|
message: "Either a device_id, nickname, or position must be provided".to_string(),
|
||||||
|
})
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyPowerStripHandler {
|
||||||
|
pub async fn refresh_session(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.write().await.deref_mut(),
|
||||||
|
PowerStripHandler::refresh_session,
|
||||||
|
discard_result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripHandler::device_reboot,
|
||||||
|
delay_s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reset(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripHandler::device_reset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<DeviceInfoPowerStripResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripHandler::get_device_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripHandler::get_device_info_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_child_device_list(&self) -> PyResult<Py<PyList>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let children = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripHandler::get_child_device_list
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Python::attach(|py| {
|
||||||
|
let results = PyList::empty(py);
|
||||||
|
|
||||||
|
for child in children {
|
||||||
|
results.append(child.into_pyobject(py)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results.into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_child_device_list_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripHandler::get_child_device_list_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_child_device_component_list_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripHandler::get_child_device_component_list_json
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(signature = (device_id=None, nickname=None, position=None))]
|
||||||
|
pub async fn plug(
|
||||||
|
&self,
|
||||||
|
device_id: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
position: Option<u8>,
|
||||||
|
) -> PyResult<PyPowerStripPlugHandler> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let identifier = PyPowerStripHandler::parse_identifier(device_id, nickname, position)?;
|
||||||
|
|
||||||
|
let child_handler = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
PowerStripHandler::plug,
|
||||||
|
identifier
|
||||||
|
)?;
|
||||||
|
Ok(PyPowerStripPlugHandler::new(child_handler))
|
||||||
|
}
|
||||||
|
}
|
||||||
8
agents/tapo/tapo-fork/tapo-py/src/api/py_handler_ext.rs
Normal file
8
agents/tapo/tapo-fork/tapo-py/src/api/py_handler_ext.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use tapo::HandlerExt;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
pub trait PyHandlerExt: Send + Sync + Sized {
|
||||||
|
fn get_inner_handler(&self) -> Arc<RwLock<impl HandlerExt + 'static>>;
|
||||||
|
}
|
||||||
138
agents/tapo/tapo-fork/tapo-py/src/api/rgb_light_strip_handler.rs
Normal file
138
agents/tapo/tapo-fork/tapo-py/src/api/rgb_light_strip_handler.rs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
use tapo::requests::Color;
|
||||||
|
use tapo::responses::{DeviceInfoRgbLightStripResult, DeviceUsageEnergyMonitoringResult};
|
||||||
|
use tapo::{DeviceManagementExt as _, HandlerExt, RgbLightStripHandler};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::api::PyHandlerExt;
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::requests::PyColorLightSetDeviceInfoParams;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "RgbLightStripHandler")]
|
||||||
|
pub struct PyRgbLightStripHandler {
|
||||||
|
pub inner: Arc<RwLock<RgbLightStripHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyRgbLightStripHandler {
|
||||||
|
pub fn new(handler: RgbLightStripHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(handler)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyHandlerExt for PyRgbLightStripHandler {
|
||||||
|
fn get_inner_handler(&self) -> Arc<RwLock<impl HandlerExt + 'static>> {
|
||||||
|
Arc::clone(&self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyRgbLightStripHandler {
|
||||||
|
pub async fn refresh_session(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.write().await.deref_mut(),
|
||||||
|
RgbLightStripHandler::refresh_session,
|
||||||
|
discard_result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), RgbLightStripHandler::on)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn off(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), RgbLightStripHandler::off)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbLightStripHandler::device_reboot,
|
||||||
|
delay_s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reset(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbLightStripHandler::device_reset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<DeviceInfoRgbLightStripResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbLightStripHandler::get_device_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbLightStripHandler::get_device_info_json,
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbLightStripHandler::get_device_usage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self) -> PyColorLightSetDeviceInfoParams {
|
||||||
|
PyColorLightSetDeviceInfoParams::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_brightness(&self, brightness: u8) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbLightStripHandler::set_brightness,
|
||||||
|
brightness
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_color(&self, color: Color) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbLightStripHandler::set_color,
|
||||||
|
color
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_hue_saturation(&self, hue: u16, saturation: u8) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbLightStripHandler::set_hue_saturation,
|
||||||
|
hue,
|
||||||
|
saturation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_color_temperature(&self, color_temperature: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbLightStripHandler::set_color_temperature,
|
||||||
|
color_temperature
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use pyo3::exceptions::PyTypeError;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyDict;
|
||||||
|
use tapo::requests::{Color, LightingEffect, LightingEffectPreset};
|
||||||
|
use tapo::responses::{DeviceInfoRgbicLightStripResult, DeviceUsageEnergyMonitoringResult};
|
||||||
|
use tapo::{DeviceManagementExt as _, HandlerExt, RgbicLightStripHandler};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::api::PyHandlerExt;
|
||||||
|
use crate::call_handler_method;
|
||||||
|
use crate::requests::{PyColorLightSetDeviceInfoParams, PyLightingEffect};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "RgbicLightStripHandler")]
|
||||||
|
pub struct PyRgbicLightStripHandler {
|
||||||
|
pub inner: Arc<RwLock<RgbicLightStripHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyRgbicLightStripHandler {
|
||||||
|
pub fn new(handler: RgbicLightStripHandler) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(handler)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyHandlerExt for PyRgbicLightStripHandler {
|
||||||
|
fn get_inner_handler(&self) -> Arc<RwLock<impl HandlerExt + 'static>> {
|
||||||
|
Arc::clone(&self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyRgbicLightStripHandler {
|
||||||
|
pub async fn refresh_session(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.write().await.deref_mut(),
|
||||||
|
RgbicLightStripHandler::refresh_session,
|
||||||
|
discard_result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn on(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), RgbicLightStripHandler::on)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn off(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(handler.read().await.deref(), RgbicLightStripHandler::off)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reboot(&self, delay_s: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbicLightStripHandler::device_reboot,
|
||||||
|
delay_s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn device_reset(&self) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbicLightStripHandler::device_reset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info(&self) -> PyResult<DeviceInfoRgbicLightStripResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbicLightStripHandler::get_device_info
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_info_json(&self) -> PyResult<Py<PyDict>> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let result = call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbicLightStripHandler::get_device_info_json,
|
||||||
|
)?;
|
||||||
|
Python::attach(|py| tapo::python::serde_object_to_py_dict(py, &result))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_usage(&self) -> PyResult<DeviceUsageEnergyMonitoringResult> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbicLightStripHandler::get_device_usage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self) -> PyColorLightSetDeviceInfoParams {
|
||||||
|
PyColorLightSetDeviceInfoParams::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_brightness(&self, brightness: u8) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbicLightStripHandler::set_brightness,
|
||||||
|
brightness
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_color(&self, color: Color) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbicLightStripHandler::set_color,
|
||||||
|
color
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_hue_saturation(&self, hue: u16, saturation: u8) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbicLightStripHandler::set_hue_saturation,
|
||||||
|
hue,
|
||||||
|
saturation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_color_temperature(&self, color_temperature: u16) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbicLightStripHandler::set_color_temperature,
|
||||||
|
color_temperature
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_lighting_effect(&self, lighting_effect: Py<PyAny>) -> PyResult<()> {
|
||||||
|
let handler = self.inner.clone();
|
||||||
|
let lighting_effect = map_lighting_effect(lighting_effect)?;
|
||||||
|
call_handler_method!(
|
||||||
|
handler.read().await.deref(),
|
||||||
|
RgbicLightStripHandler::set_lighting_effect,
|
||||||
|
lighting_effect
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_lighting_effect(lighting_effect: Py<PyAny>) -> PyResult<LightingEffect> {
|
||||||
|
if let Some(lighting_effect) =
|
||||||
|
Python::attach(|py| lighting_effect.extract::<LightingEffectPreset>(py).ok())
|
||||||
|
{
|
||||||
|
return Ok(lighting_effect.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(lighting_effect) =
|
||||||
|
Python::attach(|py| lighting_effect.extract::<PyLightingEffect>(py).ok())
|
||||||
|
{
|
||||||
|
return Ok(lighting_effect.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(PyErr::new::<PyTypeError, _>(
|
||||||
|
"Invalid lighting effect type. Must be one of `LightingEffect` or `LightingEffectPreset`",
|
||||||
|
))
|
||||||
|
}
|
||||||
23
agents/tapo/tapo-fork/tapo-py/src/errors.rs
Normal file
23
agents/tapo/tapo-fork/tapo-py/src/errors.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
use pyo3::PyErr;
|
||||||
|
use pyo3::exceptions::PyException;
|
||||||
|
use tapo::Error;
|
||||||
|
|
||||||
|
pub struct ErrorWrapper(pub Error);
|
||||||
|
|
||||||
|
impl From<Error> for ErrorWrapper {
|
||||||
|
fn from(err: Error) -> Self {
|
||||||
|
Self(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<anyhow::Error> for ErrorWrapper {
|
||||||
|
fn from(err: anyhow::Error) -> Self {
|
||||||
|
Self(err.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ErrorWrapper> for PyErr {
|
||||||
|
fn from(err: ErrorWrapper) -> PyErr {
|
||||||
|
PyException::new_err(format!("{:?}", err.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
209
agents/tapo/tapo-fork/tapo-py/src/lib.rs
Normal file
209
agents/tapo/tapo-fork/tapo-py/src/lib.rs
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
mod api;
|
||||||
|
mod errors;
|
||||||
|
mod requests;
|
||||||
|
mod responses;
|
||||||
|
mod runtime;
|
||||||
|
|
||||||
|
use log::LevelFilter;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3_log::{Caching, Logger};
|
||||||
|
|
||||||
|
use tapo::requests::{AlarmRingtone, AlarmVolume, Color, LightingEffectPreset, LightingEffectType};
|
||||||
|
use tapo::responses::{
|
||||||
|
AutoOffStatus, ColorLightState, CurrentPowerResult, DefaultBrightnessState,
|
||||||
|
DefaultColorLightState, DefaultLightState, DefaultPlugState, DefaultPowerType,
|
||||||
|
DefaultRgbLightStripState, DefaultRgbicLightStripState, DefaultStateType,
|
||||||
|
DeviceInfoColorLightResult, DeviceInfoGenericResult, DeviceInfoHubResult,
|
||||||
|
DeviceInfoLightResult, DeviceInfoPlugEnergyMonitoringResult, DeviceInfoPlugResult,
|
||||||
|
DeviceInfoPowerStripResult, DeviceInfoRgbLightStripResult, DeviceInfoRgbicLightStripResult,
|
||||||
|
DeviceUsageEnergyMonitoringResult, DeviceUsageResult, EnergyDataIntervalResult,
|
||||||
|
EnergyDataResult, EnergyUsageResult, KE100Result, OvercurrentStatus, OverheatStatus, PlugState,
|
||||||
|
PowerDataIntervalResult, PowerDataResult, PowerProtectionStatus,
|
||||||
|
PowerStripPlugEnergyMonitoringResult, PowerStripPlugResult, RgbLightStripState,
|
||||||
|
RgbicLightStripState, S200BLog, S200BResult, S200BRotationParams, Status, T31XResult, T100Log,
|
||||||
|
T100Result, T110Log, T110Result, T300Log, T300Result, TemperatureHumidityRecord,
|
||||||
|
TemperatureHumidityRecords, TemperatureUnit, TemperatureUnitKE100, UsageByPeriodResult,
|
||||||
|
WaterLeakStatus,
|
||||||
|
};
|
||||||
|
|
||||||
|
use api::{
|
||||||
|
PyApiClient, PyColorLightHandler, PyDeviceDiscovery, PyDeviceDiscoveryIter, PyDiscoveryResult,
|
||||||
|
PyGenericDeviceHandler, PyHubHandler, PyKE100Handler, PyLightHandler, PyMaybeDiscoveryResult,
|
||||||
|
PyPlugEnergyMonitoringHandler, PyPlugHandler, PyPowerStripEnergyMonitoringHandler,
|
||||||
|
PyPowerStripHandler, PyPowerStripPlugEnergyMonitoringHandler, PyPowerStripPlugHandler,
|
||||||
|
PyRgbLightStripHandler, PyRgbicLightStripHandler, PyT31XHandler, PyT100Handler, PyT110Handler,
|
||||||
|
PyT300Handler,
|
||||||
|
};
|
||||||
|
use requests::{
|
||||||
|
PyAlarmDuration, PyColorLightSetDeviceInfoParams, PyEnergyDataInterval, PyLightingEffect,
|
||||||
|
PyPowerDataInterval,
|
||||||
|
};
|
||||||
|
use responses::{
|
||||||
|
TriggerLogsS200BResult, TriggerLogsT100Result, TriggerLogsT110Result, TriggerLogsT300Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[pymodule]
|
||||||
|
#[pyo3(name = "tapo")]
|
||||||
|
fn tapo_py(py: Python, module: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
|
Logger::new(py, Caching::LoggersAndLevels)?
|
||||||
|
.filter(LevelFilter::Trace)
|
||||||
|
.install()
|
||||||
|
.expect("Failed to install the logger");
|
||||||
|
|
||||||
|
let requests = PyModule::new(py, "tapo.requests")?;
|
||||||
|
let responses = PyModule::new(py, "tapo.responses")?;
|
||||||
|
|
||||||
|
register_handlers(module)?;
|
||||||
|
register_requests(&requests)?;
|
||||||
|
register_responses(&responses)?;
|
||||||
|
register_responses_hub(&responses)?;
|
||||||
|
register_responses_power_strip(&responses)?;
|
||||||
|
|
||||||
|
module.add_submodule(&requests)?;
|
||||||
|
module.add_submodule(&responses)?;
|
||||||
|
|
||||||
|
let sys = py.import("sys")?;
|
||||||
|
let modules = sys.getattr("modules")?;
|
||||||
|
modules.set_item("tapo.requests", requests)?;
|
||||||
|
modules.set_item("tapo.responses", responses)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_requests(module: &Bound<'_, PyModule>) -> Result<(), PyErr> {
|
||||||
|
module.add_class::<Color>()?;
|
||||||
|
module.add_class::<PyLightingEffect>()?;
|
||||||
|
module.add_class::<LightingEffectPreset>()?;
|
||||||
|
module.add_class::<LightingEffectType>()?;
|
||||||
|
module.add_class::<PyColorLightSetDeviceInfoParams>()?;
|
||||||
|
module.add_class::<PyEnergyDataInterval>()?;
|
||||||
|
module.add_class::<PyPowerDataInterval>()?;
|
||||||
|
|
||||||
|
// hub requests
|
||||||
|
module.add_class::<AlarmRingtone>()?;
|
||||||
|
module.add_class::<AlarmVolume>()?;
|
||||||
|
module.add_class::<PyAlarmDuration>()?;
|
||||||
|
module.add_class::<TemperatureUnitKE100>()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_handlers(module: &Bound<'_, PyModule>) -> Result<(), PyErr> {
|
||||||
|
module.add_class::<PyApiClient>()?;
|
||||||
|
module.add_class::<PyColorLightHandler>()?;
|
||||||
|
module.add_class::<PyGenericDeviceHandler>()?;
|
||||||
|
module.add_class::<PyLightHandler>()?;
|
||||||
|
module.add_class::<PyPlugEnergyMonitoringHandler>()?;
|
||||||
|
module.add_class::<PyPlugHandler>()?;
|
||||||
|
module.add_class::<PyRgbLightStripHandler>()?;
|
||||||
|
module.add_class::<PyRgbicLightStripHandler>()?;
|
||||||
|
|
||||||
|
module.add_class::<PyHubHandler>()?;
|
||||||
|
module.add_class::<PyKE100Handler>()?;
|
||||||
|
module.add_class::<PyT100Handler>()?;
|
||||||
|
module.add_class::<PyT110Handler>()?;
|
||||||
|
module.add_class::<PyT300Handler>()?;
|
||||||
|
module.add_class::<PyT31XHandler>()?;
|
||||||
|
|
||||||
|
module.add_class::<PyPowerStripHandler>()?;
|
||||||
|
module.add_class::<PyPowerStripEnergyMonitoringHandler>()?;
|
||||||
|
module.add_class::<PyPowerStripPlugHandler>()?;
|
||||||
|
module.add_class::<PyPowerStripPlugEnergyMonitoringHandler>()?;
|
||||||
|
|
||||||
|
module.add_class::<PyDeviceDiscovery>()?;
|
||||||
|
module.add_class::<PyDeviceDiscoveryIter>()?;
|
||||||
|
module.add_class::<PyDiscoveryResult>()?;
|
||||||
|
module.add_class::<PyMaybeDiscoveryResult>()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_responses(module: &Bound<'_, PyModule>) -> Result<(), PyErr> {
|
||||||
|
module.add_class::<CurrentPowerResult>()?;
|
||||||
|
module.add_class::<DefaultBrightnessState>()?;
|
||||||
|
module.add_class::<DefaultPowerType>()?;
|
||||||
|
module.add_class::<DefaultStateType>()?;
|
||||||
|
module.add_class::<DeviceUsageEnergyMonitoringResult>()?;
|
||||||
|
module.add_class::<DeviceUsageResult>()?;
|
||||||
|
module.add_class::<EnergyDataIntervalResult>()?;
|
||||||
|
module.add_class::<EnergyDataResult>()?;
|
||||||
|
module.add_class::<EnergyUsageResult>()?;
|
||||||
|
module.add_class::<OvercurrentStatus>()?;
|
||||||
|
module.add_class::<OverheatStatus>()?;
|
||||||
|
module.add_class::<PowerDataIntervalResult>()?;
|
||||||
|
module.add_class::<PowerDataResult>()?;
|
||||||
|
module.add_class::<PowerProtectionStatus>()?;
|
||||||
|
module.add_class::<UsageByPeriodResult>()?;
|
||||||
|
|
||||||
|
// device info: generic
|
||||||
|
module.add_class::<DeviceInfoGenericResult>()?;
|
||||||
|
|
||||||
|
// device info: light
|
||||||
|
module.add_class::<DeviceInfoLightResult>()?;
|
||||||
|
module.add_class::<DefaultLightState>()?;
|
||||||
|
|
||||||
|
// device info: color light
|
||||||
|
module.add_class::<DeviceInfoColorLightResult>()?;
|
||||||
|
module.add_class::<DefaultColorLightState>()?;
|
||||||
|
module.add_class::<ColorLightState>()?;
|
||||||
|
|
||||||
|
// device info: rgb light strip
|
||||||
|
module.add_class::<DeviceInfoRgbLightStripResult>()?;
|
||||||
|
module.add_class::<DefaultRgbLightStripState>()?;
|
||||||
|
module.add_class::<RgbLightStripState>()?;
|
||||||
|
|
||||||
|
// device info: rgbic light strip
|
||||||
|
module.add_class::<DeviceInfoRgbicLightStripResult>()?;
|
||||||
|
module.add_class::<DefaultRgbicLightStripState>()?;
|
||||||
|
module.add_class::<RgbicLightStripState>()?;
|
||||||
|
module.add_class::<PyLightingEffect>()?;
|
||||||
|
module.add_class::<LightingEffectType>()?;
|
||||||
|
|
||||||
|
// device info: plugs
|
||||||
|
module.add_class::<DefaultPlugState>()?;
|
||||||
|
module.add_class::<DeviceInfoPlugEnergyMonitoringResult>()?;
|
||||||
|
module.add_class::<DeviceInfoPlugResult>()?;
|
||||||
|
module.add_class::<PlugState>()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_responses_hub(module: &Bound<'_, PyModule>) -> Result<(), PyErr> {
|
||||||
|
module.add_class::<DeviceInfoHubResult>()?;
|
||||||
|
module.add_class::<KE100Result>()?;
|
||||||
|
module.add_class::<S200BResult>()?;
|
||||||
|
module.add_class::<T100Result>()?;
|
||||||
|
module.add_class::<T110Result>()?;
|
||||||
|
module.add_class::<T300Result>()?;
|
||||||
|
module.add_class::<T31XResult>()?;
|
||||||
|
|
||||||
|
// child devices
|
||||||
|
module.add_class::<S200BLog>()?;
|
||||||
|
module.add_class::<S200BRotationParams>()?;
|
||||||
|
module.add_class::<Status>()?;
|
||||||
|
module.add_class::<T100Log>()?;
|
||||||
|
module.add_class::<T110Log>()?;
|
||||||
|
module.add_class::<T300Log>()?;
|
||||||
|
module.add_class::<TemperatureHumidityRecord>()?;
|
||||||
|
module.add_class::<TemperatureHumidityRecords>()?;
|
||||||
|
module.add_class::<TemperatureUnit>()?;
|
||||||
|
module.add_class::<TemperatureUnitKE100>()?;
|
||||||
|
module.add_class::<TriggerLogsS200BResult>()?;
|
||||||
|
module.add_class::<TriggerLogsT100Result>()?;
|
||||||
|
module.add_class::<TriggerLogsT110Result>()?;
|
||||||
|
module.add_class::<TriggerLogsT300Result>()?;
|
||||||
|
module.add_class::<WaterLeakStatus>()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_responses_power_strip(module: &Bound<'_, PyModule>) -> Result<(), PyErr> {
|
||||||
|
module.add_class::<DeviceInfoPowerStripResult>()?;
|
||||||
|
|
||||||
|
// child devices
|
||||||
|
module.add_class::<AutoOffStatus>()?;
|
||||||
|
module.add_class::<PowerStripPlugResult>()?;
|
||||||
|
module.add_class::<PowerStripPlugEnergyMonitoringResult>()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
9
agents/tapo/tapo-fork/tapo-py/src/requests.rs
Normal file
9
agents/tapo/tapo-fork/tapo-py/src/requests.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mod energy_data_interval;
|
||||||
|
mod play_alarm;
|
||||||
|
mod power_data_interval;
|
||||||
|
mod set_device_info;
|
||||||
|
|
||||||
|
pub use energy_data_interval::*;
|
||||||
|
pub use play_alarm::*;
|
||||||
|
pub use power_data_interval::*;
|
||||||
|
pub use set_device_info::*;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
#[pyclass(name = "EnergyDataInterval", eq, eq_int)]
|
||||||
|
pub enum PyEnergyDataInterval {
|
||||||
|
Hourly,
|
||||||
|
Daily,
|
||||||
|
Monthly,
|
||||||
|
}
|
||||||
9
agents/tapo/tapo-fork/tapo-py/src/requests/play_alarm.rs
Normal file
9
agents/tapo/tapo-fork/tapo-py/src/requests/play_alarm.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[pyclass(name = "AlarmDuration", eq)]
|
||||||
|
pub enum PyAlarmDuration {
|
||||||
|
Continuous,
|
||||||
|
Once,
|
||||||
|
Seconds,
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
#[pyclass(name = "PowerDataInterval", eq, eq_int)]
|
||||||
|
pub enum PyPowerDataInterval {
|
||||||
|
Every5Minutes,
|
||||||
|
Hourly,
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mod color_light;
|
||||||
|
mod lighting_effect;
|
||||||
|
|
||||||
|
pub use color_light::*;
|
||||||
|
pub use lighting_effect::*;
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use tapo::requests::{Color, ColorLightSetDeviceInfoParams};
|
||||||
|
|
||||||
|
use crate::api::{
|
||||||
|
PyColorLightHandler, PyHandlerExt, PyRgbLightStripHandler, PyRgbicLightStripHandler,
|
||||||
|
};
|
||||||
|
use crate::errors::ErrorWrapper;
|
||||||
|
use crate::runtime::tokio;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "LightSetDeviceInfoParams")]
|
||||||
|
pub struct PyColorLightSetDeviceInfoParams {
|
||||||
|
params: ColorLightSetDeviceInfoParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyColorLightSetDeviceInfoParams {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
params: ColorLightSetDeviceInfoParams::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn _send_to_inner_handler(&self, handler: impl PyHandlerExt) -> PyResult<()> {
|
||||||
|
let params = self.params.clone();
|
||||||
|
let handler = handler.get_inner_handler();
|
||||||
|
|
||||||
|
tokio()
|
||||||
|
.spawn(async move {
|
||||||
|
let handler_lock = handler.read().await;
|
||||||
|
|
||||||
|
params
|
||||||
|
.send(handler_lock.deref())
|
||||||
|
.await
|
||||||
|
.map_err(ErrorWrapper)?;
|
||||||
|
|
||||||
|
Ok::<_, ErrorWrapper>(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(anyhow::Error::from)
|
||||||
|
.map_err(ErrorWrapper::from)??;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyColorLightSetDeviceInfoParams {
|
||||||
|
pub fn on(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
params: self.params.clone().on(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn off(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
params: self.params.clone().off(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn brightness(&self, brightness: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
params: self.params.clone().brightness(brightness),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color(&self, color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
params: self.params.clone().color(color),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hue_saturation(&self, hue: u16, saturation: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
params: self.params.clone().hue_saturation(hue, saturation),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color_temperature(&self, color_temperature: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
params: self.params.clone().color_temperature(color_temperature),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send(&self, handler: Py<PyAny>) -> PyResult<()> {
|
||||||
|
if let Some(handler) = Python::attach(|py| handler.extract::<PyColorLightHandler>(py).ok())
|
||||||
|
{
|
||||||
|
return self._send_to_inner_handler(handler).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(handler) =
|
||||||
|
Python::attach(|py| handler.extract::<PyRgbLightStripHandler>(py).ok())
|
||||||
|
{
|
||||||
|
return self._send_to_inner_handler(handler).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(handler) =
|
||||||
|
Python::attach(|py| handler.extract::<PyRgbicLightStripHandler>(py).ok())
|
||||||
|
{
|
||||||
|
return self._send_to_inner_handler(handler).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
|
||||||
|
"Invalid handler type. Must be one of `PyColorLightHandler`, `PyRgbLightStripHandler` or `PyRgbicLightStripHandler`",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
use tapo::requests::{LightingEffect, LightingEffectType};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[pyclass(name = "LightingEffect")]
|
||||||
|
pub struct PyLightingEffect {
|
||||||
|
inner: LightingEffect,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyLightingEffect {
|
||||||
|
#[new]
|
||||||
|
fn new(
|
||||||
|
name: String,
|
||||||
|
r#type: LightingEffectType,
|
||||||
|
is_custom: bool,
|
||||||
|
enabled: bool,
|
||||||
|
brightness: u8,
|
||||||
|
display_colors: Vec<[u16; 3]>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: LightingEffect::new(
|
||||||
|
name,
|
||||||
|
r#type,
|
||||||
|
is_custom,
|
||||||
|
enabled,
|
||||||
|
brightness,
|
||||||
|
display_colors,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_brightness(mut slf: PyRefMut<'_, Self>, brightness: u8) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.brightness = brightness;
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_is_custom(mut slf: PyRefMut<'_, Self>, is_custom: bool) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.is_custom = is_custom;
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_display_colors(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
display_colors: Vec<[u16; 3]>,
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.display_colors = display_colors;
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_enabled(mut slf: PyRefMut<'_, Self>, enabled: bool) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.enabled = enabled;
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_id(mut slf: PyRefMut<'_, Self>, id: String) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.id = id;
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_name(mut slf: PyRefMut<'_, Self>, name: String) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.name = name;
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_type(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
r#type: LightingEffectType,
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.r#type = r#type;
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_backgrounds(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
backgrounds: Vec<[u16; 3]>,
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.backgrounds = Some(backgrounds);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_brightness_range(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
brightness_range: [u8; 2],
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.brightness_range = Some(brightness_range.to_vec());
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_direction(mut slf: PyRefMut<'_, Self>, direction: u8) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.direction = Some(direction);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_duration(mut slf: PyRefMut<'_, Self>, duration: u64) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.duration = Some(duration);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_expansion_strategy(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
expansion_strategy: u8,
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.expansion_strategy = Some(expansion_strategy);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_fade_off(mut slf: PyRefMut<'_, Self>, fade_off: u16) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.fade_off = Some(fade_off);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_hue_range(mut slf: PyRefMut<'_, Self>, hue_range: [u16; 2]) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.hue_range = Some(hue_range);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_init_states(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
init_states: Vec<[u16; 3]>,
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.init_states = Some(init_states);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_random_seed(mut slf: PyRefMut<'_, Self>, random_seed: u64) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.random_seed = Some(random_seed);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_repeat_times(mut slf: PyRefMut<'_, Self>, repeat_times: u8) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.repeat_times = Some(repeat_times);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_run_time(mut slf: PyRefMut<'_, Self>, run_time: u64) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.run_time = Some(run_time);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_saturation_range(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
saturation_range: [u8; 2],
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.saturation_range = Some(saturation_range);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_segment_length(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
segment_length: u8,
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.segment_length = Some(segment_length);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_segments(mut slf: PyRefMut<'_, Self>, segments: Vec<u8>) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.segments = Some(segments);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_sequence(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
sequence: Vec<[u16; 3]>,
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.sequence = Some(sequence);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_spread(mut slf: PyRefMut<'_, Self>, spread: u8) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.spread = Some(spread);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_transition(mut slf: PyRefMut<'_, Self>, transition: u16) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.transition = Some(transition);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_transition_range(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
transition_range: [u16; 2],
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.transition_range = Some(transition_range);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_transition_sequence(
|
||||||
|
mut slf: PyRefMut<'_, Self>,
|
||||||
|
transition_sequence: Vec<u16>,
|
||||||
|
) -> PyRefMut<'_, Self> {
|
||||||
|
(*slf).inner.transition_sequence = Some(transition_sequence);
|
||||||
|
slf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PyLightingEffect> for LightingEffect {
|
||||||
|
fn from(effect: PyLightingEffect) -> Self {
|
||||||
|
effect.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
3
agents/tapo/tapo-fork/tapo-py/src/responses.rs
Normal file
3
agents/tapo/tapo-fork/tapo-py/src/responses.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mod child_device_list_hub_result;
|
||||||
|
|
||||||
|
pub use child_device_list_hub_result::*;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
mod s200b_result;
|
||||||
|
mod t100_result;
|
||||||
|
mod t110_result;
|
||||||
|
mod t300_result;
|
||||||
|
|
||||||
|
pub use s200b_result::*;
|
||||||
|
pub use t100_result::*;
|
||||||
|
pub use t110_result::*;
|
||||||
|
pub use t300_result::*;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tapo::responses::{S200BLog, TriggerLogsResult};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct TriggerLogsS200BResult {
|
||||||
|
start_id: u64,
|
||||||
|
sum: u64,
|
||||||
|
logs: Vec<S200BLog>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TriggerLogsResult<S200BLog>> for TriggerLogsS200BResult {
|
||||||
|
fn from(result: TriggerLogsResult<S200BLog>) -> Self {
|
||||||
|
Self {
|
||||||
|
start_id: result.start_id,
|
||||||
|
sum: result.sum,
|
||||||
|
logs: result.logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3::pymethods]
|
||||||
|
impl TriggerLogsS200BResult {
|
||||||
|
pub fn to_dict(&self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
|
||||||
|
let value = serde_json::to_value(self)
|
||||||
|
.map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?;
|
||||||
|
|
||||||
|
tapo::python::serde_object_to_py_dict(py, &value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tapo::responses::{T100Log, TriggerLogsResult};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct TriggerLogsT100Result {
|
||||||
|
start_id: u64,
|
||||||
|
sum: u64,
|
||||||
|
logs: Vec<T100Log>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TriggerLogsResult<T100Log>> for TriggerLogsT100Result {
|
||||||
|
fn from(result: TriggerLogsResult<T100Log>) -> Self {
|
||||||
|
Self {
|
||||||
|
start_id: result.start_id,
|
||||||
|
sum: result.sum,
|
||||||
|
logs: result.logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3::pymethods]
|
||||||
|
impl TriggerLogsT100Result {
|
||||||
|
pub fn to_dict(&self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
|
||||||
|
let value = serde_json::to_value(self)
|
||||||
|
.map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?;
|
||||||
|
|
||||||
|
tapo::python::serde_object_to_py_dict(py, &value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tapo::responses::{T110Log, TriggerLogsResult};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct TriggerLogsT110Result {
|
||||||
|
start_id: u64,
|
||||||
|
sum: u64,
|
||||||
|
logs: Vec<T110Log>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TriggerLogsResult<T110Log>> for TriggerLogsT110Result {
|
||||||
|
fn from(result: TriggerLogsResult<T110Log>) -> Self {
|
||||||
|
Self {
|
||||||
|
start_id: result.start_id,
|
||||||
|
sum: result.sum,
|
||||||
|
logs: result.logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3::pymethods]
|
||||||
|
impl TriggerLogsT110Result {
|
||||||
|
pub fn to_dict(&self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
|
||||||
|
let value = serde_json::to_value(self)
|
||||||
|
.map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?;
|
||||||
|
|
||||||
|
tapo::python::serde_object_to_py_dict(py, &value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tapo::responses::{T300Log, TriggerLogsResult};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub struct TriggerLogsT300Result {
|
||||||
|
start_id: u64,
|
||||||
|
sum: u64,
|
||||||
|
logs: Vec<T300Log>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TriggerLogsResult<T300Log>> for TriggerLogsT300Result {
|
||||||
|
fn from(result: TriggerLogsResult<T300Log>) -> Self {
|
||||||
|
Self {
|
||||||
|
start_id: result.start_id,
|
||||||
|
sum: result.sum,
|
||||||
|
logs: result.logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3::pymethods]
|
||||||
|
impl TriggerLogsT300Result {
|
||||||
|
pub fn to_dict(&self, py: pyo3::Python) -> pyo3::PyResult<pyo3::Py<pyo3::types::PyDict>> {
|
||||||
|
let value = serde_json::to_value(self)
|
||||||
|
.map_err(|e| pyo3::exceptions::PyException::new_err(e.to_string()))?;
|
||||||
|
|
||||||
|
tapo::python::serde_object_to_py_dict(py, &value)
|
||||||
|
}
|
||||||
|
}
|
||||||
60
agents/tapo/tapo-fork/tapo-py/src/runtime.rs
Normal file
60
agents/tapo/tapo-fork/tapo-py/src/runtime.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
pub fn tokio() -> &'static tokio::runtime::Runtime {
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
static RT: std::sync::OnceLock<Runtime> = OnceLock::new();
|
||||||
|
RT.get_or_init(|| Runtime::new().expect("Failed to create tokio runtime"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! call_handler_constructor {
|
||||||
|
($self:ident, $constructor:path, $($params:expr),*) => {{
|
||||||
|
let client = $self.client.clone();
|
||||||
|
let handler = $crate::runtime::tokio()
|
||||||
|
.spawn(async move {
|
||||||
|
$constructor(client, $($params),*)
|
||||||
|
.await
|
||||||
|
.map_err($crate::errors::ErrorWrapper)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(anyhow::Error::from)
|
||||||
|
.map_err($crate::errors::ErrorWrapper::from)??;
|
||||||
|
|
||||||
|
handler
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! call_handler_method {
|
||||||
|
($handler:expr, $method:path) => (call_handler_method!($handler, $method,));
|
||||||
|
($handler:expr, $method:path, discard_result) => (call_handler_method!($handler, $method, discard_result,));
|
||||||
|
($handler:expr, $method:path, $($param:expr),*) => {{
|
||||||
|
let result = $crate::runtime::tokio()
|
||||||
|
.spawn(async move {
|
||||||
|
let result = $method($handler, $($param),*)
|
||||||
|
.await
|
||||||
|
.map_err($crate::errors::ErrorWrapper)?;
|
||||||
|
|
||||||
|
Ok::<_, $crate::errors::ErrorWrapper>(result)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(anyhow::Error::from)
|
||||||
|
.map_err($crate::errors::ErrorWrapper::from)??;
|
||||||
|
|
||||||
|
Ok::<_, PyErr>(result)
|
||||||
|
}};
|
||||||
|
($handler:expr, $method:path, discard_result, $($param:expr),*) => {{
|
||||||
|
let result = $crate::runtime::tokio()
|
||||||
|
.spawn(async move {
|
||||||
|
$method($handler, $($param),*)
|
||||||
|
.await
|
||||||
|
.map_err($crate::errors::ErrorWrapper)?;
|
||||||
|
|
||||||
|
Ok::<_, $crate::errors::ErrorWrapper>(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(anyhow::Error::from)
|
||||||
|
.map_err($crate::errors::ErrorWrapper::from)??;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}};
|
||||||
|
}
|
||||||
5
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/__init__.py
Normal file
5
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from .tapo import *
|
||||||
|
|
||||||
|
__doc__ = tapo.__doc__
|
||||||
|
if hasattr(tapo, "__all__"):
|
||||||
|
__all__ = tapo.__all__
|
||||||
23
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/__init__.pyi
Normal file
23
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/__init__.pyi
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from .api_client import *
|
||||||
|
from .color_light_handler import *
|
||||||
|
from .device_discovery import *
|
||||||
|
from .discovery_result import *
|
||||||
|
from .generic_device_handler import *
|
||||||
|
from .hub_handler import *
|
||||||
|
from .ke100_handler import *
|
||||||
|
from .light_handler import *
|
||||||
|
from .plug_energy_monitoring_handler import *
|
||||||
|
from .plug_handler import *
|
||||||
|
from .power_strip_energy_monitoring_handler import *
|
||||||
|
from .power_strip_handler import *
|
||||||
|
from .power_strip_plug_energy_monitoring_handler import *
|
||||||
|
from .power_strip_plug_handler import *
|
||||||
|
from .requests import *
|
||||||
|
from .responses import *
|
||||||
|
from .rgb_light_strip_handler import *
|
||||||
|
from .rgbic_light_strip_handler import *
|
||||||
|
from .s200b_handler import *
|
||||||
|
from .t100_handler import *
|
||||||
|
from .t110_handler import *
|
||||||
|
from .t300_handler import *
|
||||||
|
from .t31x_handler import *
|
||||||
487
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/api_client.pyi
Normal file
487
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/api_client.pyi
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
"""Tapo API Client.
|
||||||
|
|
||||||
|
Tested with light bulbs (L510, L520, L530, L535, L610, L630), light strips (L900, L920, L930), plugs (P100, P105, P110, P110M, P115),
|
||||||
|
power strips (P300, P304M, P306, P316M), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
from tapo import ApiClient
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l530("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
|
```
|
||||||
|
|
||||||
|
See [more examples](https://github.com/mihai-dinculescu/tapo/tree/main/tapo-py/examples).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .color_light_handler import ColorLightHandler
|
||||||
|
from .device_discovery import DeviceDiscovery
|
||||||
|
from .generic_device_handler import GenericDeviceHandler
|
||||||
|
from .hub_handler import HubHandler
|
||||||
|
from .light_handler import LightHandler
|
||||||
|
from .plug_energy_monitoring_handler import PlugEnergyMonitoringHandler
|
||||||
|
from .plug_handler import PlugHandler
|
||||||
|
from .power_strip_energy_monitoring_handler import PowerStripEnergyMonitoringHandler
|
||||||
|
from .power_strip_handler import PowerStripHandler
|
||||||
|
from .rgb_light_strip_handler import RgbLightStripHandler
|
||||||
|
from .rgbic_light_strip_handler import RgbicLightStripHandler
|
||||||
|
|
||||||
|
class ApiClient:
|
||||||
|
"""Tapo API Client.
|
||||||
|
|
||||||
|
Tested with light bulbs (L510, L520, L530, L535, L610, L630), light strips (L900, L920, L930), plugs (P100, P105, P110, P110M, P115),
|
||||||
|
power strips (P300, P304M, P306, P316M), hubs (H100), switches (S200B) and sensors (KE100, T100, T110, T300, T310, T315).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
from tapo import ApiClient
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l530("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
|
```
|
||||||
|
|
||||||
|
See [more examples](https://github.com/mihai-dinculescu/tapo/tree/main/tapo-py/examples).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, tapo_username: str, tapo_password: str, timeout_s: int = 30) -> None:
|
||||||
|
"""Returns a new instance of `ApiClient`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tapo_username (str): The Tapo username.
|
||||||
|
tapo_password (str): The Tapo password.
|
||||||
|
timeout_s (int): The connection timeout in seconds. The default value is 30 seconds.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ApiClient: Tapo API Client.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
from tapo import ApiClient
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l530("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
|
```
|
||||||
|
|
||||||
|
See [more examples](https://github.com/mihai-dinculescu/tapo/tree/main/tapo-py/examples).
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def discover_devices(self, target: str, timeout_s: int = 10) -> DeviceDiscovery:
|
||||||
|
"""Discovers one or more devices located at a specified unicast or broadcast IP address.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target (str): The IP address at which the discovery will take place.
|
||||||
|
This address can be either a unicast (e.g. `192.168.1.10`) or a
|
||||||
|
broadcast address (e.g. `192.168.1.255`, `255.255.255.255`, etc.).
|
||||||
|
timeout_s (int): The maximum time to wait for a response from the device(s) in seconds.
|
||||||
|
Must be between `1` and `60`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
AsyncIterator[MaybeDiscoveryResult]: An asynchronous iterator that yields `MaybeDiscoveryResult` objects.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
|
||||||
|
async for device in client.discover_devices("192.168.1.255"):
|
||||||
|
try:
|
||||||
|
device = discovery_result.get()
|
||||||
|
match device:
|
||||||
|
case DiscoveryResult.PlugEnergyMonitoring(device_info):
|
||||||
|
print(
|
||||||
|
f"Found '{device_info.nickname}' of model '{device_info.model}' at IP address '{device_info.ip}'."
|
||||||
|
)
|
||||||
|
# ...
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error discovering device: {e}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def generic_device(self, ip_address: str) -> GenericDeviceHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `GenericDeviceHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GenericDeviceHandler: Handler for generic devices. It provides the
|
||||||
|
functionality common to all Tapo [devices](https://www.tapo.com/en/).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.generic_device("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def l510(self, ip_address: str) -> LightHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `LightHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LightHandler: Handler for the [L510](https://www.tapo.com/en/search/?q=L510),
|
||||||
|
[L520](https://www.tapo.com/en/search/?q=L520) and [L610](https://www.tapo.com/en/search/?q=L610) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l510("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def l520(self, ip_address: str) -> LightHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `LightHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LightHandler: Handler for the [L510](https://www.tapo.com/en/search/?q=L510),
|
||||||
|
[L520](https://www.tapo.com/en/search/?q=L520) and [L610](https://www.tapo.com/en/search/?q=L610) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l520("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def l530(self, ip_address: str) -> ColorLightHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `ColorLightHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ColorLightHandler: Handler for the [L530](https://www.tapo.com/en/search/?q=L530),
|
||||||
|
[L535](https://www.tapo.com/en/search/?q=L535) and [L630](https://www.tapo.com/en/search/?q=L630) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l530("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def l535(self, ip_address: str) -> ColorLightHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `ColorLightHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ColorLightHandler: Handler for the [L530](https://www.tapo.com/en/search/?q=L530),
|
||||||
|
[L535](https://www.tapo.com/en/search/?q=L535) and [L630](https://www.tapo.com/en/search/?q=L630) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l535("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def l610(self, ip_address: str) -> LightHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `LightHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LightHandler: Handler for the [L510](https://www.tapo.com/en/search/?q=L510),
|
||||||
|
[L520](https://www.tapo.com/en/search/?q=L520) and [L610](https://www.tapo.com/en/search/?q=L610) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l610("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def l630(self, ip_address: str) -> ColorLightHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `ColorLightHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ColorLightHandler: Handler for the [L530](https://www.tapo.com/en/search/?q=L530),
|
||||||
|
[L630](https://www.tapo.com/en/search/?q=L630) and [L900](https://www.tapo.com/en/search/?q=L900) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l630("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def l900(self, ip_address: str) -> RgbLightStripHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `RgbLightStripHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
RgbLightStripHandler: Handler for the [L900](https://www.tapo.com/en/search/?q=L900) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l900("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def l920(self, ip_address: str) -> RgbicLightStripHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `RgbicLightStripHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
RgbicLightStripHandler: Handler for the [L920](https://www.tapo.com/en/search/?q=L920) and
|
||||||
|
[L930](https://www.tapo.com/en/search/?q=L930) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l920("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def l930(self, ip_address: str) -> RgbicLightStripHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `RgbicLightStripHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
RgbicLightStripHandler: Handler for the [L920](https://www.tapo.com/en/search/?q=L920) and
|
||||||
|
[L930](https://www.tapo.com/en/search/?q=L930) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.l930("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def p100(self, ip_address: str) -> PlugHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `PlugHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PlugHandler: Handler for the [P100](https://www.tapo.com/en/search/?q=P100) and
|
||||||
|
[P105](https://www.tapo.com/en/search/?q=P105) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.p100("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def p105(self, ip_address: str) -> PlugHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `PlugHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PlugHandler: Handler for the [P100](https://www.tapo.com/en/search/?q=P100) and
|
||||||
|
[P105](https://www.tapo.com/en/search/?q=P105) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.p105("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def p110(self, ip_address: str) -> PlugEnergyMonitoringHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `PlugEnergyMonitoringHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PlugEnergyMonitoringHandler: Handler for the [P110](https://www.tapo.com/en/search/?q=P110),
|
||||||
|
[P110M](https://www.tapo.com/en/search/?q=P110M) and
|
||||||
|
[P115](https://www.tapo.com/en/search/?q=P115) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.p110("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def p115(self, ip_address: str) -> PlugEnergyMonitoringHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `PlugEnergyMonitoringHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PlugEnergyMonitoringHandler: Handler for the [P110](https://www.tapo.com/en/search/?q=P110),
|
||||||
|
[P110M](https://www.tapo.com/en/search/?q=P110M) and
|
||||||
|
[P115](https://www.tapo.com/en/search/?q=P115) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
device = await client.p115("192.168.1.100")
|
||||||
|
|
||||||
|
await device.on()
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def p300(self, ip_address: str) -> PowerStripHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `PowerStripHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PowerStripHandler: Handler for the [P300](https://www.tp-link.com/en/search/?q=P300) and
|
||||||
|
[P306](https://www.tp-link.com/us/search/?q=P306) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
power_strip = await client.p300("192.168.1.100")
|
||||||
|
|
||||||
|
child_device_list = await power_strip.get_child_device_list()
|
||||||
|
print(f"Child device list: {child_device_list.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def p304(self, ip_address: str) -> PowerStripEnergyMonitoringHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `PowerStripEnergyMonitoringHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PowerStripEnergyMonitoringHandler: Handler for the [P304M](https://www.tp-link.com/uk/search/?q=P304M) and
|
||||||
|
[P316M](https://www.tp-link.com/us/search/?q=P316M) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
power_strip = await client.p304("192.168.1.100")
|
||||||
|
|
||||||
|
child_device_list = await power_strip.get_child_device_list()
|
||||||
|
print(f"Child device list: {child_device_list.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def p306(self, ip_address: str) -> PowerStripHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `PowerStripHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PowerStripHandler: Handler for the [P300](https://www.tp-link.com/en/search/?q=P300) and
|
||||||
|
[P306](https://www.tp-link.com/us/search/?q=P306) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
power_strip = await client.p306("192.168.1.100")
|
||||||
|
|
||||||
|
child_device_list = await power_strip.get_child_device_list()
|
||||||
|
print(f"Child device list: {child_device_list.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def p316(self, ip_address: str) -> PowerStripHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `PowerStripHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PowerStripEnergyMonitoringHandler: Handler for the [P304M](https://www.tp-link.com/uk/search/?q=P304M) and
|
||||||
|
[P316M](https://www.tp-link.com/us/search/?q=P316M) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
power_strip = await client.p316("192.168.1.100")
|
||||||
|
|
||||||
|
child_device_list = await power_strip.get_child_device_list()
|
||||||
|
print(f"Child device list: {child_device_list.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def h100(self, ip_address: str) -> HubHandler:
|
||||||
|
"""Specializes the given `ApiClient` into an authenticated `HubHandler`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip_address (str): The IP address of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HubHandler: Handler for the [H100](https://www.tapo.com/en/search/?q=H100) hubs.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
hub = await client.h100("192.168.1.100")
|
||||||
|
|
||||||
|
child_device_list = await hub.get_child_device_list()
|
||||||
|
print(f"Child device list: {child_device_list.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
from tapo.device_management_ext import DeviceManagementExt
|
||||||
|
from tapo.requests import Color, ColorLightSetDeviceInfoParams
|
||||||
|
from tapo.responses import DeviceInfoColorLightResult, DeviceUsageResult
|
||||||
|
|
||||||
|
class ColorLightHandler(DeviceManagementExt):
|
||||||
|
"""Handler for the [L530](https://www.tapo.com/en/search/?q=L530),
|
||||||
|
[L535](https://www.tapo.com/en/search/?q=L535) and
|
||||||
|
[L630](https://www.tapo.com/en/search/?q=L630) devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, handler: object):
|
||||||
|
"""Private constructor.
|
||||||
|
It should not be called from outside the tapo library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def refresh_session(self) -> None:
|
||||||
|
"""Refreshes the authentication session."""
|
||||||
|
|
||||||
|
async def on(self) -> None:
|
||||||
|
"""Turns *on* the device."""
|
||||||
|
|
||||||
|
async def off(self) -> None:
|
||||||
|
"""Turns *off* the device."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> DeviceInfoColorLightResult:
|
||||||
|
"""Returns *device info* as `DeviceInfoColorLightResult`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `ColorLightHandler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceInfoColorLightResult: Device info of Tapo L530, L535 and L630.
|
||||||
|
Superset of `GenericDeviceInfoResult`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_usage(self) -> DeviceUsageResult:
|
||||||
|
"""Returns *device usage* as `DeviceUsageResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceUsageResult: Contains the time usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def set(self) -> ColorLightSetDeviceInfoParams:
|
||||||
|
"""Returns a `ColorLightSetDeviceInfoParams` builder that allows
|
||||||
|
multiple properties to be set in a single request.
|
||||||
|
`ColorLightSetDeviceInfoParams.send` must be called at the end to apply the changes.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ColorLightSetDeviceInfoParams: Builder that is used by the
|
||||||
|
`ColorLightHandler.set` API to set multiple properties in a single request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_brightness(self, brightness: int) -> None:
|
||||||
|
"""Sets the *brightness* and turns *on* the device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
brightness (int): between 1 and 100
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_color(self, color: Color) -> None:
|
||||||
|
"""Sets the *color* and turns *on* the device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
color (Color): one of `tapo.Color` as defined in the Google Home app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_hue_saturation(self, hue: int, saturation: int) -> None:
|
||||||
|
"""Sets the *hue*, *saturation* and turns *on* the device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hue (int): between 0 and 360
|
||||||
|
saturation (int): between 1 and 100
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_color_temperature(self, color_temperature: int) -> None:
|
||||||
|
"""Sets the *color temperature* and turns *on* the device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
color_temperature (int): between 2500 and 6500
|
||||||
|
"""
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from typing import AsyncIterable, Iterable, Iterator, AsyncIterator
|
||||||
|
|
||||||
|
from .discovery_result import MaybeDiscoveryResult
|
||||||
|
|
||||||
|
class DeviceDiscoveryIter(Iterator[MaybeDiscoveryResult], AsyncIterator[MaybeDiscoveryResult]):
|
||||||
|
def __iter__(self) -> DeviceDiscoveryIter: ...
|
||||||
|
def __aiter__(self) -> DeviceDiscoveryIter: ...
|
||||||
|
def __next__(self) -> MaybeDiscoveryResult: ...
|
||||||
|
async def __anext__(self) -> MaybeDiscoveryResult: ...
|
||||||
|
|
||||||
|
class DeviceDiscovery(Iterable[MaybeDiscoveryResult], AsyncIterable[MaybeDiscoveryResult]):
|
||||||
|
def __iter__(self) -> DeviceDiscoveryIter: ...
|
||||||
|
def __aiter__(self) -> DeviceDiscoveryIter: ...
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
class DeviceManagementExt:
|
||||||
|
"""Extension class for device management capabilities like `device_reboot` and `device_reset`."""
|
||||||
|
|
||||||
|
async def device_reboot(self, delay_s: int) -> None:
|
||||||
|
"""*Reboots* the device.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
* Using a very small delay (e.g. 0 seconds) may cause a `ConnectionReset` or `TimedOut` error as the device reboots immediately.
|
||||||
|
* Using a larger delay (e.g. 2-3 seconds) allows the device to respond before rebooting, reducing the chance of errors.
|
||||||
|
* With larger delays, the method completes successfully before the device reboots.
|
||||||
|
However, subsequent commands may fail if sent during the reboot process or before the device reconnects to the network.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
delay_s (int): The delay in seconds before the device is rebooted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def device_reset(self) -> None:
|
||||||
|
"""*Hardware resets* the device.
|
||||||
|
|
||||||
|
Warning:
|
||||||
|
This action will reset the device to its factory settings.
|
||||||
|
The connection to the Wi-Fi network and the Tapo app will be lost,
|
||||||
|
and the device will need to be reconfigured.
|
||||||
|
|
||||||
|
This feature is especially useful when the device is difficult to access
|
||||||
|
and requires reconfiguration.
|
||||||
|
"""
|
||||||
238
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/discovery_result.pyi
Normal file
238
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/discovery_result.pyi
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Type, Union
|
||||||
|
|
||||||
|
from tapo import (
|
||||||
|
ColorLightHandler,
|
||||||
|
GenericDeviceHandler,
|
||||||
|
HubHandler,
|
||||||
|
LightHandler,
|
||||||
|
PlugEnergyMonitoringHandler,
|
||||||
|
PlugHandler,
|
||||||
|
PowerStripEnergyMonitoringHandler,
|
||||||
|
PowerStripHandler,
|
||||||
|
RgbicLightStripHandler,
|
||||||
|
RgbLightStripHandler,
|
||||||
|
)
|
||||||
|
from tapo.responses import (
|
||||||
|
DeviceInfoColorLightResult,
|
||||||
|
DeviceInfoGenericResult,
|
||||||
|
DeviceInfoHubResult,
|
||||||
|
DeviceInfoLightResult,
|
||||||
|
DeviceInfoPlugEnergyMonitoringResult,
|
||||||
|
DeviceInfoPlugResult,
|
||||||
|
DeviceInfoPowerStripResult,
|
||||||
|
DeviceInfoRgbicLightStripResult,
|
||||||
|
DeviceInfoRgbLightStripResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
class MaybeDiscoveryResult:
|
||||||
|
"""Potential result of the device discovery process. Using `get` will return the actual result or raise an exception."""
|
||||||
|
|
||||||
|
def get(
|
||||||
|
self,
|
||||||
|
) -> Union[
|
||||||
|
GenericDevice,
|
||||||
|
Light,
|
||||||
|
ColorLight,
|
||||||
|
RgbLightStrip,
|
||||||
|
RgbicLightStrip,
|
||||||
|
Plug,
|
||||||
|
PlugEnergyMonitoring,
|
||||||
|
PowerStrip,
|
||||||
|
PowerStripEnergyMonitoring,
|
||||||
|
Hub,
|
||||||
|
]:
|
||||||
|
"""Retrieves the actual discovery result or raises an exception."""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GenericDevice:
|
||||||
|
"""A Generic Tapo device.
|
||||||
|
|
||||||
|
If you believe this device is already supported, or would like to explore adding support for a currently
|
||||||
|
unsupported model, please [open an issue on GitHub](https://github.com/mihai-dinculescu/tapo/issues)
|
||||||
|
to start the discussion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
device_info: DeviceInfoGenericResult
|
||||||
|
"""Device info of a Generic Tapo device.
|
||||||
|
|
||||||
|
If you believe this device is already supported, or would like to explore adding support for a currently
|
||||||
|
unsupported model, please [open an issue on GitHub](https://github.com/mihai-dinculescu/tapo/issues)
|
||||||
|
to start the discussion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
handler: GenericDeviceHandler
|
||||||
|
"""Handler for generic devices. It provides the functionality common to all Tapo [devices](https://www.tapo.com/en/).
|
||||||
|
|
||||||
|
If you believe this device is already supported, or would like to explore adding support for a currently
|
||||||
|
unsupported model, please [open an issue on GitHub](https://github.com/mihai-dinculescu/tapo/issues)
|
||||||
|
to start the discussion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
"device_info",
|
||||||
|
"handler",
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Light:
|
||||||
|
"""Tapo L510, L520 and L610 devices."""
|
||||||
|
|
||||||
|
device_info: DeviceInfoLightResult
|
||||||
|
"""Device info of Tapo L510, L520 and L610."""
|
||||||
|
|
||||||
|
handler: LightHandler
|
||||||
|
"""Handler for the [L510](https://www.tapo.com/en/search/?q=L510),
|
||||||
|
[L520](https://www.tapo.com/en/search/?q=L520) and
|
||||||
|
[L610](https://www.tapo.com/en/search/?q=L610) devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
"device_info",
|
||||||
|
"handler",
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ColorLight:
|
||||||
|
"""Tapo L530, L535 and L630 devices."""
|
||||||
|
|
||||||
|
device_info: DeviceInfoColorLightResult
|
||||||
|
"""Device info of Tapo L530, L535 and L630."""
|
||||||
|
|
||||||
|
handler: ColorLightHandler
|
||||||
|
"""Handler for the [L530](https://www.tapo.com/en/search/?q=L530),
|
||||||
|
[L535](https://www.tapo.com/en/search/?q=L535) and
|
||||||
|
[L630](https://www.tapo.com/en/search/?q=L630) devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
"device_info",
|
||||||
|
"handler",
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RgbLightStrip:
|
||||||
|
"""Tapo L900 devices."""
|
||||||
|
|
||||||
|
device_info: DeviceInfoRgbLightStripResult
|
||||||
|
"""Device info of Tapo L900."""
|
||||||
|
|
||||||
|
handler: RgbLightStripHandler
|
||||||
|
"""Handler for the [L900](https://www.tapo.com/en/search/?q=L900) devices."""
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
"device_info",
|
||||||
|
"handler",
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RgbicLightStrip:
|
||||||
|
"""Tapo L920 and L930 devices."""
|
||||||
|
|
||||||
|
device_info: DeviceInfoRgbicLightStripResult
|
||||||
|
"""Device info of Tapo L920 and L930."""
|
||||||
|
|
||||||
|
handler: RgbicLightStripHandler
|
||||||
|
"""Handler for the [L920](https://www.tapo.com/en/search/?q=L920) and
|
||||||
|
[L930](https://www.tapo.com/en/search/?q=L930) devices."""
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
"device_info",
|
||||||
|
"handler",
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Plug:
|
||||||
|
"""Tapo P100 and P105 devices."""
|
||||||
|
|
||||||
|
device_info: DeviceInfoPlugResult
|
||||||
|
"""Device info of Tapo P100 and P105."""
|
||||||
|
|
||||||
|
handler: PlugHandler
|
||||||
|
"""Handler for the [P100](https://www.tapo.com/en/search/?q=P100) and
|
||||||
|
[P105](https://www.tapo.com/en/search/?q=P105) devices."""
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
"device_info",
|
||||||
|
"handler",
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlugEnergyMonitoring:
|
||||||
|
"""Tapo P110, P110M and P115 devices."""
|
||||||
|
|
||||||
|
device_info: DeviceInfoPlugEnergyMonitoringResult
|
||||||
|
"""Device info of Tapo P110, P110M and P115."""
|
||||||
|
|
||||||
|
handler: PlugEnergyMonitoringHandler
|
||||||
|
"""Handler for the [P110](https://www.tapo.com/en/search/?q=P110),
|
||||||
|
[P110M](https://www.tapo.com/en/search/?q=P110M) and
|
||||||
|
[P115](https://www.tapo.com/en/search/?q=P115) devices."""
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
"device_info",
|
||||||
|
"handler",
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PowerStrip:
|
||||||
|
"""Tapo P300 and P306 devices."""
|
||||||
|
|
||||||
|
device_info: DeviceInfoPowerStripResult
|
||||||
|
"""Device info of Tapo P300 and P306."""
|
||||||
|
|
||||||
|
handler: PowerStripHandler
|
||||||
|
"""Handler for the [P300](https://www.tp-link.com/en/search/?q=P300) and
|
||||||
|
[P306](https://www.tp-link.com/us/search/?q=P306) devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
"device_info",
|
||||||
|
"handler",
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PowerStripEnergyMonitoring:
|
||||||
|
"""Tapo P304M and P316M devices."""
|
||||||
|
|
||||||
|
device_info: DeviceInfoPowerStripResult
|
||||||
|
"""Device info of Tapo P304M and P316M."""
|
||||||
|
|
||||||
|
handler: PowerStripEnergyMonitoringHandler
|
||||||
|
"""Handler for the [P304M](https://www.tp-link.com/uk/search/?q=P304M) and
|
||||||
|
[P316M](https://www.tp-link.com/us/search/?q=P316M) devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
"device_info",
|
||||||
|
"handler",
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Hub:
|
||||||
|
"""Tapo H100 devices."""
|
||||||
|
|
||||||
|
device_info: DeviceInfoHubResult
|
||||||
|
"""Device info of Tapo H100."""
|
||||||
|
|
||||||
|
handler: HubHandler
|
||||||
|
"""Handler for the [H100](https://www.tapo.com/en/search/?q=H100) devices."""
|
||||||
|
|
||||||
|
__match_args__ = (
|
||||||
|
"device_info",
|
||||||
|
"handler",
|
||||||
|
)
|
||||||
|
|
||||||
|
class DiscoveryResult:
|
||||||
|
"""Result of the device discovery process."""
|
||||||
|
|
||||||
|
GenericDevice: Type[GenericDevice] = GenericDevice
|
||||||
|
Light: Type[Light] = Light
|
||||||
|
ColorLight: Type[ColorLight] = ColorLight
|
||||||
|
RgbLightStrip: Type[RgbLightStrip] = RgbLightStrip
|
||||||
|
RgbicLightStrip: Type[RgbicLightStrip] = RgbicLightStrip
|
||||||
|
Plug: Type[Plug] = Plug
|
||||||
|
PlugEnergyMonitoring: Type[PlugEnergyMonitoring] = PlugEnergyMonitoring
|
||||||
|
PowerStrip: Type[PowerStrip] = PowerStrip
|
||||||
|
PowerStripEnergyMonitoring: Type[PowerStripEnergyMonitoring] = PowerStripEnergyMonitoring
|
||||||
|
Hub: Type[Hub] = Hub
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
from tapo.responses import DeviceInfoGenericResult
|
||||||
|
|
||||||
|
class GenericDeviceHandler:
|
||||||
|
"""Handler for generic devices. It provides the functionality common to
|
||||||
|
all Tapo [devices](https://www.tapo.com/en/).
|
||||||
|
|
||||||
|
If you'd like to propose support for a device that isn't currently supported,
|
||||||
|
please [open an issue on GitHub](https://github.com/mihai-dinculescu/tapo/issues) to start the conversation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, handler: object):
|
||||||
|
"""Private constructor.
|
||||||
|
It should not be called from outside the tapo library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def refresh_session(self) -> None:
|
||||||
|
"""Refreshes the authentication session."""
|
||||||
|
|
||||||
|
async def on(self) -> None:
|
||||||
|
"""Turns *on* the device."""
|
||||||
|
|
||||||
|
async def off(self) -> None:
|
||||||
|
"""Turns *off* the device."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> DeviceInfoGenericResult:
|
||||||
|
"""Returns *device info* as `DeviceInfoGenericResult`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `GenericDeviceHandler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceInfoGenericResult: Device info of a Generic Tapo device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
295
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/hub_handler.pyi
Normal file
295
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/hub_handler.pyi
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
from tapo import KE100Handler, S200BHandler, T100Handler, T110Handler, T300Handler, T31XHandler
|
||||||
|
from tapo.device_management_ext import DeviceManagementExt
|
||||||
|
from tapo.requests import AlarmDuration, AlarmRingtone, AlarmVolume
|
||||||
|
from tapo.responses import (
|
||||||
|
DeviceInfoHubResult,
|
||||||
|
KE100Result,
|
||||||
|
S200BResult,
|
||||||
|
T100Result,
|
||||||
|
T110Result,
|
||||||
|
T300Result,
|
||||||
|
T31XResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
class HubHandler(DeviceManagementExt):
|
||||||
|
"""Handler for the [H100](https://www.tapo.com/en/search/?q=H100) devices."""
|
||||||
|
|
||||||
|
def __init__(self, handler: object):
|
||||||
|
"""Private constructor.
|
||||||
|
It should not be called from outside the tapo library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def refresh_session(self) -> None:
|
||||||
|
"""Refreshes the authentication session."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> DeviceInfoHubResult:
|
||||||
|
"""Returns *device info* as `DeviceInfoHubResult`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `HubHandler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceInfoHubResult: Device info of Tapo H100.
|
||||||
|
Superset of `GenericDeviceInfoResult`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_child_device_list(
|
||||||
|
self,
|
||||||
|
) -> List[
|
||||||
|
Union[KE100Result, S200BResult, T100Result, T110Result, T300Result, T31XResult, None]
|
||||||
|
]:
|
||||||
|
"""Returns *child device list* as `List[KE100Result | S200BResult | T100Result | T110Result | T300Result | T31XResult | None]`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API
|
||||||
|
or to support all the possible devices connected to the hub.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `HubHandler.get_child_device_list_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_child_device_list_json(self, start_index: int) -> dict:
|
||||||
|
"""Returns *child device list* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start_index (int): the index to start fetching the child device list.
|
||||||
|
It should be `0` for the first page, `10` for the second, and so on.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_child_device_component_list_json(self) -> dict:
|
||||||
|
"""Returns *child device component list* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_supported_ringtone_list() -> List[str]:
|
||||||
|
"""Returns a list of ringtones (alarm types) supported by the hub.
|
||||||
|
Used for debugging only.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[str]: List of the ringtones supported by the hub.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def play_alarm(
|
||||||
|
self,
|
||||||
|
ringtone: AlarmRingtone,
|
||||||
|
volume: AlarmVolume,
|
||||||
|
duration: AlarmDuration,
|
||||||
|
seconds: Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
|
"""Start playing the hub alarm.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ringtone (AlarmRingtone): The ringtone of a H100 alarm.
|
||||||
|
volume (AlarmVolume): The volume of the alarm.
|
||||||
|
duration (AlarmDuration): Controls how long the alarm plays for.
|
||||||
|
seconds (Optional[int]): Play the alarm a number of seconds. Required if `duration` is `AlarmDuration.Seconds`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def stop_alarm(self) -> None:
|
||||||
|
"""Stop playing the hub alarm, if it's currently playing."""
|
||||||
|
|
||||||
|
async def ke100(
|
||||||
|
self, device_id: Optional[str] = None, nickname: Optional[str] = None
|
||||||
|
) -> KE100Handler:
|
||||||
|
"""Returns a `KE100Handler` for the device matching the provided `device_id` or `nickname`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_id (Optional[str]): The Device ID of the device
|
||||||
|
nickname (Optional[str]): The Nickname of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
KE100Handler: Handler for the [KE100](https://www.tp-link.com/en/search/?q=KE100) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
# Connect to the hub
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
hub = await client.h100("192.168.1.100")
|
||||||
|
|
||||||
|
# Get a handler for the child device
|
||||||
|
device = await hub.ke100(device_id="0000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def s200b(
|
||||||
|
self, device_id: Optional[str] = None, nickname: Optional[str] = None
|
||||||
|
) -> S200BHandler:
|
||||||
|
"""Returns a `S200BHandler` for the device matching the provided `device_id` or `nickname`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_id (Optional[str]): The Device ID of the device
|
||||||
|
nickname (Optional[str]): The Nickname of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
S200BHandler: Handler for the [S200B](https://www.tapo.com/en/search/?q=S200B) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
# Connect to the hub
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
hub = await client.h100("192.168.1.100")
|
||||||
|
|
||||||
|
# Get a handler for the child device
|
||||||
|
device = await hub.s200b(device_id="0000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def t100(
|
||||||
|
self, device_id: Optional[str] = None, nickname: Optional[str] = None
|
||||||
|
) -> T100Handler:
|
||||||
|
"""Returns a `T100Handler` for the device matching the provided `device_id` or `nickname`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_id (Optional[str]): The Device ID of the device
|
||||||
|
nickname (Optional[str]): The Nickname of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
T100Handler: Handler for the [T100](https://www.tapo.com/en/search/?q=T100) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
# Connect to the hub
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
hub = await client.h100("192.168.1.100")
|
||||||
|
|
||||||
|
# Get a handler for the child device
|
||||||
|
device = await hub.t100(device_id="0000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def t110(
|
||||||
|
self, device_id: Optional[str] = None, nickname: Optional[str] = None
|
||||||
|
) -> T110Handler:
|
||||||
|
"""Returns a `T110Handler` for the device matching the provided `device_id` or `nickname`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_id (Optional[str]): The Device ID of the device
|
||||||
|
nickname (Optional[str]): The Nickname of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
T110Handler: Handler for the [T110](https://www.tapo.com/en/search/?q=T110) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
# Connect to the hub
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
hub = await client.h100("192.168.1.100")
|
||||||
|
|
||||||
|
# Get a handler for the child device
|
||||||
|
device = await hub.t110(device_id="0000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def t300(
|
||||||
|
self, device_id: Optional[str] = None, nickname: Optional[str] = None
|
||||||
|
) -> T300Handler:
|
||||||
|
"""Returns a `T300Handler` for the device matching the provided `device_id` or `nickname`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_id (Optional[str]): The Device ID of the device
|
||||||
|
nickname (Optional[str]): The Nickname of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
T300Handler: Handler for the [T300](https://www.tapo.com/en/search/?q=T300) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
# Connect to the hub
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
hub = await client.h100("192.168.1.100")
|
||||||
|
|
||||||
|
# Get a handler for the child device
|
||||||
|
device = await hub.t300(device_id="0000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def t310(
|
||||||
|
self, device_id: Optional[str] = None, nickname: Optional[str] = None
|
||||||
|
) -> T31XHandler:
|
||||||
|
"""Returns a `T31XHandler` for the device matching the provided `device_id` or `nickname`.
|
||||||
|
Args:
|
||||||
|
device_id (Optional[str]): The Device ID of the device
|
||||||
|
nickname (Optional[str]): The Nickname of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
T31XHandler: Handler for the [T310](https://www.tapo.com/en/search/?q=T310)
|
||||||
|
and [T315](https://www.tapo.com/en/search/?q=T315) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
# Connect to the hub
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
hub = await client.h100("192.168.1.100")
|
||||||
|
|
||||||
|
# Get a handler for the child device
|
||||||
|
device = await hub.t310(device_id="0000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def t315(
|
||||||
|
self, device_id: Optional[str] = None, nickname: Optional[str] = None
|
||||||
|
) -> T31XHandler:
|
||||||
|
"""Returns a `T31XHandler` for the device matching the provided `device_id` or `nickname`.
|
||||||
|
Args:
|
||||||
|
device_id (Optional[str]): The Device ID of the device
|
||||||
|
nickname (Optional[str]): The Nickname of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
T31XHandler: Handler for the [T310](https://www.tapo.com/en/search/?q=T310)
|
||||||
|
and [T315](https://www.tapo.com/en/search/?q=T315) devices.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
# Connect to the hub
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
hub = await client.h100("192.168.1.100")
|
||||||
|
|
||||||
|
# Get a handler for the child device
|
||||||
|
device = await hub.t315(device_id="0000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
69
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/ke100_handler.pyi
Normal file
69
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/ke100_handler.pyi
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
from tapo.requests import TemperatureUnitKE100
|
||||||
|
from tapo.responses import KE100Result
|
||||||
|
|
||||||
|
class KE100Handler:
|
||||||
|
"""Handler for the [KE100](https://www.tp-link.com/en/search/?q=KE100) devices."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> KE100Result:
|
||||||
|
"""Returns *device info* as `KE100Result`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `KE100Handler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
KE100Result: Device info of Tapo KE100 thermostatic radiator valve (TRV).
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_child_protection(self, on: bool) -> None:
|
||||||
|
"""Sets *child protection* on the device to *on* or *off*.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
on (bool)
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_frost_protection(self, on: bool) -> None:
|
||||||
|
"""Sets *frost protection* on the device to *on* or *off*.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
on (bool)
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_max_control_temperature(self, value: int, unit: TemperatureUnitKE100) -> None:
|
||||||
|
"""Sets the *maximum control temperature*.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (int)
|
||||||
|
unit (TemperatureUnitKE100)
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_min_control_temperature(self, value: int, unit: TemperatureUnitKE100) -> None:
|
||||||
|
"""Sets the *minimum control temperature*.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (int)
|
||||||
|
unit (TemperatureUnitKE100)
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_target_temperature(self, value: int, unit: TemperatureUnitKE100) -> None:
|
||||||
|
"""Sets the *target temperature*.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (int): between `min_control_temperature` and `max_control_temperature`
|
||||||
|
unit (TemperatureUnitKE100)
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_temperature_offset(self, value: int, unit: TemperatureUnitKE100) -> None:
|
||||||
|
"""Sets the *temperature offset*.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (int): between -10 and 10
|
||||||
|
unit (TemperatureUnitKE100)
|
||||||
|
"""
|
||||||
54
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/light_handler.pyi
Normal file
54
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/light_handler.pyi
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
from tapo.device_management_ext import DeviceManagementExt
|
||||||
|
from tapo.responses import DeviceInfoLightResult, DeviceUsageResult
|
||||||
|
|
||||||
|
class LightHandler(DeviceManagementExt):
|
||||||
|
"""Handler for the [L510](https://www.tapo.com/en/search/?q=L510),
|
||||||
|
[L520](https://www.tapo.com/en/search/?q=L520) and
|
||||||
|
[L610](https://www.tapo.com/en/search/?q=L610) devices."""
|
||||||
|
|
||||||
|
def __init__(self, handler: object):
|
||||||
|
"""Private constructor.
|
||||||
|
It should not be called from outside the tapo library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def refresh_session(self) -> None:
|
||||||
|
"""Refreshes the authentication session."""
|
||||||
|
|
||||||
|
async def on(self) -> None:
|
||||||
|
"""Turns *on* the device."""
|
||||||
|
|
||||||
|
async def off(self) -> None:
|
||||||
|
"""Turns *off* the device."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> DeviceInfoLightResult:
|
||||||
|
"""Returns *device info* as `DeviceInfoLightResult`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `LightHandler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceInfoLightResult: Device info of Tapo L510, L520 and L610.
|
||||||
|
Superset of `GenericDeviceInfoResult`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_usage(self) -> DeviceUsageResult:
|
||||||
|
"""Returns *device usage* as `DeviceUsageResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceUsageResult: Contains the time usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def set_brightness(self, brightness: int) -> None:
|
||||||
|
"""Sets the *brightness* and turns *on* the device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
brightness (int): between 1 and 100
|
||||||
|
"""
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from tapo.device_management_ext import DeviceManagementExt
|
||||||
|
from tapo.requests import EnergyDataInterval, PowerDataInterval
|
||||||
|
from tapo.responses import (
|
||||||
|
CurrentPowerResult,
|
||||||
|
DeviceInfoPlugEnergyMonitoringResult,
|
||||||
|
DeviceUsageEnergyMonitoringResult,
|
||||||
|
EnergyDataResult,
|
||||||
|
EnergyUsageResult,
|
||||||
|
PowerDataResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
class PlugEnergyMonitoringHandler(DeviceManagementExt):
|
||||||
|
"""Handler for the [P110](https://www.tapo.com/en/search/?q=P110),
|
||||||
|
[P110M](https://www.tapo.com/en/search/?q=P110M) and
|
||||||
|
[P115](https://www.tapo.com/en/search/?q=P115) devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, handler: object):
|
||||||
|
"""Private constructor.
|
||||||
|
It should not be called from outside the tapo library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def refresh_session(self) -> None:
|
||||||
|
"""Refreshes the authentication session."""
|
||||||
|
|
||||||
|
async def on(self) -> None:
|
||||||
|
"""Turns *on* the device."""
|
||||||
|
|
||||||
|
async def off(self) -> None:
|
||||||
|
"""Turns *off* the device."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> DeviceInfoPlugEnergyMonitoringResult:
|
||||||
|
"""Returns *device info* as `DeviceInfoPlugEnergyMonitoringResult`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `PlugEnergyMonitoringHandler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceInfoPlugEnergyMonitoringResult: Device info of P110, P110M and P115.
|
||||||
|
Superset of `GenericDeviceInfoResult`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_current_power(self) -> CurrentPowerResult:
|
||||||
|
"""Returns *current power* as `CurrentPowerResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CurrentPowerResult: Contains the current power reading of the device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_usage(self) -> DeviceUsageEnergyMonitoringResult:
|
||||||
|
"""Returns *device usage* as `DeviceUsageResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceUsageEnergyMonitoringResult:
|
||||||
|
Contains the time usage, the power consumption, and the energy savings of the device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_energy_usage(self) -> EnergyUsageResult:
|
||||||
|
"""Returns *energy usage* as `EnergyUsageResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EnergyUsageResult:
|
||||||
|
Contains local time, current power and the energy usage and runtime for today and for the current month.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_energy_data(
|
||||||
|
self,
|
||||||
|
interval: EnergyDataInterval,
|
||||||
|
start_date: datetime,
|
||||||
|
end_date: datetime = None,
|
||||||
|
) -> EnergyDataResult:
|
||||||
|
"""Returns *energy data* as `EnergyDataResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EnergyDataResult: Energy data result for the requested `EnergyDataInterval`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_power_data(
|
||||||
|
self,
|
||||||
|
interval: PowerDataInterval,
|
||||||
|
start_date_time: datetime,
|
||||||
|
end_date_time: datetime,
|
||||||
|
) -> PowerDataResult:
|
||||||
|
"""Returns *power data* as `PowerDataResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PowerDataResult: Power data result for the requested `PowerDataInterval`.
|
||||||
|
"""
|
||||||
47
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/plug_handler.pyi
Normal file
47
agents/tapo/tapo-fork/tapo-py/tapo-py/tapo/plug_handler.pyi
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from tapo.device_management_ext import DeviceManagementExt
|
||||||
|
from tapo.responses import DeviceInfoPlugResult, DeviceUsageResult
|
||||||
|
|
||||||
|
class PlugHandler(DeviceManagementExt):
|
||||||
|
"""Handler for the [P100](https://www.tapo.com/en/search/?q=P100) and
|
||||||
|
[P105](https://www.tapo.com/en/search/?q=P105) devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, handler: object):
|
||||||
|
"""Private constructor.
|
||||||
|
It should not be called from outside the tapo library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def refresh_session(self) -> None:
|
||||||
|
"""Refreshes the authentication session."""
|
||||||
|
|
||||||
|
async def on(self) -> None:
|
||||||
|
"""Turns *on* the device."""
|
||||||
|
|
||||||
|
async def off(self) -> None:
|
||||||
|
"""Turns *off* the device."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> DeviceInfoPlugResult:
|
||||||
|
"""Returns *device info* as `DeviceInfoPlugResult`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `PlugHandler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceInfoPlugResult: Device info of Tapo P100 and P105.
|
||||||
|
Superset of `GenericDeviceInfoResult`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_usage(self) -> DeviceUsageResult:
|
||||||
|
"""Returns *device usage* as `DeviceUsageResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceUsageResult: Contains the time usage.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from tapo import PowerStripPlugEnergyMonitoringHandler
|
||||||
|
from tapo.device_management_ext import DeviceManagementExt
|
||||||
|
from tapo.responses import DeviceInfoPowerStripResult, PowerStripPlugEnergyMonitoringResult
|
||||||
|
|
||||||
|
class PowerStripEnergyMonitoringHandler(DeviceManagementExt):
|
||||||
|
"""Handler for the [P304M](https://www.tp-link.com/uk/search/?q=P304M) and
|
||||||
|
[P316M](https://www.tp-link.com/us/search/?q=P316M) devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, handler: object):
|
||||||
|
"""Private constructor.
|
||||||
|
It should not be called from outside the tapo library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def refresh_session(self) -> None:
|
||||||
|
"""Refreshes the authentication session."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> DeviceInfoPowerStripResult:
|
||||||
|
"""Returns *device info* as `DeviceInfoPowerStripResult`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `PowerStripEnergyMonitoringHandler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceInfoPowerStripResult: Device info of Tapo P300, P304M, P306 and P316M. Superset of `DeviceInfoGenericResult`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_child_device_list(
|
||||||
|
self,
|
||||||
|
) -> List[PowerStripPlugEnergyMonitoringResult]:
|
||||||
|
"""Returns *child device list* as `List[PowerStripPlugEnergyMonitoringResult]`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `PowerStripEnergyMonitoringHandler.get_child_device_list_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_child_device_list_json(self) -> dict:
|
||||||
|
"""Returns *child device list* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_child_device_component_list_json(self) -> dict:
|
||||||
|
"""Returns *child device component list* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def plug(
|
||||||
|
self,
|
||||||
|
device_id: Optional[str] = None,
|
||||||
|
nickname: Optional[str] = None,
|
||||||
|
position: Optional[int] = None,
|
||||||
|
) -> PowerStripPlugEnergyMonitoringHandler:
|
||||||
|
"""Returns a `PowerStripPlugEnergyMonitoringHandler` for the device matching the provided `device_id`, `nickname`, or `position`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_id (Optional[str]): The Device ID of the device
|
||||||
|
nickname (Optional[str]): The Nickname of the device
|
||||||
|
position (Optional[str]): The Position of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PowerStripPlugEnergyMonitoringHandler: Handler for the [P304M](https://www.tp-link.com/uk/search/?q=P304M) and
|
||||||
|
[P316M](https://www.tp-link.com/us/search/?q=P316M) child plugs.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
# Connect to the hub
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
power_strip = await client.p304("192.168.1.100")
|
||||||
|
|
||||||
|
# Get a handler for the child device
|
||||||
|
device = await power_strip.plug(device_id="0000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from tapo import PowerStripPlugHandler
|
||||||
|
from tapo.device_management_ext import DeviceManagementExt
|
||||||
|
from tapo.responses import DeviceInfoPowerStripResult, PowerStripPlugResult
|
||||||
|
|
||||||
|
class PowerStripHandler(DeviceManagementExt):
|
||||||
|
"""Handler for the [P300](https://www.tp-link.com/en/search/?q=P300) and
|
||||||
|
[P306](https://www.tp-link.com/us/search/?q=P306) devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, handler: object):
|
||||||
|
"""Private constructor.
|
||||||
|
It should not be called from outside the tapo library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def refresh_session(self) -> None:
|
||||||
|
"""Refreshes the authentication session."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> DeviceInfoPowerStripResult:
|
||||||
|
"""Returns *device info* as `DeviceInfoPowerStripResult`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `PowerStripHandler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceInfoPowerStripResult: Device info of Tapo P300, P304M, P306 and P316M. Superset of `DeviceInfoGenericResult`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_child_device_list(
|
||||||
|
self,
|
||||||
|
) -> List[PowerStripPlugResult]:
|
||||||
|
"""Returns *child device list* as `List[PowerStripPlugResult]`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `PowerStripHandler.get_child_device_list_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_child_device_list_json(self) -> dict:
|
||||||
|
"""Returns *child device list* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_child_device_component_list_json(self) -> dict:
|
||||||
|
"""Returns *child device component list* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def plug(
|
||||||
|
self,
|
||||||
|
device_id: Optional[str] = None,
|
||||||
|
nickname: Optional[str] = None,
|
||||||
|
position: Optional[int] = None,
|
||||||
|
) -> PowerStripPlugHandler:
|
||||||
|
"""Returns a `PowerStripPlugHandler` for the device matching the provided `device_id`, `nickname`, or `position`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_id (Optional[str]): The Device ID of the device
|
||||||
|
nickname (Optional[str]): The Nickname of the device
|
||||||
|
position (Optional[str]): The Position of the device
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PowerStripPlugHandler: Handler for the [P300](https://www.tp-link.com/en/search/?q=P300) and
|
||||||
|
[P306](https://www.tp-link.com/us/search/?q=P306) child plugs.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
# Connect to the hub
|
||||||
|
client = ApiClient("tapo-username@example.com", "tapo-password")
|
||||||
|
power_strip = await client.p300("192.168.1.100")
|
||||||
|
|
||||||
|
# Get a handler for the child device
|
||||||
|
device = await power_strip.plug(device_id="0000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
# Get the device info of the child device
|
||||||
|
device_info = await device.get_device_info()
|
||||||
|
print(f"Device info: {device_info.to_dict()}")
|
||||||
|
```
|
||||||
|
"""
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from tapo.requests import EnergyDataInterval, PowerDataInterval
|
||||||
|
from tapo.responses import (
|
||||||
|
CurrentPowerResult,
|
||||||
|
DeviceUsageEnergyMonitoringResult,
|
||||||
|
EnergyDataResult,
|
||||||
|
EnergyUsageResult,
|
||||||
|
PowerDataResult,
|
||||||
|
PowerStripPlugEnergyMonitoringResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
class PowerStripPlugEnergyMonitoringHandler:
|
||||||
|
"""Handler for the [P304M](https://www.tp-link.com/uk/search/?q=P304M) and
|
||||||
|
[P316M](https://www.tp-link.com/us/search/?q=P316M) child plugs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, handler: object):
|
||||||
|
"""Private constructor.
|
||||||
|
It should not be called from outside the tapo library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def on(self) -> None:
|
||||||
|
"""Turns *on* the device."""
|
||||||
|
|
||||||
|
async def off(self) -> None:
|
||||||
|
"""Turns *off* the device."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> PowerStripPlugEnergyMonitoringResult:
|
||||||
|
"""Returns *device info* as `PowerStripPlugEnergyMonitoringResult`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `PowerStripPlugEnergyMonitoringHandler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PowerStripPlugEnergyMonitoringResult: P304M and P316M power strip child plugs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_current_power(self) -> CurrentPowerResult:
|
||||||
|
"""Returns *current power* as `CurrentPowerResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CurrentPowerResult: Contains the current power reading of the device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_usage(self) -> DeviceUsageEnergyMonitoringResult:
|
||||||
|
"""Returns *device usage* as `DeviceUsageResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeviceUsageEnergyMonitoringResult:
|
||||||
|
Contains the time usage, the power consumption, and the energy savings of the device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_energy_usage(self) -> EnergyUsageResult:
|
||||||
|
"""Returns *energy usage* as `EnergyUsageResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EnergyUsageResult:
|
||||||
|
Contains local time, current power and the energy usage and runtime for today and for the current month.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_energy_data(
|
||||||
|
self,
|
||||||
|
interval: EnergyDataInterval,
|
||||||
|
start_date: datetime,
|
||||||
|
end_date: datetime = None,
|
||||||
|
) -> EnergyDataResult:
|
||||||
|
"""Returns *energy data* as `EnergyDataResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EnergyDataResult: Energy data result for the requested `EnergyDataInterval`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_power_data(
|
||||||
|
self,
|
||||||
|
interval: PowerDataInterval,
|
||||||
|
start_date_time: datetime,
|
||||||
|
end_date_time: datetime,
|
||||||
|
) -> PowerDataResult:
|
||||||
|
"""Returns *power data* as `PowerDataResult`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PowerDataResult: Power data result for the requested `PowerDataInterval`.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
from tapo.responses import PowerStripPlugResult
|
||||||
|
|
||||||
|
class PowerStripPlugHandler:
|
||||||
|
"""Handler for the [P300](https://www.tp-link.com/en/search/?q=P300) and
|
||||||
|
[P306](https://www.tp-link.com/us/search/?q=P306) child plugs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, handler: object):
|
||||||
|
"""Private constructor.
|
||||||
|
It should not be called from outside the tapo library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def on(self) -> None:
|
||||||
|
"""Turns *on* the device."""
|
||||||
|
|
||||||
|
async def off(self) -> None:
|
||||||
|
"""Turns *off* the device."""
|
||||||
|
|
||||||
|
async def get_device_info(self) -> PowerStripPlugResult:
|
||||||
|
"""Returns *device info* as `PowerStripPlugResult`.
|
||||||
|
It is not guaranteed to contain all the properties returned from the Tapo API.
|
||||||
|
If the deserialization fails, or if a property that you care about it's not present,
|
||||||
|
try `PowerStripPlugHandler.get_device_info_json`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PowerStripPlugResult: P300 and P306 power strip child plugs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_device_info_json(self) -> dict:
|
||||||
|
"""Returns *device info* as json.
|
||||||
|
It contains all the properties returned from the Tapo API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Device info as a dictionary.
|
||||||
|
"""
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user