This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Belevo\EnelixEMS;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class LadestationAdapter
|
||||
{
|
||||
public const GO_E_ALT = 1;
|
||||
public const GO_E_GEMINI = 2;
|
||||
public const SMART_ME_PICO = 3;
|
||||
|
||||
/**
|
||||
* @param array<string, string> $konfiguration
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function statusAnfrage(int $geraetetyp, array $konfiguration): array
|
||||
{
|
||||
if ($geraetetyp === self::GO_E_ALT) {
|
||||
return self::anfrage('GET', self::lokaleBasisUrl($konfiguration) . '/mqtt?payload=');
|
||||
}
|
||||
if ($geraetetyp === self::GO_E_GEMINI) {
|
||||
return self::anfrage('GET', self::lokaleBasisUrl($konfiguration) . '/api/status');
|
||||
}
|
||||
if ($geraetetyp === self::SMART_ME_PICO) {
|
||||
$id = self::pflichtwert($konfiguration, 'GeraeteID');
|
||||
|
||||
return self::anfrage(
|
||||
'GET',
|
||||
'https://api.smart-me.com/pico/charging/' . rawurlencode($id),
|
||||
$konfiguration
|
||||
);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unbekannter Geraetetyp.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $konfiguration
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
public static function steuerAnfragen(
|
||||
int $geraetetyp,
|
||||
array $konfiguration,
|
||||
int $ladestrom
|
||||
): array {
|
||||
if ($ladestrom < 0 || $ladestrom > 32 || ($ladestrom > 0 && $ladestrom < 6)) {
|
||||
throw new InvalidArgumentException('Ladestrom muss 0 oder 6 bis 32 A betragen.');
|
||||
}
|
||||
|
||||
if ($geraetetyp === self::GO_E_ALT) {
|
||||
$basis = self::lokaleBasisUrl($konfiguration) . '/mqtt?payload=';
|
||||
if ($ladestrom === 0) {
|
||||
return [self::anfrage('GET', $basis . 'alw=0')];
|
||||
}
|
||||
|
||||
return [
|
||||
self::anfrage('GET', $basis . 'alw=1'),
|
||||
self::anfrage('GET', $basis . 'amp=' . $ladestrom),
|
||||
];
|
||||
}
|
||||
|
||||
if ($geraetetyp === self::GO_E_GEMINI) {
|
||||
$basis = self::lokaleBasisUrl($konfiguration) . '/api/set?';
|
||||
if ($ladestrom === 0) {
|
||||
return [self::anfrage('GET', $basis . 'frc=1&sua=false')];
|
||||
}
|
||||
|
||||
return [
|
||||
self::anfrage('GET', $basis . 'frc=2'),
|
||||
self::anfrage('GET', $basis . 'amp=' . $ladestrom),
|
||||
];
|
||||
}
|
||||
|
||||
if ($geraetetyp === self::SMART_ME_PICO) {
|
||||
$seriennummer = self::pflichtwert($konfiguration, 'Seriennummer');
|
||||
|
||||
return [self::anfrage(
|
||||
'POST',
|
||||
'https://api.smart-me.com/pico/loadmanagementgroup/current/'
|
||||
. rawurlencode($seriennummer)
|
||||
. '?current=' . ($ladestrom * 1000),
|
||||
$konfiguration
|
||||
)];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unbekannter Geraetetyp.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{Rohstatus: int, Istleistung_W: float}
|
||||
*/
|
||||
public static function dekodiereStatus(int $geraetetyp, string $antwort): array
|
||||
{
|
||||
$daten = json_decode($antwort, true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($daten)) {
|
||||
throw new RuntimeException('Die Geraeteantwort ist kein JSON-Objekt.');
|
||||
}
|
||||
|
||||
if ($geraetetyp === self::GO_E_ALT || $geraetetyp === self::GO_E_GEMINI) {
|
||||
if (!isset($daten['car'], $daten['nrg'])
|
||||
|| !is_numeric($daten['car'])
|
||||
|| !is_array($daten['nrg'])
|
||||
|| !isset($daten['nrg'][11])
|
||||
|| !is_numeric($daten['nrg'][11])
|
||||
) {
|
||||
throw new RuntimeException('Die go-e-Antwort enthaelt nicht alle Statusfelder.');
|
||||
}
|
||||
|
||||
$faktor = $geraetetyp === self::GO_E_ALT ? 10.0 : 1.0;
|
||||
|
||||
return [
|
||||
'Rohstatus' => (int) $daten['car'],
|
||||
'Istleistung_W' => (float) $daten['nrg'][11] * $faktor,
|
||||
];
|
||||
}
|
||||
|
||||
if ($geraetetyp === self::SMART_ME_PICO) {
|
||||
if (!isset($daten['State'], $daten['ActiveChargingPower'])
|
||||
|| !is_numeric($daten['State'])
|
||||
|| !is_numeric($daten['ActiveChargingPower'])
|
||||
) {
|
||||
throw new RuntimeException('Die Pico-Antwort enthaelt nicht alle Statusfelder.');
|
||||
}
|
||||
|
||||
return [
|
||||
'Rohstatus' => (int) $daten['State'],
|
||||
'Istleistung_W' => round((float) $daten['ActiveChargingPower'] * 1000.0),
|
||||
];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unbekannter Geraetetyp.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $konfiguration
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function anfrage(
|
||||
string $methode,
|
||||
string $url,
|
||||
array $konfiguration = []
|
||||
): array {
|
||||
$anfrage = ['Methode' => $methode, 'URL' => $url];
|
||||
if (str_starts_with($url, 'https://api.smart-me.com/')) {
|
||||
$anfrage['Benutzername'] = self::pflichtwert($konfiguration, 'Benutzername');
|
||||
$anfrage['Passwort'] = self::pflichtwert($konfiguration, 'Passwort');
|
||||
}
|
||||
|
||||
return $anfrage;
|
||||
}
|
||||
|
||||
/** @param array<string, string> $konfiguration */
|
||||
private static function lokaleBasisUrl(array $konfiguration): string
|
||||
{
|
||||
$adresse = trim(self::pflichtwert($konfiguration, 'Geraeteadresse'));
|
||||
if (str_contains($adresse, '/') || str_contains($adresse, '?')) {
|
||||
throw new InvalidArgumentException('Geraeteadresse muss ein Hostname oder eine IP-Adresse sein.');
|
||||
}
|
||||
|
||||
return 'http://' . $adresse;
|
||||
}
|
||||
|
||||
/** @param array<string, string> $konfiguration */
|
||||
private static function pflichtwert(array $konfiguration, string $name): string
|
||||
{
|
||||
$wert = trim($konfiguration[$name] ?? '');
|
||||
if ($wert === '') {
|
||||
throw new InvalidArgumentException($name . ' darf nicht leer sein.');
|
||||
}
|
||||
|
||||
return $wert;
|
||||
}
|
||||
}
|
||||
|
||||
final class LadestationTransport
|
||||
{
|
||||
/** @var callable(array<string, mixed>): string */
|
||||
private $anfrageAusfuehren;
|
||||
|
||||
/** @param callable(array<string, mixed>): string $anfrageAusfuehren */
|
||||
public function __construct(callable $anfrageAusfuehren)
|
||||
{
|
||||
$this->anfrageAusfuehren = $anfrageAusfuehren;
|
||||
}
|
||||
|
||||
/** @param array<string, string> $konfiguration */
|
||||
public function statusLesen(int $geraetetyp, array $konfiguration): string
|
||||
{
|
||||
return ($this->anfrageAusfuehren)(
|
||||
LadestationAdapter::statusAnfrage($geraetetyp, $konfiguration)
|
||||
);
|
||||
}
|
||||
|
||||
/** @param array<string, string> $konfiguration */
|
||||
public function ladestromSetzen(
|
||||
int $geraetetyp,
|
||||
array $konfiguration,
|
||||
int $ladestrom
|
||||
): void {
|
||||
foreach (LadestationAdapter::steuerAnfragen($geraetetyp, $konfiguration, $ladestrom) as $anfrage) {
|
||||
($this->anfrageAusfuehren)($anfrage);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user