<?php
/**
* DessMonitorClient – ujednolicony klient:
* - read($dev, "<param_id>") => queryDeviceCtrlValue
* - write($dev, "<param_id>", $value) => ctrlDevice z mapowaniem
* - read($dev, "energy_flow") => webQueryDeviceEnergyFlowEs
* - param("<param_id>")->bind($dev)->get()/set()
*/
class DessMonitorClient
{
private static string $baseUrl = "https://api.dessmonitor.com";
private string $token;
private string $secret;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public function __construct(string $token, string $secret)
{
self::$baseUrl = rtrim(self::$baseUrl, "/");
$this->token = $token;
$this->secret = $secret;
}
// ---------------------------
// Public API (high-level)
// ---------------------------
/**
* Czytanie:
* - "energy_flow" (alias) -> webQueryDeviceEnergyFlowEs
* - w przeciwnym razie traktuje $what jako ID parametru -> queryDeviceCtrlValue
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public static function login(string $usr, string $pwd, string $companyKey, int $source = 1): array {
$base = rtrim(self::$baseUrl, "/") . "/public/";
$salt = (string) round(microtime(true) * 1000);
// wartości jak w dess_login.php / web
$appClient = "web";
$appId = "dessmonitor.com";
$appVersion = "1.0.0";
// UWAGA: kolejność w podpisie ma znaczenie
$signatureTail =
"&action=authSource" .
"&usr=" . $usr .
"&company-key=" . $companyKey .
"&source=" . $source .
"&_app_client_=" . $appClient .
"&_app_id_=" . $appId .
"&_app_version_=" . $appVersion;
$sign = sha1($salt . sha1($pwd) . $signatureTail);
$params = [
"sign" => $sign,
"salt" => $salt,
"action" => "authSource",
"usr" => $usr,
"company-key" => $companyKey,
"source" => (string)$source,
"_app_client_" => $appClient,
"_app_id_" => $appId,
"_app_version_" => $appVersion,
];
$url = $base . "?" . http_build_query($params);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_CONNECTTIMEOUT => 10,
// tak jak u Ciebie (jeśli masz poprawne CA, ustaw docelowo true)
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_HTTPHEADER => [
"Accept: application/json",
"User-Agent: Mozilla/5.0",
],
]);
$resp = curl_exec($ch);
$http = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($resp === false || $resp === "") {
throw new RuntimeException("Puste logowanie. HTTP=$http curl_error=$err URL=$url");
}
$json = json_decode($resp, true);
if (!is_array($json)) {
throw new RuntimeException("Nie-JSON w authSource. HTTP=$http BODY=$resp");
}
if (($json["err"] ?? null) !== 0) {
$desc = $json["desc"] ?? "UNKNOWN";
throw new RuntimeException("Błąd authSource: err=" . ($json["err"] ?? "NULL") . " desc=$desc HTTP=$http BODY=$resp");
}
// najczęściej "dat", czasem "data"
$dat = $json["dat"] ?? ($json["data"] ?? null);
if (!is_array($dat)) {
throw new RuntimeException("authSource: brak pola dat/data w odpowiedzi. BODY=$resp");
}
// Zwracamy wyłącznie tablicę z logowania (bez zapisywania w obiekcie)
return [
"secret" => $dat["secret"] ?? null,
"token" => $dat["token"] ?? null,
"expire" => $dat["expire"] ?? null,
"role" => $dat["role"] ?? null,
"usr" => $dat["usr"] ?? null,
"uid" => $dat["uid"] ?? null,
"pprauth" => $dat["pprauth"] ?? null,
// jeśli API zwraca jeszcze coś – zachowaj:
"raw" => $dat,
];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public function read(array $dev, string $what, array $ctx = []): array
{
// alias: specjalny odczyt
if ($what === "energy_flow") {
return $this->webQueryDeviceEnergyFlowEs($dev);
}
// domyślnie: traktuj $what jako ID parametru sterującego
return $this->queryDeviceCtrlValue(
$dev,
$what,
$ctx["i18n"] ?? "en_US"
);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
* Ustawianie – te same nazwy parametrów co read()
*/
public function write(array $dev, string $param, $value, array $ctx = []): array
{
return $this->setParam($dev, $param, $value, $ctx["i18n"] ?? "en_US");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
* Fluent: param("...")->bind($dev)->get()/set()
* Uwaga: param() nie zna $dev, więc musisz wywołać bind($dev) lub użyć paramForDev().
*/
public function param(string $id): DessParam
{
return new DessParam($this, $id);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
* Wygodny skrót: paramForDev($dev, "id")->get()/set()
*/
public function paramForDev(array $dev, string $id): DessParam
{
return $this->param($id)->bind($dev);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// ---------------------------
// Fluent helper (internal)
// ---------------------------
public function _paramGet(array $dev, string $id, string $i18n = "en_US"): array
{
return $this->queryDeviceCtrlValue($dev, $id, $i18n);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public function _paramSet(array $dev, string $id, $value, string $i18n = "en_US"): array
{
return $this->setParam($dev, $id, $value, $i18n);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// ---------------------------
// Low-level core (sign/call)
// ---------------------------
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
private function salt(): string
{
return (string) round(microtime(true) * 1000);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
* sign = sha1( salt + secret + token + "&action=...&k=v&k2=v2..." )
* KOLEJNOŚĆ $orderedParams jest krytyczna.
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
private function sign(string $salt, string $action, array $orderedParams): string
{
$tail = "&action=" . $action;
foreach ($orderedParams as $k => $v) {
$tail .= "&" . $k . "=" . $v;
}
return sha1($salt . $this->secret . $this->token . $tail);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
private function call(string $action, array $orderedParams): array
{
$salt = $this->salt();
$sign = $this->sign($salt, $action, $orderedParams);
$params = array_merge(
["sign" => $sign, "salt" => $salt, "token" => $this->token, "action" => $action],
$orderedParams
);
$url = self::$baseUrl . "/public/?" . http_build_query($params);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_CONNECTTIMEOUT => 10,
// Jeśli masz poprawnie CA/SSL, ustaw docelowo true.
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_HTTPHEADER => [
"Accept: application/json",
"User-Agent: Mozilla/5.0",
],
]);
$resp = curl_exec($ch);
$http = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($resp === false || $resp === "") {
throw new RuntimeException("Pusta odpowiedz. HTTP=$http curl_error=$err URL=$url");
}
$json = json_decode($resp, true);
if (!is_array($json)) {
throw new RuntimeException("Nie-JSON. HTTP=$http BODY=$resp");
}
if (($json["err"] ?? null) !== 0) {
$desc = $json["desc"] ?? "UNKNOWN";
throw new RuntimeException("API error: err=" . ($json["err"] ?? "NULL") . " desc=$desc BODY=$resp");
}
return $json;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// ---------------------------
// Concrete actions
// ---------------------------
public function ctrlDevice(array $dev, string $id, $val, string $i18n = "en_US"): array
{
$ordered = [
"source" => "1",
"pn" => $dev["pn"],
"sn" => $dev["sn"],
"devcode" => (string)$dev["devcode"],
"devaddr" => (string)$dev["devaddr"],
"id" => $id,
"val" => (string)$val,
"i18n" => $i18n,
];
return $this->call("ctrlDevice", $ordered);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public function queryDeviceCtrlValue(array $dev, string $id, string $i18n = "en_US"): array
{
$ordered = [
"source" => "1",
"pn" => $dev["pn"],
"sn" => $dev["sn"],
"devcode" => (string)$dev["devcode"],
"devaddr" => (string)$dev["devaddr"],
"id" => $id,
"i18n" => $i18n,
];
return $this->call("queryDeviceCtrlValue", $ordered);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public function webQueryDeviceEnergyFlowEs(array $dev): array
{
// kolejność jak w Twoim URL
$ordered = [
"source" => "1",
"devcode" => (string)$dev["devcode"],
"pn" => $dev["pn"],
"devaddr" => (string)$dev["devaddr"],
"sn" => $dev["sn"],
];
return $this->call("webQueryDeviceEnergyFlowEs", $ordered);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// ---------------------------
// Param mapping (SET)
// ---------------------------
/**
* Tu trzymasz mapowania wartości wejściowych -> val do ctrlDevice.
* Dzięki temu write() i ->set() przyjmują "ludzkie" wartości, a wysyłają API value.
*/
public function setParam(array $dev, string $param, $value, string $i18n = "en_US"): array
{
switch ($param) {
case "bat_max_total_charge_current":
case "bat_max_utility_charge_current":
return $this->ctrlDevice($dev, $param, (int)$value, $i18n);
case "los_output_source_priority":
// Twoje mapowanie: Utility => 0, inaczej => 2
$val = ($value === "Utility" || $value === 0 || $value === "0") ? 0 : 2;
return $this->ctrlDevice($dev, $param, $val, $i18n);
default:
// fallback
return $this->ctrlDevice($dev, $param, $value, $i18n);
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
* DessParam – obiekt parametru (fluent).
*/
class DessParam
{
private DessMonitorClient $client;
private string $id;
private ?array $dev = null;
private string $i18n = "en_US";
public function __construct(DessMonitorClient $client, string $id)
{
$this->client = $client;
$this->id = $id;
}
public function bind(array $dev): self
{
$this->dev = $dev;
return $this;
}
public function i18n(string $i18n): self
{
$this->i18n = $i18n;
return $this;
}
public function get(): array
{
$this->assertBound();
return $this->client->_paramGet($this->dev, $this->id, $this->i18n);
}
public function set($value): array
{
$this->assertBound();
return $this->client->_paramSet($this->dev, $this->id, $value, $this->i18n);
}
private function assertBound(): void
{
if (!is_array($this->dev)) {
throw new InvalidArgumentException("DessParam: brak bind($dev). Użyj paramForDev() albo ->bind($dev).");
}
}
}
?>