Management of Yandex smart home devices with your own scripts

Management of Yandex smart home devices with your own scripts

The article will talk about the basic management of Yandex smart home devices – namely, the on/off functions, because I don’t need anything else, but from the base it is quite easy with the help of documentation, to learn how to manage and other properties. Maybe this material is not for the article at all, but I wanted to share it. Do not scold too much for the code – I am not a real programmer. The code will be written in the simplest PHP.

AI: Control smart home devices with your own PHP scripts

Why?

I wanted to somehow configure one scenario that cannot be done through Alice. And what to do in such situations? Of course, you need to write your own scripts.

If anyone is interested – the function of turning off the heater during the peak electricity tariff when using a three-tariff meter and turning it back on after the end of the peak tariff, but only if it was turned on before that.

Yes, there are things like Home Assistant in the world, but so far I’m too lazy to install it, and somehow managed without it. So far I have not realized why he is for me. And it seems that such a scenario cannot be done there anyway. It is important to turn on only when it was turned on. If the device was not turned on before the peak time, then it is not necessary to turn it back on after the end of the peak time.

Finding solutions

I decided to start with the Yandex API. Fortunately, there are no problems with this, it was found quite quickly:

https://yandex.ru/dev/dialogs/smart-home/doc/concepts/platform-quickstart.html

However, the documentation is rather sparse on working examples.

A good thing is to try to find something ready on Github or somewhere before writing something of your own. I’ll be honest, I didn’t search very deeply, but I mainly came across options when, on the contrary, you need to screw your “smart home” to Alice so that you can control it with commands from Alice.

Registration

As the documentation says, you need to register in oauth at the link https://oauth.yandex.ru/client/new/

An important wonder on which I wasted several hours. You need to register immediately using the link, and not try to click on the “create program” button in the interface – for some reason it tries to create a program in the context of “id”, in the url you can see another link https://oauth.yandex.ru/client /new/ id and in this case it is not possible to set the necessary rights for the future program.

Fill in the data:

Register oauth Yandex

Any name.

Tick ​​- web services.

Redirect URI – the link to which redirection will take place after successful authorization. In my case, I use a specially prepared web page on my server, but for starters you can use http://oauth.yandex.ru/verification_code – this URL will simply display the code on the page when needed.

Access to data – according to the documentation: iot:view, iot:control

Basically, you need to authorize once for the scripts to work, the token is given for a year, then it seems like you can renew it automatically, I wrote a function for this, but I haven’t had a chance to check it yet.

We create an application, go to it:

Let’s pay attention to ClientID and Client secret – they will come in handy. You can not record anywhere – they are always available on the program page.

Obtaining a token

From the console it can be done like this

$ curl "https://oauth.yandex.ru/authorize?response_type=code&client_id=<client_id>&force_confirm=no&scope=iot:view%20iot:control"
Found. Redirecting to https://passport.yandex.ru/auth?retpath=https%3A%2F%2Foauth.yandex.ru%2Fauthorize%3Fresponse_type%3Dcode%26client_id%3D%253Cclient_id%253E%26force_confirm%3Dno%26scope%3Diot%3Aview%2520iot%3Acontrol&noreturn=1&origin=oauth

Next, we go in the browser:

https://passport.yandex.ru/auth?retpath=https%3A%2F%2Foauth.yandex.ru%2Fauthorize%3Fresponse_type%3Dcode%26client_id%3D%253Cclient_id%253E%26force_confirm%3Dno%2 2520iot%3Acontrol&noreturn=1&origin =oauth

We authenticate and receive a code – you will see the code after redirecting to the page http://oauth.yandex.ru/verification_code, if you do not have a more automated authorization page ready yet.

Next, with this code, we get the token itself:

curl -H "Authorization: Basic $(echo -n "<ClientID>:<Client secret>"|base64 -w 0)" -d "grant_type=authorization_code&code=<code>" "https://oauth.yandex.ru/token"

In response, you will receive a JSON with the token and some required parameters:

{
  "token_type": "bearer",
  "access_token": "AQAAAACy1C6ZAAAAfa6vDLuItEy8pg-iIpnDxIs",
  "expires_in": 124234123534,
  "refresh_token": "1:GN686QVt0mmakDd9:A4pYuW9LGk0_UnlrMIWklkAuJkUWbq27loFekJVmSYrdfzdePBy7:A-2dHOmBxiXgajnD-kYOwQ"
}

token_type – I have not seen another

access_token is the same Oauth token

expires_in – the time in seconds until it expires

refresh_token – a token with which you can refresh the main token and, in particular, the cooling time.

My way to get a token in PHP. This file (for example iot_yandex_auth.php) should be (optionally) placed on your web server. You can, probably, not do this and get by with authorization by a deaf person. And apparently, the function of updating the token should work according to the idea.

Code iot_yandex_auth.php
<?php
$client_id="<client_id>";
$client_secret="<client_secret>";
$file="private_folder_kae6iev_taex/iot_yandex_token.json";

function universal_curl($url,$headers,$data = "",$method = ""){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    if($method!=""){
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    }
    if($data!=""){
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    }
    curl_setopt($ch, CURLOPT_FAILONERROR, 0);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_VERBOSE, 0);
    curl_setopt($ch, CURLOPT_TIMEOUT, 5);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    $result = curl_exec($ch);
    $result = json_decode($result,true);
    curl_close($ch);
    return $result;
}


if(isset($_GET["code"])&&is_numeric($_GET["code"])){
    $auth=base64_encode($client_id.":".$client_secret);
    $headers=["Authorization: Basic ".$auth];
    $url="https://oauth.yandex.ru/token";
    $data="grant_type=authorization_code&code=".$_GET["code"];
    $response=universal_curl($url,$headers,$data);
    if(isset($response["access_token"])){
        $response["expires"]=$response["expires_in"]+time();
        if(file_put_contents($file, json_encode($response))){
            echo "Success";
        }
        exit;
    }
}

$url="https://oauth.yandex.ru/authorize?response_type=code&client_id=".$client_id."&force_confirm=yes&scope=iot:view%20iot:control";
header('Location: '.$url);

go to https://ownsite.ru/iot_yandex_auth.php

If there is no code in the GET request, the page redirects to the right place, if there is, it goes to Yandex, receives the token and puts it in a folder on the server.

Please note that $client_id and $client_secret must be filled in, and $file in the current configuration will point to the folder in / of your site. In this case, the file will be available from all over the Internet, it would be good to cover the location or somehow make it more beautiful. You can and should put it somewhere higher so that the location is not accessible from the Internet in principle.

It may not be obvious yet, but it is necessary that the rights allow writing to $ file to your web server and the file will not be able to be created – you will see an error about this in the errors of your web server.

Even when writing JSON to a file, I add the expires parameter there, because it is not obvious to me what else to do with expires_in and how to understand exactly when the token will expire.

Also, this location should be available for scripts that will already directly control your smart home.

Management

Everything is ready, you can start driving.

We create a function file, for example functions.php – somewhere else, for example, in /opt/scripts/smart_home

Do not forget to change $yandex_iot_client_id, $yandex_iot_client_secret and $yandex_iot_file to current ones.

Functions.php code
<?php
$yandex_iot_client_id="<client_id>";
$yandex_iot_client_secret="<client_secret>";
$yandex_iot_file="/var/www/ownsite.ru/private_folder_kae6iev_taex/iot_yandex_token.json";

function universal_curl($url,$headers,$data = "",$method = ""){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    if($method!=""){
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    }
    if($data!=""){
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    }
    curl_setopt($ch, CURLOPT_FAILONERROR, 0);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_VERBOSE, 0);
    curl_setopt($ch, CURLOPT_TIMEOUT, 5);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    $result = curl_exec($ch);
    $result = json_decode($result,true);
    curl_close($ch);
    return $result;
}

function universal_yandex_iot($function,$headers_add = array(),$data = "",$method = ""){
    $iot_api_url="https://api.iot.yandex.net";
    $url=$iot_api_url.$function;
    $headers=array_merge(array("Authorization: Bearer ".get_yandex_iot_token()),$headers_add);
    return(universal_curl($url,$headers,$data,$method));
}

function get_yandex_iot_power_state($device_id){
    $function="/v1.0/devices/".$device_id;
    $result=universal_yandex_iot($function);
    if(isset($result["capabilities"])){
        foreach($result["capabilities"] as $capability){
            if($capability["type"]=="devices.capabilities.on_off"){
                if($capability["state"]["value"]==""){
                    return(0);
                }elseif($capability["state"]["value"]=="1"){
                    return(1);
                }else{
                    return(-1);
                }
            }
        }
    }else{
        return(-1);
    }
}

function get_yandex_iot_info(){
    $function="/v1.0/user/info";
    $result=universal_yandex_iot($function);
    return($result);
}

function yandex_iot_power_change($device_id,$state){
    $function="/v1.0/devices/actions";
    $headers=["Content-Type: application/json"];
    $data="{"devices":[{"id":"".$device_id.'","actions":[{"type":"devices.capabilities.on_off","state":{"instance":"on","value":'.$state.'}}]}]}';
    $result=universal_yandex_iot($function,$headers,$data);
    if(isset($result["status"])&&$result["status"]=="ok"){
        return(true);
    }else{
        return(false);
    }
}

function get_yandex_iot_token(){
    global $yandex_iot_file;
    $token=file_get_contents($yandex_iot_file);
    $token_arr=json_decode($token,true);
    if($token_arr["expires"]-time()<20995200){
        update_yandex_iot_token();
    }
    return($token_arr["access_token"]);
}

function update_yandex_iot_token(){
    global $yandex_iot_file,$yandex_iot_client_id,$yandex_iot_client_secret;
    $token=file_get_contents($yandex_iot_file);
    $token_arr=json_decode($token,true);
    $url="https://oauth.yandex.ru/token";
    $auth=base64_encode($yandex_iot_client_id.":".$yandex_iot_client_secret);
    $headers=["Authorization: Basic ".$auth,"application/x-www-form-urlencoded"];
    $data="grant_type=refresh_token&refresh_token=".$token_arr["refresh_token"];
    $response=universal_curl($url,$headers,$data);
    if(isset($response["access_token"])){
        $response["expires"]=$response["expires_in"]+time();
        if(file_put_contents($yandex_iot_file, json_encode($response))){
            return(true);
        }
    }else{
        return(false);
    }
}
?>

universal_curl – universal curl

universal_yandex_iot – adaptation of universal curl for Yandex

get_yandex_iot_power_state – getting the power state

get_yandex_iot_info – receives all information about the smart home, all capabilities, device_id, etc.

yandex_iot_power_change – power state change

get_yandex_iot_token – gets the token from the file and updates (at least tries) if the lifetime of the token is less than 8 months.

update_yandex_iot_token – token update (attempt). Here, too, when writing JSON to a file, I add the expires parameter there, because it is not obvious to me what else to do with expires_in and how to understand exactly when the token will expire.

We create a test file, for example, yandex_test.php.

<?php
require_once('functions.php');

//print_r(get_yandex_iot_info());
//exit;
echo get_yandex_iot_power_state("<device_id>")."\n";
echo yandex_iot_power_change("<device_id>","false")."\n";
sleep(1);
echo get_yandex_iot_power_state("<device_id>")."\n";
echo yandex_iot_power_change("<device_id>","true")."\n";
?>

We turn something on and off, we are glad that it works.

In addition, you can control not only food, but also other parameters by studying the documentation https://yandex.ru/dev/dialogs/smart-home/doc/concepts/platform-protocol.html. For now, I only need the function of power management, but having a base – it is not difficult to adjust the rest.

Related posts