") => queryDeviceCtrlValue * - write($dev, "", $value) => ctrlDevice z mapowaniem * - read($dev, "energy_flow") => webQueryDeviceEnergyFlowEs * - param("")->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)."); } } } ?>