2024年11月16日土曜日

Tuya スマートプラグを PowerShell で制御する

前回ブラウザからスマートプラグを制御したとき,URL の情報が見えていたので,後は HTTP リクエストを送れさえすれば制御は余裕と思っていたらそうではなかった.
リクエスト送信前に署名鍵を Tuya から取得し,その鍵で送信するリクエストデータの署名を作成し送信しないとだめっぽい.

で探してみたら,すでに Unix shell から制御するコードはあったので,ありがたくこれを PowerShell に変換してみたのが一番下のコード.

ハマったポイントは,

  • UNIX Time を秒数で得る命令 (Get-Date -UFormat %s) はタイムゾーン分の誤差が発生する (PowerShell のバグっぽい) ので,タイムゾーンオフセット分増減が必要.
  • 署名を作るために必要な Client Secret (Tuya developer サイトから取得する) は 16進32桁なので,128bit の数値だと思ったら,これはそのまま文字列データとして 256bit の数値として扱わないと,Tuya 側で有効な署名と認められなかった

でやった結果.Powershell で 10秒毎に消費電力を取得してみた.なかなか安価で消費電力のログ取れる環境は無いので,これはかなり満足度が高いヽ(´ー`)ノ

以下 PowerShell コード.

##############################################################################
# 定数設定
$DeviceID = "YOUR_DEVICE_ID_00000"
$ClientID = "YOUR_CLIENT_ID_00000"
$ClientSecret = "YOUR_CLIENT_SECRET_0000000000000"
$BaseUrl = "https://openapi.tuyaus.com"
##############################################################################
$ClientSecret = ([System.Text.Encoding]::ASCII.GetBytes($ClientSecret))
##############################################################################
# SHA256-HMAC 計算
function Get-HMACSHA256 {
param(
[Parameter(Mandatory=$true)]
[byte[]]$Key,
[Parameter(Mandatory=$true)]
[String]$Data
)
[byte []] $Data = ([System.Text.Encoding]::UTF8.GetBytes($Data))
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = $Key
$hash = $hmac.ComputeHash($Data)
$hmac.Dispose()
return [System.BitConverter]::ToString($hash).Replace("-", "")
}
function Get-SHA256 {
param(
[Parameter(Mandatory=$true)]
[String]$Data
)
[byte []]$Data = [System.Text.Encoding]::UTF8.GetBytes($Data)
$sha256 = New-Object System.Security.Cryptography.SHA256Managed
$hashBytes = $sha256.ComputeHash($Data)
# ハッシュ値を16進数文字列に変換
return [System.BitConverter]::ToString($hashBytes).Replace("-", "").ToLower()
}
##############################################################################
function InvokeCommand {
param(
[Parameter(Mandatory=$true)] [String]$URL,
$Command,
[String]$AccessToken = ''
)
# Signature 計算
$Hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
$Method = 'GET'
if($PSBoundParameters.ContainsKey('Command')){
$Body = ConvertTo-Json $Command
$Hash = Get-SHA256 -Data $Body
$Method = "POST"
}
$StringToSign = "$ClientID$AccessToken$TimeStamp$Method`n$Hash`n`n$URL"
$Sign = Get-HMACSHA256 -Key $ClientSecret -Data $StringToSign
# ヘッダ生成
$Headers = @{
"sign_method" = "HMAC-SHA256"
"client_id" = $ClientID
"t" = $TimeStamp
"mode" = "cors"
"Content-Type" = "application/json"
"sign" = $Sign
}
if($AccessToken -ne ''){
$Headers.access_token = $AccessToken
}
# HTTP リクエスト送信
try{
if($PSBoundParameters.ContainsKey('Command')){
$Response = Invoke-WebRequest -Uri "$BaseUrl$URL" -Headers $Headers -Method $Method -Body $Body
}else{
$Response = Invoke-WebRequest -Uri "$BaseUrl$URL" -Headers $Headers -Method $Method
}
}catch{
throw $_
}
# レスポンス解析
$Content = ConvertFrom-Json $Response.Content
if(!$Content.success){
throw "Tyua API access failed: $($Content.msg)"
}
$Content
}
##############################################################################
# Tyua API
function TuyaGetToken {
$script:TimeStamp = ((Get-Date -UFormat %s) - (Get-TimeZone).BaseUtcOffset.TotalSeconds) -replace "\..*", "000"
$resp = InvokeCommand -URL "/v1.0/token?grant_type=1"
$resp.result.access_token
}
function TuyaInvokeCommand {
param(
[Parameter(Mandatory=$true)] [String][String]$AccessToken,
[Parameter(Mandatory=$true)] $Commands
)
(InvokeCommand -URL "/v1.0/iot-03/devices/$DeviceID/commands" -AccessToken $AccessToken -Command $Commands).result
}
function TuyaGetStatus {
param(
[Parameter(Mandatory=$true)] [String][String]$AccessToken
)
$Result = @{}
foreach($elem in (InvokeCommand -URL "/v1.0/iot-03/devices/$DeviceID/status" -AccessToken $Token).result){
$Result[$elem.code] = $elem.value
}
$Result
}
##############################################################################
$ProgressPreference = "SilentlyContinue"
# コンセント1 を On, コンセント2 を Off にする
$Commands = @{
commands = @(
@{
code = "switch_1"
value = $True
},
@{
code = "switch_2"
value = $False
}
)
}
TuyaInvokeCommand -AccessToken $Token -Commands $Commands
# 10秒ごとに消費電力を表示
while($True){
$Token = TuyaGetToken
(TuyaGetStatus -AccessToken $Token).cur_power / 10
Start-Sleep 10
}
view raw smartplug.ps1 hosted with ❤ by GitHub

2024年11月10日日曜日

Tuya スマートプラグを PC から制御する

大昔にスマートプラグ (ネットワーク経由で On/Off できるコンセント) を買ったけど,独自の Android アプリでしか On/Off できなかったので,あまり使い道がなく放置していた.
で,最近これは Tuya という IoT プラットフォームに準拠した製品であることがわかったので,PC から制御してみた.
ところが Tuya のチュートリアル通りにやっても画面が説明と異なる等多々あり進まなかったので,自分でうまく行った手順を以下に記載しておく.

【開発者アカウント作成・デバイスのリンク】

●まずはスマートプラグと,Android の Smart Life アプリとの連携を済ませておく.

Tuya developer でアカウント作成

●Cloud → Create Cloud Project をクリック

●Create Cloud Project 画面で
  • Project Name: てきとう
  • Industry: Smart Home
  • Development: Smart Home
  • Data Center: データが保存されるサーバの場所? よくわからないがとりあえず Western America Datacenter
にして Create クリック

●Configuration Wizard はそのままで Authorize をクリック

●Devices → Link App Accout → Add App Account をクリックすると,QR コードが表示される

●Android の Smart Life アプリ右上の「+」 → QR コードをスキャン,をタップ後,PC の QR コードを読み込む

●Android アプリの「ログインを確認」をタップ

●以下のような画面が出てくるので,そのまま OK.これで Tuya developer アカウントとデバイスがリンクできた.

【デバイスを PC から制御する】

●どんな操作ができるか (コマンドがあるか) 調べてみる.
ここで表示されている Device ID が後々必要なので控えておき,Devices → All Devices → Debug Device をクリック

●Device Debugging をクリック.
Standard Instruction Set を見ると,使用できる設定系のコマンドがわかる.うちのだと,switch_1 / switch_2 で 2個あるコンセントの On/Off, countdown_n は秒数を設定するとその時間経過後に On/Off を反転する.

Standard Status Set を見ると,状態取得系のプロパティがわかる.うちのだとコンセントの On/Off 状態や,現在の電力が取得できる.

●Cloud → API Explorer をクリック

●Device Control の左の▶をクリック,Send Commands をクリック.

  • device_id: 先程調べた Device ID
  • code: switch_1 など
  • value: true, false など
で Submit Request をクリックすると,スマートプラグが On/Off される.
ここで,Request URL / Response をみると,どういうリクエストを送ればいいかが一発でわかるので,後は好きな言語で好きなように制御できる.もう少し手順が必要

●Get the status of a single device を実行すると,上の Standard Status Set で調べたプロパティが取得できる.

【最後に】

このスマートプラグ,電力計が付いていたので買ったものだが,電力見るのも専用アプリだけでログも取れないので放置していたが,これからは PC でログ取りできるので,かなり使えるアイテムになったヽ(´ー`)ノ