Fixed performance

This commit is contained in:
Tim Lappe 2025-03-23 14:26:17 +01:00
parent 5f13dc83b6
commit 0ae1bdf76b
8 changed files with 62 additions and 182 deletions

View File

@ -70,8 +70,8 @@ final class CalendarController extends AbstractController
$icsContent = $this->calendarExportService->generateIcsContent($days, $entityHistory, $absences); $icsContent = $this->calendarExportService->generateIcsContent($days, $entityHistory, $absences);
$response = new Response($icsContent); $response = new Response($icsContent);
//$response->headers->set('Content-Type', 'text/calendar; charset=utf-8'); $response->headers->set('Content-Type', 'text/calendar; charset=utf-8');
//$response->headers->set('Content-Disposition', 'attachment; filename="work_calendar.ics"'); $response->headers->set('Content-Disposition', 'attachment; filename="work_calendar.ics"');
return $response; return $response;
} }

View File

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Core\Services\Home;
use DateTimeImmutable;
interface HomeEntityInterface
{
public function getId(): string;
public function getState(): mixed;
public function getName(): string;
public function getType(): HomeEntityType;
public function getLastChanged(): DateTimeImmutable;
public function getLastUpdated(): DateTimeImmutable;
}

View File

@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Core\Services\Home;
interface HomeServiceInterface
{
public function findEntity(string $entityId): HomeEntityInterface;
public function findAllEntities(): array;
public function callService(string $service, array $data = []): array;
}

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace App\HomeAssistant; namespace App\HomeAssistant;
use function explode; use function explode;
use function in_array;
final readonly class EntityState final readonly class EntityState
{ {
@ -30,17 +29,6 @@ final readonly class EntityState
$data['context'] ?? null, $data['context'] ?? null,
); );
} }
public function isOn(): bool
{
return in_array($this->state, ['on', 'home', 'open', 'unlocked', 'active'], true);
}
public function isOff(): bool
{
return in_array($this->state, ['off', 'away', 'closed', 'locked', 'inactive'], true);
}
public function getDomain(): string public function getDomain(): string
{ {
$parts = explode('.', $this->entityId, 2); $parts = explode('.', $this->entityId, 2);

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace App\HomeAssistant; namespace App\HomeAssistant;
use DateTimeInterface; use DateTimeInterface;
use function explode;
use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
@ -29,11 +28,6 @@ final readonly class HomeAssistantClient
return $this->request('GET', '/api/states'); return $this->request('GET', '/api/states');
} }
public function getServices(): array
{
return $this->request('GET', '/api/services');
}
public function getEntityState(string $entityId): array public function getEntityState(string $entityId): array
{ {
return $this->request('GET', "/api/states/{$entityId}"); return $this->request('GET', "/api/states/{$entityId}");
@ -49,25 +43,6 @@ final readonly class HomeAssistantClient
return $this->request('GET', "/api/history/period/{$startDate->format('Y-m-d\TH:i:s\Z')}?{$queryParams}")[0]; return $this->request('GET', "/api/history/period/{$startDate->format('Y-m-d\TH:i:s\Z')}?{$queryParams}")[0];
} }
public function callService(string $domain, string $service, array $data = []): array
{
return $this->request('POST', "/api/services/{$domain}/{$service}", $data);
}
public function turnOn(string $entityId): array
{
$domain = explode('.', $entityId)[0];
return $this->callService($domain, 'turn_on', ['entity_id' => $entityId]);
}
public function turnOff(string $entityId): array
{
$domain = explode('.', $entityId)[0];
return $this->callService($domain, 'turn_off', ['entity_id' => $entityId]);
}
private function request(string $method, string $endpoint, array $data = []): array private function request(string $method, string $endpoint, array $data = []): array
{ {
$options = [ $options = [

View File

@ -4,64 +4,13 @@ declare(strict_types=1);
namespace App\HomeAssistant; namespace App\HomeAssistant;
use App\Core\Services\Home\HomeEntityInterface; final readonly class HomeAssistantHomeService
use App\Core\Services\Home\HomeEntityType;
use App\Core\Services\Home\HomeServiceInterface;
use function array_filter;
use function array_keys;
use function array_map;
use function explode;
use function str_contains;
final readonly class HomeAssistantHomeService implements HomeServiceInterface
{ {
public function __construct( public function __construct(
private HomeAssistantClient $client, private HomeAssistantClient $client,
) { ) {
} }
public function findEntity(string $entityId): HomeEntityInterface
{
$entityState = $this->getEntityState($entityId);
return new HomeAssistantEntity($entityState);
}
public function findAllEntities(): array
{
$states = $this->getAllEntityStates();
if (empty($states)) {
throw new HomeAssistantException('No entities found');
}
$entities = [];
foreach ($states as $state) {
$type = HomeEntityType::tryFrom($state->getDomain());
if ($type === null) {
continue;
}
$entities[] = new HomeAssistantEntity($state);
}
return array_filter($entities, fn (HomeEntityInterface $entity) => $entity->getType() === HomeEntityType::LIGHT);
}
public function callService(string $service, array $data = []): array
{
// Extract domain and service name from the service string
if (str_contains($service, '.')) {
[$domain, $serviceName] = explode('.', $service, 2);
return $this->client->callService($domain, $serviceName, $data);
}
throw new HomeAssistantException("Invalid service format. Expected 'domain.service'");
}
/** /**
* @return EntityState[] * @return EntityState[]
*/ */
@ -81,41 +30,4 @@ final readonly class HomeAssistantHomeService implements HomeServiceInterface
return EntityState::fromArray($state); return EntityState::fromArray($state);
} }
/**
* @return EntityState[]
*/
public function getEntitiesByDomain(string $domain): array
{
$allStates = $this->getAllEntityStates();
return array_filter(
$allStates,
static fn (EntityState $state): bool => $state->getDomain() === $domain,
);
}
public function turnOn(string $entityId): EntityState
{
$this->client->turnOn($entityId);
return $this->getEntityState($entityId);
}
public function turnOff(string $entityId): EntityState
{
$this->client->turnOff($entityId);
return $this->getEntityState($entityId);
}
/**
* @return string[]
*/
public function getAvailableDomains(): array
{
$services = $this->client->getServices();
return array_keys($services);
}
} }

View File

@ -21,6 +21,27 @@ final class CalendarExportService
) { ) {
} }
/**
* @param array<array{
* date: DateTimeImmutable,
* weekday: int,
* weekdayName: string,
* weekNumber: int,
* class: string,
* isFtk: bool,
* isUrlaub: bool
* }> $days
* @param array<array{
* last_changed: string,
* state: string
* }> $entityHistory
*
* @param array<array{
* date: DateTimeImmutable,
* isFtk: bool,
* isUrlaub: bool
* }> $absences
*/
public function generateIcsContent(array $days, array $entityHistory, array $absences): string public function generateIcsContent(array $days, array $entityHistory, array $absences): string
{ {
$icsContent = [ $icsContent = [
@ -38,21 +59,16 @@ final class CalendarExportService
} }
$dayDate = clone $day['date']; $dayDate = clone $day['date'];
$startOfDay = DateTimeImmutable::createFromInterface($dayDate)->setTime(0, 0, 0);
$typicalEndOfDay = DateTimeImmutable::createFromInterface($dayDate)->setTime(17, 30, 0); $filteredEntityHistory = array_filter($entityHistory, function (array $state) use ($dayDate) {
return new DateTimeImmutable($state['last_changed']) >= $dayDate->setTime(0, 0, 0) && new DateTimeImmutable($state['last_changed']) <= $dayDate->setTime(23, 59, 59);
$filteredEntityHistory = array_filter($entityHistory, function (array $state) use ($startOfDay, $typicalEndOfDay) {
return $state['last_changed'] >= $startOfDay && $state['last_changed'] <= $typicalEndOfDay;
}); });
$startAndEndTimes = $this->getWorkTimesFromHomeAssistant($filteredEntityHistory, $dayDate); $startAndEndTimes = $this->getWorkTimesFromHomeAssistant($filteredEntityHistory);
if ($startAndEndTimes === null) { if ($startAndEndTimes === null || count($filteredEntityHistory) === 0) {
$eventStart = clone $day['date']; $eventStart = $dayDate->setTime(9, 0, 0);
$eventStart->setTime(9, 0, 0); $eventEnd = $dayDate->setTime(18, 0, 0);
$eventEnd = clone $day['date'];
$eventEnd->setTime(17, 30, 0);
} else { } else {
[$eventStart, $eventEnd] = $startAndEndTimes; [$eventStart, $eventEnd] = $startAndEndTimes;
} }
@ -80,17 +96,28 @@ final class CalendarExportService
return implode("\r\n", $icsContent); return implode("\r\n", $icsContent);
} }
private function getWorkTimesFromHomeAssistant(array $stateHistory, DateTimeInterface $date): ?array /**
* @param array<array{
* last_changed: string,
* state: string
* }> $states
*
* @return array{
* start: DateTimeImmutable,
* end: DateTimeImmutable
* }|null
*/
private function getWorkTimesFromHomeAssistant(array $states): ?array
{ {
try { try {
if (count($states) === 0) {
if (empty($stateHistory) || empty($stateHistory[0])) {
return null; return null;
} }
$states = array_values($states);
$startTime = null; $startTime = null;
$endTime = null; $endTime = null;
$states = $stateHistory[0];
foreach ($states as $i => $state) { foreach ($states as $i => $state) {
if ($state['state'] === self::OFFICE_STATE) { if ($state['state'] === self::OFFICE_STATE) {

View File

@ -7,6 +7,7 @@ use DateTime;
use DateTimeImmutable; use DateTimeImmutable;
use DatePeriod; use DatePeriod;
use DateInterval; use DateInterval;
use DateTimeInterface;
use IntlDateFormatter; use IntlDateFormatter;
final class CalendarService final class CalendarService
@ -25,6 +26,19 @@ final class CalendarService
); );
} }
/**
* @param string $startDate
* @param string $endDate
* @return array<array{
* date: DateTimeImmutable,
* weekday: int,
* weekdayName: string,
* weekNumber: int,
* class: string,
* isFtk: bool,
* isUrlaub: bool
* }>
*/
public function getAllDays(string $startDate, string $endDate): array public function getAllDays(string $startDate, string $endDate): array
{ {
$period = new DatePeriod( $period = new DatePeriod(
@ -37,7 +51,7 @@ final class CalendarService
foreach ($period as $date) { foreach ($period as $date) {
$dateObj = DateTimeImmutable::createFromInterface($date); $dateObj = DateTimeImmutable::createFromInterface($date);
$days[] = [ $days[] = [
'date' => $date, 'date' => $dateObj,
'weekday' => (int) $dateObj->format('N'), 'weekday' => (int) $dateObj->format('N'),
'weekdayName' => $this->formatter->format($date), 'weekdayName' => $this->formatter->format($date),
'weekNumber' => (int) $dateObj->format('W'), 'weekNumber' => (int) $dateObj->format('W'),
@ -95,7 +109,7 @@ final class CalendarService
return false; return false;
} }
private function isSameDay(DateTime $date1, DateTime $date2): bool private function isSameDay(DateTimeInterface $date1, DateTimeInterface $date2): bool
{ {
return $date1->format('Y-m-d') === $date2->format('Y-m-d'); return $date1->format('Y-m-d') === $date2->format('Y-m-d');
} }