We’re transferring data from the NEW het.uz account to your smart home.

Previously, I wrote an article about how to parse the balance from an electricity consumer’s personal account. Anyone who used the previous version of the parser (and the account itself) probably noticed that parsing was spotty and incredibly slow. And now, like a bolt from the blue, HET is finally changing that piece of dried-up sh… ahem… anyway! They have a new account. If the developers of this new account are reading this article, salute you and my respects! And for everyone else, I hasten to share the good news: given the way this new account is built, there’s no need to parse anything anymore! Because now you can easily retrieve data directly from the API, which the new account itself works with. With a simple press of F12, you can track what, how, and where the JS application is fetching. The data, by the way, updates quite quickly. For example, a payment made to your account appears in your account almost instantly. But as beautiful and convenient as this office is, I need a little more. This time, I decided to make sure the data would immediately enter the smart home’s circulatory system—that is, directly into MQTT.

First, we’ll need an MQTT client library for PHP. I picked the first one I found here: https://github.com/php-mqtt/client , and it worked just fine.

We run into the folder where our script will be located and install the library

 composer require php-mqtt/client 

Next, create a startup file that will contain credentials and some settings. You can name it whatever you like.

 <pre class="wp-block-syntaxhighlighter-code"><?php
	define('HET_LOGIN',"номер счёта");
	define('HET_PASS',"Пароль");
	
	$server   = 'хост MQTT брокера';
	$port     = 1883;
	$clientId = 'het-informer';
	$mqttLogin = "логин от mqtt";
	$mqttPass = "пароль от mqtt";
	// $balancefile = "dom.txt"; 
        // Раскомментируй строку выше, если хочешь кешировать данные о балансе, чтобы можно было забирать их как статику. В моём случае это нужно для работы навыка Алисы.
	require_once ('het.php');</pre> 

The next file is called het.php

 <pre class="wp-block-syntaxhighlighter-code"><?php
	if(!defined('HET_LOGIN')){
		die();
	}

	require_once 'vendor/autoload.php'; //Подгружаем библиотеку mqtt
	
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); 
	curl_setopt($ch, CURLOPT_TIMEOUT, 5);
	curl_setopt($ch, CURLOPT_URL, 'https://cabinet-api.het.uz/household-consumer/v1/mobile-cabinet/user-login'); // Тут у нас адрес для авторизации. Мы должны отправить туда json массив с учётными данными.
	curl_setopt($ch, CURLOPT_USERAGENT,'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36'); // Прикидываемся браузером, чтобы не спровоцировать какой-нибудь фильтр. Раньше у них такой стоял...
	curl_setopt($ch, CURLOPT_POST, true); // отправлять будем методом POST
	curl_setopt($ch, CURLOPT_POSTFIELDS, '{"login":"'.HET_LOGIN.'", "password":"'.HET_PASS.'"}'); // Собственно, массив с учётными данными
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_HTTP09_ALLOWED, true);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
	$headers = array(
	   "Connection: keep-alive",
	   "Keep-Alive: timeout=5, max=100",
	   "Content-Type: application/json", // вот тут обязательно указываем тип данных, иначе API пошлёт нас нафиг.
	);
	curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
	$authfile = '/tmp/el'.HET_LOGIN.'.json'; // тут мы создаём файл, куда будем прятать токен авторизации. Попытки постоянных повторных автоиризаций во время тестирования завели меня во временный бан. Больше так не делаем. :)

	if(file_exists($authfile)){
		if($cache = file_get_contents($authfile)){
			if(stristr($cache,'Successfully')){
				$answer = $cache;
				$cache = json_decode($cache, true);
				if($cache["timestamp"]+$cache["data"]["expiresIn"] > time()){ // Проверяем актуальность токена, взятого из файла. При любой непонятной ситуации включаем переавторизацию. 
					$refresh = false;
				} else {
					$refresh = true;
				}
			} else {
				$refresh = true;
			}
		} else {
			$refresh = true;
		}
	} else {
		
		$refresh = true;
	}

	if($refresh == true){
		echo "REFRESH\r\n";
		$answer = curl_exec($ch);
		if (curl_error($ch)) {
			echo curl_error($ch);
		}
		if(stristr($answer,'Successfully')){
			file_put_contents('/tmp/el'.HET_LOGIN.'.json',$answer);
		}
	}
	if(stristr($answer,'Successfully')){
		$answer = json_decode($answer, true);
	} else {
                unlink($authfile); // Если что-то пошло не так, грохаем кеш авторизации и завершаем работу скрипта, ибо дальше в ней нет смысла.
		die('unsuccess');
	}
			
	$token = $answer["data"]["accessToken"]; // Ну так, для наглядности. Конечно, модно было не плодить переменные, но чё нам, жалко памяти? ;)

	curl_setopt($ch, CURLOPT_URL, 'https://cabinet-api.het.uz/household-consumer/v1/mobile-cabinet/consumer-state'); // Запрашиваем данные со нужного раздела API
	curl_setopt($ch, CURLOPT_POST, false);
	$headers = array(
	   "Connection: keep-alive",
	   "Keep-Alive: timeout=5, max=100",
	   "Authorization: Bearer ".$token, //Добавляем токен авторизации в заголовок нового запроса.
	   "Coato-Code: ".$answer["data"]["coatoCode"], //Тут требуется добавить номер РЭС. Берётся так-же из ответа на авторизацию.
	);
	curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
	$answer = curl_exec($ch); // Пуляем запрос

	if (curl_error($ch)) {
		echo curl_error($ch);
	}
	curl_close($ch);
	print_r($answer); // Смотрим, что прилетело. В принципе, это можно закомментить
	if(stristr($answer,'Successfully')){
		
		$answer = json_decode($answer,true); // Превращаем JSON в обычный массив
		$mqtt = new \PhpMqtt\Client\MqttClient($server, $port, $clientId); // создаём экземпляр для MQTT
		
		if(isset($balancefile))
			file_put_contents($balancefile,$answer["data"]["balance"] / 100); // Складываем данные о балансе в статический файл, если включено в настройках.
		
		$connectionSettings = (new \PhpMqtt\Client\ConnectionSettings)
			->setConnectTimeout(3)
			->setUsername($mqttLogin)
			->setPassword($mqttPass); // тут у нас добавляются данные для авторизации в mqtt. Если у вас там всё открыто (чего я категорически не советую), можно это выпилить и сделать $mqtt->connect() без аргументов.
		
		$mqtt->connect($connectionSettings, true);
		if($answer["data"]["balance"]!==null)
			$mqtt->publish('het/'.HET_LOGIN.'/balance', $answer["data"]["balance"] / 100, 1);
		if($answer["data"]["lastCrawlReading"]!==null)
			$mqtt->publish('het/'.HET_LOGIN.'/lastCrawlReading', $answer["data"]["lastCrawlReading"] / 1000, 0);
		if($answer["data"]["lastCrawlDate"]!==null)
			$mqtt->publish('het/'.HET_LOGIN.'/lastCrawlDate', $answer["data"]["lastCrawlDate"], 0);
		if($answer["data"]["lastPayment"]!==null)
			$mqtt->publish('het/'.HET_LOGIN.'/lastPayment', $answer["data"]["lastPayment"] / 100, 0);
		if($answer["data"]["lastPaymentDate"]!==null)
			$mqtt->publish('het/'.HET_LOGIN.'/lastPaymentDate', $answer["data"]["lastPaymentDate"], 0);
		if($answer["data"]["currentMonthCalcKwh"]!==null)
			$mqtt->publish('het/'.HET_LOGIN.'/currentMonthCalcKwh', $answer["data"]["currentMonthCalcKwh"] / 1000, 0);	
		if($answer["data"]["currentMonthCalcSum"]!==null)
			$mqtt->publish('het/'.HET_LOGIN.'/currentMonthCalcSum', $answer["data"]["currentMonthCalcSum"] / 100, 0);
		$mqtt->publish('het/'.HET_LOGIN.'/ecoCurrentMonthCalcKwh', $answer["data"]["ecoCurrentMonthCalcKwh"]/1000, 0);

// Значения, которые по идее, должны быть дробными приходят целыми числами, по этому приходится делать соответствующие деления. Ну, либо можно оставить это на откуп узлам умного дома. Но мне было проще вот так. В некоторых случаях API отдаёт значение null, что в результате роняет библиотеку mqtt, по этому делаем проверку и не отправляем ничего со значением null. 
		$mqtt->disconnect(); // Закрываем соединение
	} else {
		unlink($authfile); // Если в ответ на запрос данных пришло не то, что нужно, грохаем кеш авторизации, потому, что причина скорее всего в ней.
	}
?></pre> 

If for some reason authorization fails, the script will request it again the next time it’s run. I didn’t use recursive iterations, as this could lead to a ban on the API side. The script can be run via cron, uploaded to a web server, and called via smart home automation.

P.S.
Huge kudos to the developers of the new dashboard! Five years ago, I tried to get an API from het.uz to retrieve data, but all my emails were ignored.

Why bother collecting this data? For example, I set up an automatic notification in a family Telegram group if the balance on my home or summer cottage meter was less than 30,000 soums. I even wanted to automatically generate invoices, but so far, no payment system has been willing to help me implement this idea. I hope to see that happen someday. :)