$url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code === 200 && $response) {
return json_decode($response, true);
}
return null;
}
// Funktion zur Berechnung der Hintergrundfarbe
function calculateBackgroundColor() {
$lat = 50.8009;
$lon = 11.5875;
$dayColor = [224, 224, 224];
$nightColor = [96, 96, 96];
$twilightDuration = 2;
$now = time();
$sunrise = date_sunrise($now, SUNFUNCS_RET_TIMESTAMP, $lat, $lon);
$sunset = date_sunset($now, SUNFUNCS_RET_TIMESTAMP, $lat, $lon);
$current_hour = (float)date('G') + ((float)date('i') / 60);
$sunrise_hour = (float)date('G', $sunrise) + ((float)date('i', $sunrise) / 60);
$sunset_hour = (float)date('G', $sunset) + ((float)date('i', $sunset) / 60);
$t = 0;
if ($current_hour >= $sunset_hour && $current_hour <= $sunset_hour + $twilightDuration) {
$t = ($current_hour - $sunset_hour) / $twilightDuration;
} else if ($current_hour >= $sunrise_hour - $twilightDuration && $current_hour < $sunrise_hour) {
$t = 1 - (($current_hour - ($sunrise_hour - $twilightDuration)) / $twilightDuration);
} else if ($current_hour < $sunrise_hour - $twilightDuration || $current_hour > $sunset_hour + $twilightDuration) {
$t = 1;
}
$r = round($dayColor[0] + ($nightColor[0] - $dayColor[0]) * $t);
$g = round($dayColor[1] + ($nightColor[1] - $dayColor[1]) * $t);
$b = round($dayColor[2] + ($nightColor[2] - $dayColor[2]) * $t);
return "rgb($r,$g,$b)";
}
// Funktion zur Berechnung der Akku-Laufzeit
function calculateBatteryRuntime($usable_energy, $power_display, $battery_reserve_percent) {
// Wenn der Akku geladen wird (positive Leistung) oder keine nutzbare Energie vorhanden ist
if ($power_display >= 0 || $usable_energy <= 0) {
return '-';
}
// Umrechnung von W in kW und Berechnung der Laufzeit in Stunden
$power_kw = abs($power_display) / 1000;
if ($power_kw <= 0) {
return '-';
}
$runtime_hours = $usable_energy / $power_kw;
// Aktuelle Zeit
$current_time = time();
$end_time = $current_time + ($runtime_hours * 3600);
// Deutsche Wochentage
$german_weekdays = [
'Sun' => 'So',
'Mon' => 'Mo',
'Tue' => 'Di',
'Wed' => 'Mi',
'Thu' => 'Do',
'Fri' => 'Fr',
'Sat' => 'Sa'
];
$weekday_english = date('D', $end_time);
$weekday_german = $german_weekdays[$weekday_english] ?? $weekday_english;
// Formatierte Ausgabe: HH:mm Uhr\nWochentag TT.MM.JJJJ
$time_formatted = date('H:i', $end_time) . " Uhr";
$date_formatted = $weekday_german . ". " . date('d.m.y', $end_time);
return $time_formatted . "
" . $date_formatted;
}
// Funktion zur Bestimmung des Akku-Status-Textes
function getBatteryStatusText($power_display, $usable_soc) {
// Akku wird geladen (positive Leistung)
if ($power_display > 0) {
return ['label' => 'Akku:', 'value' => 'wird geladen'];
}
// Akku ist im Leerlauf (Leistung = 0) und hat noch 1-2% Restkapazität
elseif ($power_display == 0 && $usable_soc > 0 && $usable_soc <= 2) {
return ['label' => 'Akku:', 'value' => 'warten auf Ladung'];
}
// Akku ist leer
elseif ($usable_soc <= 0) {
return ['label' => 'Akku ist leer:', 'value' => 'Netzbezug'];
}
// Akku wird entladen (negative Leistung) und hat noch Kapazität
else {
return ['label' => 'Akku reicht bei aktuellem
Verbrauch bis circa:', 'value' => ''];
}
}
// Funktion zur Bestimmung des Akku-Icons basierend auf SoC
function getBatteryIcon($soc) {
if ($soc <= 15) {
return ''; // leer
} elseif ($soc <= 25) {
return ''; // fast leer
} elseif ($soc <= 50) {
return ''; // halb voll
} elseif ($soc <= 75) {
return ''; // fast voll
} else {
return ''; // voll
}
}
// Dauer formatieren
function format_duration($seconds) {
if (!$seconds || $seconds <= 0) return "–";
$h = floor($seconds / 3600);
$m = floor(($seconds % 3600) / 60);
$s = $seconds % 60;
return sprintf("%02d:%02d:%02d", $h, $m, $s);
}
// Modus übersetzen
function translate_mode($mode) {
$map = [
'off' => 'Aus',
'pv' => 'PV',
'minpv' => 'Min+PV',
'now' => 'Schnell'
];
return $map[strtolower($mode)] ?? ucfirst($mode);
}
// Shelly-Daten abrufen
$data_3em = fetchShellyData($endpoint_3em);
$data_pv = fetchShellyData($endpoint_pv);
// EVCC-Daten abrufen
$evcc_data = @file_get_contents($evcc_url);
$data_evcc = $evcc_data ? json_decode($evcc_data, true) : null;
$loadpoint = $data_evcc['loadpoints'][0] ?? [];
$battery = $data_evcc['battery'][0] ?? null;
// Daten verarbeiten für Netzanschluss
if ($data_3em && isset($data_3em["em:0"])) {
$em1 = $data_3em["em:0"];
$act_power_3em = (float)$em1["total_act_power"];
$voltage_avg_3em = ($em1["a_voltage"] + $em1["b_voltage"] + $em1["c_voltage"]) / 3;
$freq_3em = (float)$em1["a_freq"];
$pf_avg_3em = ($em1["a_pf"] + $em1["b_pf"] + $em1["c_pf"]) / 3;
$eff_current_3em = $voltage_avg_3em > 0 ? abs($act_power_3em / $voltage_avg_3em) : 0;
$act_power_f_3em = number_format($act_power_3em, 1, ',', '.');
$voltage_f_3em = number_format($voltage_avg_3em, 1, ',', '');
$freq_f_3em = number_format($freq_3em, 2, ',', '');
$pf_f_3em = number_format($pf_avg_3em, 2, ',', '');
$eff_current_f_3em = number_format($eff_current_3em, 2, ',', '');
} else {
$act_power_3em = 0;
$act_power_f_3em = "Fehler";
$voltage_f_3em = "Fehler";
$freq_f_3em = "Fehler";
$pf_f_3em = "Fehler";
$eff_current_f_3em = "Fehler";
}
// Daten verarbeiten für PV
if ($data_pv) {
$act_power_pv = abs($data_pv['em1:0']['act_power'] ?? 0);
$current_pv = number_format(($data_pv['em1:0']['current'] ?? 0), 2, ',', '');
$voltage_pv = number_format(($data_pv['em1:0']['voltage'] ?? 0), 1, ',', '');
$freq_pv = number_format(($data_pv['em1:0']['freq'] ?? 0), 2, ',', '');
$pf_pv = number_format(($data_pv['em1:0']['pf'] ?? 0), 2, ',', '');
$pv_kWp = 5.035;
$power_per_kWp = $pv_kWp > 0 ? $act_power_pv / $pv_kWp : 0;
$power_per_kWp_f = number_format($power_per_kWp, 1, ',', '.');
$act_power_formatted_pv = number_format($act_power_pv, 1, ',', '.');
} else {
$act_power_pv = 0;
$act_power_formatted_pv = "Fehler";
$current_pv = "Fehler";
$voltage_pv = "Fehler";
$freq_pv = "Fehler";
$pf_pv = "Fehler";
$power_per_kWp_f = "Fehler";
}
// EVCC-Daten verarbeiten
if ($data_evcc) {
// Tarifpreis
$tarif_price = $data_evcc['tariffGrid'] ?? null;
$tarif_display = ($tarif_price !== null)
? "Ladekosten-Summe (gerundet): Preis aktuell: " . number_format(round($tarif_price + 0.005, 2), 2, ',', '.') . " €/kWh."
: "Tarif nicht verfügbar";
// Batteriestand
$soc = $battery['soc'] ?? 0;
$battery_fill = max(0, min(100, $soc));
// Wallbox-Daten
$connected = !empty($loadpoint['connected']);
$charging = !empty($loadpoint['charging']);
$power_evcc = $loadpoint['chargePower'] ?? 0;
$chargedEnergy = $loadpoint['chargedEnergy'] ?? 0;
$modeRaw = htmlspecialchars($loadpoint['mode'] ?? 'Unbekannt');
$mode = translate_mode($modeRaw);
$vehicle = htmlspecialchars($loadpoint['vehicleTitle'] ?? ($loadpoint['vehicleName'] ?? ''));
$duration = format_duration($loadpoint['chargeDuration'] ?? 0);
// Ladekosten berechnen
$charged_kwh = $chargedEnergy / 1000.0;
$tarif_display_value = ($tarif_price !== null) ? round($tarif_price + 0.005, 2) : 0;
$preis_anzeige = round($charged_kwh * $tarif_display_value, 2);
// Status-Logik
if ($charging) {
$statusText = "⚡ lädt";
$statusColor = "orange";
} elseif ($connected) {
$statusText = "🚗 verbunden";
$statusColor = "#222";
} else {
$statusText = "✅ frei";
$statusColor = "green";
}
// Batterie-Daten
$capacity = $battery['capacity'] ?? 0;
$power_raw = $battery['power'] ?? 0;
$power_display = -1 * $power_raw;
$factor = $capacity / 100;
$energy_total = $soc * $factor;
$usable_soc = max(0, $soc - $battery_reserve_percent);
$usable_energy = $usable_soc * $factor;
$power_sign = $power_display >= 0 ? "+" : "-";
$soc_display = (round($soc, 1) == round($soc)) ? number_format($soc, 0, ',', '.') : number_format($soc, 1, ',', '.');
$usable_soc_display = (round($usable_soc, 1) == round($usable_soc)) ? number_format($usable_soc, 0, ',', '.') : number_format($usable_soc, 1, ',', '.');
// Akku-Laufzeit berechnen
$battery_runtime = calculateBatteryRuntime($usable_energy, $power_display, $battery_reserve_percent);
// Akku-Status-Text bestimmen
$battery_status = getBatteryStatusText($power_display, $usable_soc);
// Akku-Icon bestimmen
$battery_icon = getBatteryIcon($soc);
} else {
$statusText = "⚠️ EVCC nicht erreichbar";
$statusColor = "red";
$power_evcc = 0;
$soc = 0;
$battery_fill = 0;
$battery_runtime = '-';
$battery_status = ['label' => 'Akku:', 'value' => 'Daten nicht verfügbar'];
$battery_icon = ''; // leer bei Fehler
$mode = 'Unbekannt';
$duration = '–';
}
// Hintergrundfarbe berechnen
$backgroundColor = calculateBackgroundColor();
$timestamp = date("d.m.Y H:i:s");
// Variablen für Titel aktualisieren
$title_power_3em = ($act_power_3em >= 0 ? '+' : '') . $act_power_f_3em;
$title_power_pv = ($act_power_pv > 0 ? '+' : '') . $act_power_formatted_pv;
// Wenn AJAX-Abfrage, nur die Inhalte zurückgeben
if ($isAjax) {
ob_clean();
?>