760 lines
24 KiB
PHP
760 lines
24 KiB
PHP
<?php
|
|
|
|
class EaseeGateway extends IPSModule
|
|
{
|
|
private const WEBSOCKET_MODULE_ID = "{D68FD31F-0E90-7019-F16C-1949BD3079EF}";
|
|
private const SIMPLE_RX_DATA_ID = "{018EF6B5-AB94-40C6-AA53-46943E824ACF}";
|
|
private const SIMPLE_TX_DATA_ID = "{79827379-F36E-4ADA-8A95-5F8D1DC92FA9}";
|
|
private const CHILD_REQUEST_DATA_ID = "{BC4E9B83-9C9B-44C3-A902-5CEB45B0E5B1}";
|
|
private const CHILD_EVENT_DATA_ID = "{A8EF2E37-6B35-4E92-A7D1-4F8172790BA2}";
|
|
private const SIGNALR_BASE_URL = "https://streams.easee.com/hubs/chargers";
|
|
private const API_BASE_URL = "https://api.easee.com";
|
|
private const RECORD_SEPARATOR = "\x1e";
|
|
|
|
public function Create()
|
|
{
|
|
parent::Create();
|
|
|
|
$this->RegisterPropertyBoolean("Active", true);
|
|
$this->RegisterPropertyString("Username", "");
|
|
$this->RegisterPropertyString("Password", "");
|
|
$this->RegisterPropertyBoolean("VerifyCertificate", true);
|
|
|
|
$this->RegisterAttributeString("ObservationCache", "{}");
|
|
|
|
$this->RegisterVariableBoolean("Connected", "SignalR verbunden", "~Switch", false);
|
|
$this->RegisterVariableInteger("SubscriptionCount", "Abonnierte Ladestationen", "", 0);
|
|
$this->RegisterVariableString("LastError", "Letzter Fehler", "", 0);
|
|
|
|
$this->RegisterTimer(
|
|
"MaintainConnectionTimer",
|
|
0,
|
|
'IPS_RequestAction(' . $this->InstanceID . ', "MaintainConnection", 0);'
|
|
);
|
|
$this->RegisterTimer(
|
|
"TokenRefreshTimer",
|
|
0,
|
|
'IPS_RequestAction(' . $this->InstanceID . ', "RefreshToken", 0);'
|
|
);
|
|
|
|
}
|
|
|
|
public function ApplyChanges()
|
|
{
|
|
parent::ApplyChanges();
|
|
|
|
$this->SetTimerInterval("MaintainConnectionTimer", 0);
|
|
$this->SetTimerInterval("TokenRefreshTimer", 0);
|
|
$this->SetBuffer("SignalRReady", "0");
|
|
$this->SetBuffer("ReceiveBuffer", "");
|
|
$this->SetBuffer("Subscriptions", "{}");
|
|
$this->SetBuffer("SubscribedThisConnection", "{}");
|
|
$this->SetBuffer("PendingInvocations", "{}");
|
|
$this->SetValue("SubscriptionCount", 0);
|
|
$this->SetConnected(false);
|
|
|
|
if (!$this->ReadPropertyBoolean("Active")) {
|
|
$this->SetStatus(104);
|
|
return;
|
|
}
|
|
|
|
if (
|
|
trim($this->ReadPropertyString("Username")) === "" ||
|
|
$this->ReadPropertyString("Password") === ""
|
|
) {
|
|
$this->SetStatus(201);
|
|
$this->SetLastError("Easee-Benutzername oder Passwort fehlt");
|
|
return;
|
|
}
|
|
|
|
if (!$this->EnsureWebSocketParent()) {
|
|
$this->SetStatus(203);
|
|
return;
|
|
}
|
|
|
|
$this->SetTimerInterval("MaintainConnectionTimer", 10000);
|
|
$this->SetTimerInterval("TokenRefreshTimer", 1800000);
|
|
|
|
if (!$this->RefreshCredentials(false)) {
|
|
$this->SetStatus(202);
|
|
return;
|
|
}
|
|
|
|
$this->ConnectSignalR();
|
|
}
|
|
|
|
private function EnsureWebSocketParent(): bool
|
|
{
|
|
$instance = IPS_GetInstance($this->InstanceID);
|
|
$parentID = (int)$instance["ConnectionID"];
|
|
|
|
if ($parentID > 0) {
|
|
$parent = IPS_GetInstance($parentID);
|
|
if ($parent["ModuleInfo"]["ModuleID"] === self::WEBSOCKET_MODULE_ID) {
|
|
return true;
|
|
}
|
|
IPS_DisconnectInstance($this->InstanceID);
|
|
}
|
|
|
|
$parentID = IPS_CreateInstance(self::WEBSOCKET_MODULE_ID);
|
|
if ($parentID <= 0) {
|
|
$this->SetLastError("WebSocket-Client konnte nicht erstellt werden");
|
|
return false;
|
|
}
|
|
|
|
IPS_SetName($parentID, "Easee SignalR WebSocket");
|
|
$objectParentID = IPS_GetParent($this->InstanceID);
|
|
if ($objectParentID > 0) {
|
|
IPS_SetParent($parentID, $objectParentID);
|
|
}
|
|
|
|
if (!IPS_ConnectInstance($this->InstanceID, $parentID)) {
|
|
$this->SetLastError("WebSocket-Client konnte nicht verbunden werden");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function RequestAction($Ident, $Value)
|
|
{
|
|
switch ($Ident) {
|
|
case "MaintainConnection":
|
|
$this->MaintainConnection();
|
|
break;
|
|
|
|
case "RefreshToken":
|
|
if ($this->RefreshCredentials(false)) {
|
|
$this->ConnectSignalR();
|
|
}
|
|
break;
|
|
|
|
case "Reconnect":
|
|
$this->SetBuffer("AccessToken", "");
|
|
$this->SetBuffer("RefreshToken", "");
|
|
if ($this->RefreshCredentials(false)) {
|
|
$this->ConnectSignalR();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new Exception("Unbekannte Aktion");
|
|
}
|
|
}
|
|
|
|
public function GetConfigurationForParent()
|
|
{
|
|
return json_encode([
|
|
"Active" => $this->ReadPropertyBoolean("Active"),
|
|
"URL" => $this->GetBuffer("WebSocketURL"),
|
|
"VerifyCertificate" => $this->ReadPropertyBoolean("VerifyCertificate"),
|
|
"Headers" => "[]",
|
|
]);
|
|
}
|
|
|
|
public function ReceiveData($JSONString)
|
|
{
|
|
$packet = json_decode($JSONString, true);
|
|
if (!is_array($packet) || !isset($packet["Buffer"])) {
|
|
return;
|
|
}
|
|
|
|
$this->SetBuffer("LastReceive", (string)time());
|
|
$buffer = $this->GetBuffer("ReceiveBuffer") . $packet["Buffer"];
|
|
$frames = explode(self::RECORD_SEPARATOR, $buffer);
|
|
$this->SetBuffer("ReceiveBuffer", array_pop($frames));
|
|
|
|
foreach ($frames as $frame) {
|
|
if ($frame !== "") {
|
|
$this->HandleSignalRFrame($frame);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function ForwardData($JSONString)
|
|
{
|
|
$packet = json_decode($JSONString, true);
|
|
if (!is_array($packet) || !isset($packet["Buffer"])) {
|
|
return json_encode(["success" => false, "error" => "Ungültiges Datenpaket"]);
|
|
}
|
|
|
|
return $this->ProcessStationRequest($packet["Buffer"]);
|
|
}
|
|
|
|
public function ProcessStationRequest($JSONString)
|
|
{
|
|
$request = json_decode($JSONString, true);
|
|
if (!is_array($request) || !isset($request["action"])) {
|
|
return json_encode(["success" => false, "error" => "Ungültige Gateway-Anfrage"]);
|
|
}
|
|
|
|
$serialNumber = strtoupper(trim((string)($request["serialNumber"] ?? "")));
|
|
|
|
switch ($request["action"]) {
|
|
case "Subscribe":
|
|
if ($serialNumber === "") {
|
|
return json_encode(["success" => false, "error" => "Seriennummer fehlt"]);
|
|
}
|
|
$this->RegisterSubscription($serialNumber);
|
|
return $this->CreateStateResponse($serialNumber);
|
|
|
|
case "GetState":
|
|
if ($serialNumber === "") {
|
|
return json_encode(["success" => false, "error" => "Seriennummer fehlt"]);
|
|
}
|
|
$this->RegisterSubscription($serialNumber);
|
|
return $this->CreateStateResponse($serialNumber);
|
|
|
|
case "SetCurrent":
|
|
return json_encode(
|
|
$this->SetDynamicChargerCurrent(
|
|
$serialNumber,
|
|
(float)($request["amps"] ?? -1)
|
|
)
|
|
);
|
|
|
|
default:
|
|
return json_encode(["success" => false, "error" => "Unbekannte Gateway-Aktion"]);
|
|
}
|
|
}
|
|
|
|
private function MaintainConnection()
|
|
{
|
|
if (!$this->ReadPropertyBoolean("Active")) {
|
|
return;
|
|
}
|
|
|
|
$now = time();
|
|
$lastNegotiation = (int)$this->GetBuffer("LastNegotiation");
|
|
$lastReceive = (int)$this->GetBuffer("LastReceive");
|
|
$ready = $this->GetBuffer("SignalRReady") === "1";
|
|
|
|
if ($ready) {
|
|
$this->SendSignalRFrame(["type" => 6]);
|
|
|
|
if ($lastReceive > 0 && ($now - $lastReceive) <= 90) {
|
|
return;
|
|
}
|
|
|
|
$this->SetConnected(false);
|
|
$this->SetBuffer("SignalRReady", "0");
|
|
}
|
|
|
|
if (($now - $lastNegotiation) >= 45) {
|
|
$this->ConnectSignalR();
|
|
return;
|
|
}
|
|
|
|
$this->SendHandshake();
|
|
}
|
|
|
|
private function ConnectSignalR()
|
|
{
|
|
if (!$this->EnsureAccessToken()) {
|
|
$this->SetStatus(202);
|
|
return false;
|
|
}
|
|
|
|
$this->SetBuffer("LastNegotiation", (string)time());
|
|
$token = $this->GetBuffer("AccessToken");
|
|
$response = $this->HttpRequest(
|
|
"POST",
|
|
self::SIGNALR_BASE_URL . "/negotiate?negotiateVersion=1",
|
|
"",
|
|
$token
|
|
);
|
|
|
|
if ($response["httpCode"] === 401 && $this->RefreshCredentials(false)) {
|
|
$token = $this->GetBuffer("AccessToken");
|
|
$response = $this->HttpRequest(
|
|
"POST",
|
|
self::SIGNALR_BASE_URL . "/negotiate?negotiateVersion=1",
|
|
"",
|
|
$token
|
|
);
|
|
}
|
|
|
|
if (!$response["success"]) {
|
|
$this->SetStatus(203);
|
|
$this->SetLastError(
|
|
"SignalR-Aushandlung fehlgeschlagen: HTTP " . $response["httpCode"] .
|
|
($response["error"] !== "" ? " / " . $response["error"] : "")
|
|
);
|
|
return false;
|
|
}
|
|
|
|
$data = json_decode($response["body"], true);
|
|
$connectionToken = is_array($data)
|
|
? ($data["connectionToken"] ?? $data["connectionId"] ?? "")
|
|
: "";
|
|
|
|
if ($connectionToken === "") {
|
|
$this->SetStatus(203);
|
|
$this->SetLastError("SignalR-Antwort enthält kein Verbindungstoken");
|
|
return false;
|
|
}
|
|
|
|
$webSocketURL =
|
|
"wss://streams.easee.com/hubs/chargers?id=" . rawurlencode($connectionToken) .
|
|
"&access_token=" . rawurlencode($token);
|
|
|
|
$this->SetBuffer("WebSocketURL", $webSocketURL);
|
|
$this->SetBuffer("SignalRReady", "0");
|
|
$this->SetBuffer("ReceiveBuffer", "");
|
|
$this->SetBuffer("SubscribedThisConnection", "{}");
|
|
$this->SetBuffer("PendingInvocations", "{}");
|
|
$this->SetConnected(false);
|
|
|
|
if (!$this->ConfigureWebSocketParent($webSocketURL)) {
|
|
$this->SetStatus(203);
|
|
return false;
|
|
}
|
|
|
|
$this->SendHandshake();
|
|
return true;
|
|
}
|
|
|
|
private function ConfigureWebSocketParent(string $url): bool
|
|
{
|
|
$instance = IPS_GetInstance($this->InstanceID);
|
|
$parentID = (int)$instance["ConnectionID"];
|
|
if ($parentID <= 0) {
|
|
$this->SetLastError("WebSocket-Client ist nicht verbunden");
|
|
return false;
|
|
}
|
|
|
|
IPS_SetProperty($parentID, "Active", true);
|
|
IPS_SetProperty($parentID, "URL", $url);
|
|
IPS_SetProperty($parentID, "VerifyCertificate", $this->ReadPropertyBoolean("VerifyCertificate"));
|
|
IPS_SetProperty($parentID, "Headers", "[]");
|
|
IPS_ApplyChanges($parentID);
|
|
|
|
return true;
|
|
}
|
|
|
|
private function SendHandshake()
|
|
{
|
|
$this->SendRawToWebSocket(
|
|
json_encode(["protocol" => "json", "version" => 1]) . self::RECORD_SEPARATOR
|
|
);
|
|
}
|
|
|
|
private function SendSignalRFrame(array $frame)
|
|
{
|
|
$this->SendRawToWebSocket(json_encode($frame) . self::RECORD_SEPARATOR);
|
|
}
|
|
|
|
private function SendRawToWebSocket(string $payload)
|
|
{
|
|
return $this->SendDataToParent(json_encode([
|
|
"DataID" => self::SIMPLE_TX_DATA_ID,
|
|
"Buffer" => $payload,
|
|
]));
|
|
}
|
|
|
|
private function HandleSignalRFrame(string $frame)
|
|
{
|
|
if ($frame === "{}") {
|
|
$this->SetBuffer("SignalRReady", "1");
|
|
$this->SetStatus(102);
|
|
$this->SetLastError("");
|
|
$this->SetConnected(true);
|
|
$this->SendAllSubscriptions();
|
|
return;
|
|
}
|
|
|
|
$message = json_decode($frame, true);
|
|
if (!is_array($message)) {
|
|
$this->SetLastError("Ungültige SignalR-Nachricht");
|
|
return;
|
|
}
|
|
|
|
if (isset($message["error"])) {
|
|
$this->SetStatus(203);
|
|
$this->SetLastError("SignalR-Handshake: " . $message["error"]);
|
|
return;
|
|
}
|
|
|
|
$type = (int)($message["type"] ?? 0);
|
|
if ($type === 6) {
|
|
return;
|
|
}
|
|
|
|
if ($type === 7) {
|
|
$this->SetBuffer("SignalRReady", "0");
|
|
$this->SetConnected(false);
|
|
$this->SetLastError("SignalR-Verbindung wurde beendet");
|
|
return;
|
|
}
|
|
|
|
$serialNumber = "";
|
|
if ($type === 3 && isset($message["invocationId"])) {
|
|
$pending = $this->ReadBufferArray("PendingInvocations");
|
|
$invocationID = (string)$message["invocationId"];
|
|
$serialNumber = (string)($pending[$invocationID] ?? "");
|
|
unset($pending[$invocationID]);
|
|
$this->SetBuffer("PendingInvocations", json_encode($pending));
|
|
}
|
|
|
|
if (isset($message["arguments"])) {
|
|
$this->ExtractObservations($message["arguments"], $serialNumber);
|
|
}
|
|
if (isset($message["result"])) {
|
|
$this->ExtractObservations($message["result"], $serialNumber);
|
|
}
|
|
}
|
|
|
|
private function RegisterSubscription(string $serialNumber)
|
|
{
|
|
$subscriptions = $this->ReadBufferArray("Subscriptions");
|
|
if (!isset($subscriptions[$serialNumber])) {
|
|
$subscriptions[$serialNumber] = true;
|
|
$this->SetBuffer("Subscriptions", json_encode($subscriptions));
|
|
$this->SetValue("SubscriptionCount", count($subscriptions));
|
|
}
|
|
|
|
if ($this->GetBuffer("SignalRReady") === "1") {
|
|
$this->SendSubscription($serialNumber);
|
|
}
|
|
}
|
|
|
|
private function SendAllSubscriptions()
|
|
{
|
|
foreach (array_keys($this->ReadBufferArray("Subscriptions")) as $serialNumber) {
|
|
$this->SendSubscription($serialNumber);
|
|
}
|
|
}
|
|
|
|
private function SendSubscription(string $serialNumber)
|
|
{
|
|
$sent = $this->ReadBufferArray("SubscribedThisConnection");
|
|
if (isset($sent[$serialNumber])) {
|
|
return;
|
|
}
|
|
|
|
$invocationID = (string)(((int)$this->GetBuffer("InvocationID")) + 1);
|
|
$this->SetBuffer("InvocationID", $invocationID);
|
|
|
|
$pending = $this->ReadBufferArray("PendingInvocations");
|
|
$pending[$invocationID] = $serialNumber;
|
|
$this->SetBuffer("PendingInvocations", json_encode($pending));
|
|
|
|
$this->SendSignalRFrame([
|
|
"type" => 1,
|
|
"invocationId" => $invocationID,
|
|
"target" => "SubscribeWithCurrentState",
|
|
"arguments" => [$serialNumber, true],
|
|
]);
|
|
|
|
$sent[$serialNumber] = true;
|
|
$this->SetBuffer("SubscribedThisConnection", json_encode($sent));
|
|
}
|
|
|
|
private function ExtractObservations($node, string $serialNumber = "", int $depth = 0)
|
|
{
|
|
if (!is_array($node) || $depth > 12) {
|
|
return;
|
|
}
|
|
|
|
foreach (["serialNumber", "SerialNumber", "chargerId", "ChargerId", "mid", "Mid"] as $key) {
|
|
if (isset($node[$key]) && is_scalar($node[$key])) {
|
|
$candidate = strtoupper((string)$node[$key]);
|
|
if ($this->IsSubscribedSerial($candidate)) {
|
|
$serialNumber = $candidate;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($this->IsList($node)) {
|
|
foreach ($node as $item) {
|
|
if (is_string($item)) {
|
|
$candidate = strtoupper($item);
|
|
if ($this->IsSubscribedSerial($candidate)) {
|
|
$serialNumber = $candidate;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$observationID = null;
|
|
foreach (["id", "Id", "observationId", "ObservationId"] as $key) {
|
|
if (array_key_exists($key, $node) && is_numeric($node[$key])) {
|
|
$observationID = (int)$node[$key];
|
|
break;
|
|
}
|
|
}
|
|
|
|
$valueExists = false;
|
|
$value = null;
|
|
foreach (["value", "Value"] as $key) {
|
|
if (array_key_exists($key, $node)) {
|
|
$valueExists = true;
|
|
$value = $node[$key];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (
|
|
$serialNumber !== "" &&
|
|
$valueExists &&
|
|
($observationID === 109 || $observationID === 120)
|
|
) {
|
|
$this->PublishObservation($serialNumber, $observationID, $value);
|
|
}
|
|
|
|
foreach ($node as $item) {
|
|
if (is_array($item)) {
|
|
$this->ExtractObservations($item, $serialNumber, $depth + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function PublishObservation(string $serialNumber, int $observationID, $value)
|
|
{
|
|
$cache = $this->ReadAttributeArray("ObservationCache");
|
|
if (!isset($cache[$serialNumber]) || !is_array($cache[$serialNumber])) {
|
|
$cache[$serialNumber] = [];
|
|
}
|
|
$cache[$serialNumber][(string)$observationID] = $value;
|
|
$cache[$serialNumber]["updated"] = time();
|
|
$this->WriteAttributeString("ObservationCache", json_encode($cache));
|
|
|
|
$this->SendDataToChildren(json_encode([
|
|
"DataID" => self::CHILD_EVENT_DATA_ID,
|
|
"Buffer" => json_encode([
|
|
"type" => "Observation",
|
|
"serialNumber" => $serialNumber,
|
|
"id" => $observationID,
|
|
"value" => $value,
|
|
"timestamp" => time(),
|
|
]),
|
|
]));
|
|
}
|
|
|
|
private function CreateStateResponse(string $serialNumber): string
|
|
{
|
|
$cache = $this->ReadAttributeArray("ObservationCache");
|
|
return json_encode([
|
|
"success" => true,
|
|
"connected" => $this->GetBuffer("SignalRReady") === "1",
|
|
"state" => $cache[$serialNumber] ?? [],
|
|
]);
|
|
}
|
|
|
|
private function SetDynamicChargerCurrent(string $serialNumber, float $amps): array
|
|
{
|
|
if ($serialNumber === "") {
|
|
return ["success" => false, "error" => "Seriennummer fehlt"];
|
|
}
|
|
if ($amps < 0 || $amps > 32) {
|
|
return ["success" => false, "error" => "Strom muss zwischen 0 und 32 A liegen"];
|
|
}
|
|
|
|
return $this->AuthorizedApiRequest(
|
|
"POST",
|
|
"/api/chargers/" . rawurlencode($serialNumber) .
|
|
"/commands/set_dynamic_charger_current",
|
|
json_encode(["amps" => $amps, "minutes" => 0])
|
|
);
|
|
}
|
|
|
|
private function AuthorizedApiRequest(string $method, string $path, string $body): array
|
|
{
|
|
if (!$this->EnsureAccessToken()) {
|
|
return ["success" => false, "error" => "Kein Easee-Access-Token"];
|
|
}
|
|
|
|
$response = $this->HttpRequest(
|
|
$method,
|
|
self::API_BASE_URL . $path,
|
|
$body,
|
|
$this->GetBuffer("AccessToken")
|
|
);
|
|
|
|
if ($response["httpCode"] === 401 && $this->RefreshCredentials(false)) {
|
|
$response = $this->HttpRequest(
|
|
$method,
|
|
self::API_BASE_URL . $path,
|
|
$body,
|
|
$this->GetBuffer("AccessToken")
|
|
);
|
|
}
|
|
|
|
if (!$response["success"]) {
|
|
$error = $response["error"];
|
|
if ($error === "") {
|
|
$error = "Easee-HTTP-Fehler " . $response["httpCode"];
|
|
}
|
|
return [
|
|
"success" => false,
|
|
"error" => $error,
|
|
"httpCode" => $response["httpCode"],
|
|
];
|
|
}
|
|
|
|
return [
|
|
"success" => true,
|
|
"httpCode" => $response["httpCode"],
|
|
"body" => $response["body"],
|
|
];
|
|
}
|
|
|
|
public function RefreshCredentials(bool $reconnect = true): bool
|
|
{
|
|
$tokens = null;
|
|
$accessToken = $this->GetBuffer("AccessToken");
|
|
$refreshToken = $this->GetBuffer("RefreshToken");
|
|
|
|
if ($accessToken !== "" && $refreshToken !== "") {
|
|
$tokens = $this->RequestTokenPair(
|
|
self::API_BASE_URL . "/api/accounts/refresh_token",
|
|
[
|
|
"accessToken" => $accessToken,
|
|
"refreshToken" => $refreshToken,
|
|
],
|
|
$accessToken
|
|
);
|
|
}
|
|
|
|
if ($tokens === null) {
|
|
$tokens = $this->RequestTokenPair(
|
|
self::API_BASE_URL . "/api/accounts/login",
|
|
[
|
|
"userName" => $this->ReadPropertyString("Username"),
|
|
"password" => $this->ReadPropertyString("Password"),
|
|
]
|
|
);
|
|
}
|
|
|
|
if ($tokens === null || !isset($tokens["accessToken"])) {
|
|
$this->SetLastError("Easee-Anmeldung oder Token-Erneuerung fehlgeschlagen");
|
|
$this->SetStatus(202);
|
|
return false;
|
|
}
|
|
|
|
$this->SetBuffer("AccessToken", (string)$tokens["accessToken"]);
|
|
if (isset($tokens["refreshToken"])) {
|
|
$this->SetBuffer("RefreshToken", (string)$tokens["refreshToken"]);
|
|
}
|
|
$this->SetBuffer("TokenUpdated", (string)time());
|
|
|
|
if ($reconnect) {
|
|
$this->ConnectSignalR();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private function EnsureAccessToken(): bool
|
|
{
|
|
if ($this->GetBuffer("AccessToken") !== "") {
|
|
return true;
|
|
}
|
|
return $this->RefreshCredentials(false);
|
|
}
|
|
|
|
private function RequestTokenPair(string $url, array $payload, string $bearerToken = "")
|
|
{
|
|
$response = $this->HttpRequest(
|
|
"POST",
|
|
$url,
|
|
json_encode($payload),
|
|
$bearerToken
|
|
);
|
|
if (!$response["success"]) {
|
|
return null;
|
|
}
|
|
|
|
$data = json_decode($response["body"], true);
|
|
return is_array($data) ? $data : null;
|
|
}
|
|
|
|
private function HttpRequest(
|
|
string $method,
|
|
string $url,
|
|
string $body = "",
|
|
string $bearerToken = ""
|
|
): array {
|
|
$headers = [
|
|
"Accept: application/json",
|
|
"Content-Type: application/json",
|
|
];
|
|
if ($bearerToken !== "") {
|
|
$headers[] = "Authorization: Bearer " . $bearerToken;
|
|
}
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_CUSTOMREQUEST => $method,
|
|
CURLOPT_TIMEOUT => 30,
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_MAXREDIRS => 5,
|
|
CURLOPT_HTTPHEADER => $headers,
|
|
CURLOPT_POSTFIELDS => $body,
|
|
]);
|
|
|
|
$responseBody = curl_exec($ch);
|
|
$curlError = curl_error($ch);
|
|
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
return [
|
|
"success" => $responseBody !== false && $curlError === "" &&
|
|
$httpCode >= 200 && $httpCode < 300,
|
|
"body" => $responseBody === false ? "" : $responseBody,
|
|
"error" => $curlError,
|
|
"httpCode" => $httpCode,
|
|
];
|
|
}
|
|
|
|
private function SetConnected(bool $connected)
|
|
{
|
|
$this->SetValue("Connected", $connected);
|
|
$this->SendDataToChildren(json_encode([
|
|
"DataID" => self::CHILD_EVENT_DATA_ID,
|
|
"Buffer" => json_encode([
|
|
"type" => "GatewayStatus",
|
|
"connected" => $connected,
|
|
]),
|
|
]));
|
|
}
|
|
|
|
private function SetLastError(string $message)
|
|
{
|
|
$this->SetValue("LastError", $message);
|
|
if ($message !== "") {
|
|
$this->SendDebug("EaseeGateway", $message, 0);
|
|
}
|
|
}
|
|
|
|
private function IsSubscribedSerial(string $candidate): bool
|
|
{
|
|
return isset($this->ReadBufferArray("Subscriptions")[$candidate]);
|
|
}
|
|
|
|
private function IsList(array $value): bool
|
|
{
|
|
$index = 0;
|
|
foreach ($value as $key => $_) {
|
|
if ($key !== $index++) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private function ReadAttributeArray(string $name): array
|
|
{
|
|
$value = json_decode($this->ReadAttributeString($name), true);
|
|
return is_array($value) ? $value : [];
|
|
}
|
|
|
|
private function ReadBufferArray(string $name): array
|
|
{
|
|
$value = json_decode($this->GetBuffer($name), true);
|
|
return is_array($value) ? $value : [];
|
|
}
|
|
}
|