Added home assistant
This commit is contained in:
parent
c60b5f653e
commit
abc6fae033
4
.env
4
.env
@ -7,3 +7,7 @@ APP_SECRET=akwhdaeuifzuieshuikjfjk
|
|||||||
###> application ###
|
###> application ###
|
||||||
REQUIRED_API_KEY=74857389798572903480209489024
|
REQUIRED_API_KEY=74857389798572903480209489024
|
||||||
###< application ###
|
###< application ###
|
||||||
|
|
||||||
|
HOME_ASSISTANT_URL=https://ha.strolap.com
|
||||||
|
HOME_ASSISTANT_TOKEN=
|
||||||
|
HOME_ASSISTANT_VERIFY_SSL=true
|
||||||
|
|||||||
3
config/secrets/dev/dev.HOME_ASSISTANT_TOKEN.ad10d3.php
Normal file
3
config/secrets/dev/dev.HOME_ASSISTANT_TOKEN.ad10d3.php
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?php // dev.HOME_ASSISTANT_TOKEN.ad10d3 on Sat, 15 Mar 2025 08:32:34 +0000
|
||||||
|
|
||||||
|
return "\x85\xCB\xCD4\xAC\x23\xEF\x87\x08\x7F\x14q\x93Kq\xB3Q\xDF\x27\xD3\xF1B5\x7C\x7CO\xADz\x86\x9EK\x1B\xE5b1\xEA\x26\xA9\x5Bk\xBC\xE0y\xE4V\xA1q\x8D\xC1\x1F\xA4\x98f\xBF\xCF\x29\x3C2\xEE.\xCAE\xFD1\xAD\xA3\xAE\xC5\x94\xD1\xA8\x9C\x0F\xFDy\xC5K\x9A\x8D\xAC\xA9\xF1_\xBA4\x87\x98\x8An\x84\x8A\x23\xCF\x89or\xBF\x09\xBC\x8En\x7F\x04\xEB\x25\x01l\x9Fc\x09\x03\xA25\x3E\x3AVVL2r\x89ifh\xE0N\x40\xCA\xFB\x20A\xCDtCVl\xDF\x01\x3E\x0FP\x5Er\xC3\x27\x82\xE6_\xBE\xE1bi\x20k\xDA\x09J\xF7\x91rKs\x98\xC2\x7B\x3Ac\xD9\xB6z\x2FHw\x84\xA1\x3B\x3FA\xDB\x83_cB\xFF\x94M\xC8\x86\xEE\x05\xA7U\xA1\x8A\x9A\x81\xB3\x85\xF6\xFBG\xC4\x0B\x8D\xA9\x98\x82\xC4\xB5\xA6\x95\xFE\x5D\xBE\x98EQ\xA9\xC3\xE8\xBB\xDD7\x133\x5C\x40\xD5\xC1\x5D\x3B";
|
||||||
@ -3,4 +3,5 @@
|
|||||||
return [
|
return [
|
||||||
'ABSENCE_API_ID' => null,
|
'ABSENCE_API_ID' => null,
|
||||||
'ABSENCE_API_KEY' => null,
|
'ABSENCE_API_KEY' => null,
|
||||||
|
'HOME_ASSISTANT_TOKEN' => null,
|
||||||
];
|
];
|
||||||
|
|||||||
57
src/Commands/ShowEntityStateHistoryCommand.php
Normal file
57
src/Commands/ShowEntityStateHistoryCommand.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\HomeAssistant\HomeAssistantClient;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:show-entity-state-history',
|
||||||
|
description: 'Shows the state history for a Home Assistant entity',
|
||||||
|
)]
|
||||||
|
final class ShowEntityStateHistoryCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly HomeAssistantClient $client,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->addArgument('entity-id', InputArgument::REQUIRED, 'The entity ID to show history for');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$entityId = $input->getArgument('entity-id');
|
||||||
|
|
||||||
|
$history = $this->client->getEntityStateHistory($entityId, new DateTimeImmutable('-10 days'), new DateTimeImmutable('+10 days'));
|
||||||
|
|
||||||
|
if (empty($history)) {
|
||||||
|
$io->error('No history found for entity ' . $entityId);
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->title('State History for ' . $entityId);
|
||||||
|
|
||||||
|
foreach ($history[0] as $state) {
|
||||||
|
$io->writeln(sprintf(
|
||||||
|
'%s: %s',
|
||||||
|
$state['last_changed'],
|
||||||
|
$state['state']
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/Core/Services/Home/HomeEntityInterface.php
Normal file
22
src/Core/Services/Home/HomeEntityInterface.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
25
src/Core/Services/Home/HomeEntityType.php
Normal file
25
src/Core/Services/Home/HomeEntityType.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Core\Services\Home;
|
||||||
|
|
||||||
|
enum HomeEntityType
|
||||||
|
{
|
||||||
|
case LIGHT;
|
||||||
|
case SWITCH;
|
||||||
|
case SENSOR;
|
||||||
|
case BINARY_SENSOR;
|
||||||
|
case CLIMATE;
|
||||||
|
case MEDIA_PLAYER;
|
||||||
|
case SCENE;
|
||||||
|
case SCRIPT;
|
||||||
|
case AUTOMATION;
|
||||||
|
case CAMERA;
|
||||||
|
case COVER;
|
||||||
|
case FAN;
|
||||||
|
case LOCK;
|
||||||
|
case VACUUM;
|
||||||
|
case WEATHER;
|
||||||
|
case ZONE;
|
||||||
|
}
|
||||||
14
src/Core/Services/Home/HomeServiceInterface.php
Normal file
14
src/Core/Services/Home/HomeServiceInterface.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
55
src/HomeAssistant/EntityState.php
Normal file
55
src/HomeAssistant/EntityState.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\HomeAssistant;
|
||||||
|
|
||||||
|
use function explode;
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
final readonly class EntityState
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $entityId,
|
||||||
|
public string $state,
|
||||||
|
public array $attributes,
|
||||||
|
public string $lastChanged,
|
||||||
|
public string $lastUpdated,
|
||||||
|
public array|string|null $context = null,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromArray(array $data): self
|
||||||
|
{
|
||||||
|
return new self(
|
||||||
|
$data['entity_id'],
|
||||||
|
$data['state'],
|
||||||
|
$data['attributes'] ?? [],
|
||||||
|
$data['last_changed'] ?? '',
|
||||||
|
$data['last_updated'] ?? '',
|
||||||
|
$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
|
||||||
|
{
|
||||||
|
$parts = explode('.', $this->entityId, 2);
|
||||||
|
|
||||||
|
return $parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->attributes['friendly_name'] ?? $this->entityId;
|
||||||
|
}
|
||||||
|
}
|
||||||
107
src/HomeAssistant/HomeAssistantClient.php
Normal file
107
src/HomeAssistant/HomeAssistantClient.php
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\HomeAssistant;
|
||||||
|
|
||||||
|
use DateTimeInterface;
|
||||||
|
use function explode;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||||
|
|
||||||
|
final readonly class HomeAssistantClient
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private HttpClientInterface $httpClient,
|
||||||
|
#[Autowire('%env(HOME_ASSISTANT_URL)%')]
|
||||||
|
private string $baseUrl,
|
||||||
|
#[Autowire('%env(HOME_ASSISTANT_TOKEN)%')]
|
||||||
|
private string $token,
|
||||||
|
#[Autowire('%env(HOME_ASSISTANT_VERIFY_SSL)%')]
|
||||||
|
private bool $verifySSL,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStates(): array
|
||||||
|
{
|
||||||
|
return $this->request('GET', '/api/states');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getServices(): array
|
||||||
|
{
|
||||||
|
return $this->request('GET', '/api/services');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityState(string $entityId): array
|
||||||
|
{
|
||||||
|
return $this->request('GET', "/api/states/{$entityId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityStateHistory(string $entityId, DateTimeInterface $startDate, DateTimeInterface $endDate): array
|
||||||
|
{
|
||||||
|
$queryParams = http_build_query([
|
||||||
|
'end_time' => $endDate->format('Y-m-d\TH:i:s\Z'),
|
||||||
|
'filter_entity_id' => $entityId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->request('GET', "/api/history/period/{$startDate->format('Y-m-d\TH:i:s\Z')}?{$queryParams}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
$options = [
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => "Bearer {$this->token}",
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
'verify_peer' => $this->verifySSL,
|
||||||
|
'verify_host' => $this->verifySSL,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!empty($data)) {
|
||||||
|
$options['json'] = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->httpClient->request(
|
||||||
|
$method,
|
||||||
|
$this->baseUrl.$endpoint,
|
||||||
|
$options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->handleResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleResponse(ResponseInterface $response): array
|
||||||
|
{
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
|
||||||
|
if ($statusCode >= 200 && $statusCode < 300) {
|
||||||
|
return $response->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = $response->getContent(false);
|
||||||
|
|
||||||
|
throw new HomeAssistantException($content, $statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/HomeAssistant/HomeAssistantEntity.php
Normal file
67
src/HomeAssistant/HomeAssistantEntity.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\HomeAssistant;
|
||||||
|
|
||||||
|
use App\Core\Services\Home\HomeEntityInterface;
|
||||||
|
use App\Core\Services\Home\HomeEntityType;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
|
final readonly class HomeAssistantEntity implements HomeEntityInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private EntityState $entityState,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return $this->entityState->entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getState(): mixed
|
||||||
|
{
|
||||||
|
return $this->entityState->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->entityState->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): HomeEntityType
|
||||||
|
{
|
||||||
|
$domain = $this->entityState->getDomain();
|
||||||
|
|
||||||
|
return match ($domain) {
|
||||||
|
'light' => HomeEntityType::LIGHT,
|
||||||
|
'switch' => HomeEntityType::SWITCH,
|
||||||
|
'sensor' => HomeEntityType::SENSOR,
|
||||||
|
'binary_sensor' => HomeEntityType::BINARY_SENSOR,
|
||||||
|
'climate' => HomeEntityType::CLIMATE,
|
||||||
|
'media_player' => HomeEntityType::MEDIA_PLAYER,
|
||||||
|
'scene' => HomeEntityType::SCENE,
|
||||||
|
'script' => HomeEntityType::SCRIPT,
|
||||||
|
'automation' => HomeEntityType::AUTOMATION,
|
||||||
|
'camera' => HomeEntityType::CAMERA,
|
||||||
|
'cover' => HomeEntityType::COVER,
|
||||||
|
'fan' => HomeEntityType::FAN,
|
||||||
|
'lock' => HomeEntityType::LOCK,
|
||||||
|
'vacuum' => HomeEntityType::VACUUM,
|
||||||
|
'weather' => HomeEntityType::WEATHER,
|
||||||
|
'zone' => HomeEntityType::ZONE,
|
||||||
|
default => throw new HomeAssistantException("Unknown entity type: {$domain}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastChanged(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return new DateTimeImmutable($this->entityState->lastChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastUpdated(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return new DateTimeImmutable($this->entityState->lastUpdated);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/HomeAssistant/HomeAssistantException.php
Normal file
11
src/HomeAssistant/HomeAssistantException.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\HomeAssistant;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
final class HomeAssistantException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
121
src/HomeAssistant/HomeAssistantHomeService.php
Normal file
121
src/HomeAssistant/HomeAssistantHomeService.php
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\HomeAssistant;
|
||||||
|
|
||||||
|
use App\Core\Services\Home\HomeEntityInterface;
|
||||||
|
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(
|
||||||
|
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[]
|
||||||
|
*/
|
||||||
|
public function getAllEntityStates(): array
|
||||||
|
{
|
||||||
|
$states = $this->client->getStates();
|
||||||
|
|
||||||
|
return array_map(
|
||||||
|
static fn (array $state): EntityState => EntityState::fromArray($state),
|
||||||
|
$states,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityState(string $entityId): EntityState
|
||||||
|
{
|
||||||
|
$state = $this->client->getEntityState($entityId);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,17 +2,25 @@
|
|||||||
|
|
||||||
namespace App\Service;
|
namespace App\Service;
|
||||||
|
|
||||||
use App\Model\Absence;
|
use App\HomeAssistant\HomeAssistantClient;
|
||||||
use DateTime;
|
use DateTimeImmutable;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use DateTimeZone;
|
use DateTimeZone;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
final class CalendarExportService
|
final class CalendarExportService
|
||||||
{
|
{
|
||||||
private const DUSSELDORF_ADDRESS = 'CHECK24 \nHeinricht-Heine-Allee 53\n40213 Düsseldorf\nDeutschland';
|
private const string DUSSELDORF_ADDRESS = 'CHECK24 \nHeinricht-Heine-Allee 53\n40213 Düsseldorf\nDeutschland';
|
||||||
private const KAMEN_ADDRESS = 'Privat \nHeidkamp 21\n59174 Kamen\nDeutschland';
|
private const string KAMEN_ADDRESS = 'Privat \nHeidkamp 21\n59174 Kamen\nDeutschland';
|
||||||
private const COMPANY_NAME = 'CHECK24';
|
private const string COMPANY_NAME = 'CHECK24';
|
||||||
private const TIMEZONE = 'Europe/Berlin';
|
private const string TIMEZONE = 'Europe/Berlin';
|
||||||
|
private const string PERSON_ENTITY = 'person.tim';
|
||||||
|
private const string OFFICE_STATE = 'CHECK24';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly HomeAssistantClient $homeAssistantClient
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
public function generateIcsContent(array $days, array $absences): string
|
public function generateIcsContent(array $days, array $absences): string
|
||||||
{
|
{
|
||||||
@ -30,11 +38,18 @@ final class CalendarExportService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$dayDate = clone $day['date'];
|
||||||
|
$startAndEndTimes = $this->getWorkTimesFromHomeAssistant($dayDate);
|
||||||
|
|
||||||
|
if ($startAndEndTimes === null) {
|
||||||
$eventStart = clone $day['date'];
|
$eventStart = clone $day['date'];
|
||||||
$eventStart->setTime(9, 0, 0);
|
$eventStart->setTime(9, 0, 0);
|
||||||
|
|
||||||
$eventEnd = clone $day['date'];
|
$eventEnd = clone $day['date'];
|
||||||
$eventEnd->setTime(17, 30, 0);
|
$eventEnd->setTime(17, 30, 0);
|
||||||
|
} else {
|
||||||
|
[$eventStart, $eventEnd] = $startAndEndTimes;
|
||||||
|
}
|
||||||
|
|
||||||
$isHomeOffice = $day['isFtk'] ?? false;
|
$isHomeOffice = $day['isFtk'] ?? false;
|
||||||
$location = $isHomeOffice ? self::KAMEN_ADDRESS : self::DUSSELDORF_ADDRESS;
|
$location = $isHomeOffice ? self::KAMEN_ADDRESS : self::DUSSELDORF_ADDRESS;
|
||||||
@ -46,7 +61,7 @@ final class CalendarExportService
|
|||||||
|
|
||||||
$icsContent[] = 'BEGIN:VEVENT';
|
$icsContent[] = 'BEGIN:VEVENT';
|
||||||
$icsContent[] = 'UID:' . $this->generateUid($eventStart);
|
$icsContent[] = 'UID:' . $this->generateUid($eventStart);
|
||||||
$icsContent[] = 'DTSTAMP:' . $this->formatDateTime(new DateTime('now', new DateTimeZone(self::TIMEZONE)));
|
$icsContent[] = 'DTSTAMP:' . $this->formatDateTime(new DateTimeImmutable('now', new DateTimeZone(self::TIMEZONE)));
|
||||||
$icsContent[] = 'DTSTART;TZID=' . self::TIMEZONE . ':' . $this->formatDateTimeLocal($eventStart);
|
$icsContent[] = 'DTSTART;TZID=' . self::TIMEZONE . ':' . $this->formatDateTimeLocal($eventStart);
|
||||||
$icsContent[] = 'DTEND;TZID=' . self::TIMEZONE . ':' . $this->formatDateTimeLocal($eventEnd);
|
$icsContent[] = 'DTEND;TZID=' . self::TIMEZONE . ':' . $this->formatDateTimeLocal($eventEnd);
|
||||||
$icsContent[] = 'SUMMARY:' . $name;
|
$icsContent[] = 'SUMMARY:' . $name;
|
||||||
@ -59,16 +74,67 @@ final class CalendarExportService
|
|||||||
return implode("\r\n", $icsContent);
|
return implode("\r\n", $icsContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getWorkTimesFromHomeAssistant(DateTimeInterface $date): ?array
|
||||||
|
{
|
||||||
|
$startOfDay = DateTimeImmutable::createFromInterface($date)->setTime(0, 0, 0);
|
||||||
|
$typicalEndOfDay = DateTimeImmutable::createFromInterface($date)->setTime(17, 30, 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stateHistory = $this->homeAssistantClient->getEntityStateHistory(
|
||||||
|
self::PERSON_ENTITY,
|
||||||
|
$startOfDay,
|
||||||
|
$typicalEndOfDay
|
||||||
|
);
|
||||||
|
|
||||||
|
if (empty($stateHistory) || empty($stateHistory[0])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$startTime = null;
|
||||||
|
$endTime = null;
|
||||||
|
$states = $stateHistory[0];
|
||||||
|
|
||||||
|
foreach ($states as $i => $state) {
|
||||||
|
if ($state['state'] === self::OFFICE_STATE) {
|
||||||
|
$startTime = new DateTimeImmutable($state['last_changed']);
|
||||||
|
|
||||||
|
for ($j = $i + 1; $j < count($states); $j++) {
|
||||||
|
if ($states[$j]['state'] !== self::OFFICE_STATE) {
|
||||||
|
$endTime = new DateTimeImmutable($states[$j]['last_changed']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($startTime !== null && $endTime === null) {
|
||||||
|
$endTime = clone $startTime;
|
||||||
|
$endTime = $endTime->modify('+9 hours');
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($startTime === null || $endTime === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$startTime = $startTime->setTimezone(new DateTimeZone('Europe/Berlin'));
|
||||||
|
$endTime = $endTime->setTimezone(new DateTimeZone('Europe/Berlin'));
|
||||||
|
|
||||||
|
return [$startTime, $endTime];
|
||||||
|
} catch (RuntimeException) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function isWeekend(array $day): bool
|
private function isWeekend(array $day): bool
|
||||||
{
|
{
|
||||||
return $day['weekday'] >= 6;
|
return $day['weekday'] >= 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatDateTime(DateTimeInterface $dateTime): string
|
private function formatDateTime(DateTimeImmutable $dateTime): string
|
||||||
{
|
{
|
||||||
$utcDateTime = clone $dateTime;
|
return $dateTime->setTimezone(new DateTimeZone('UTC'))->format('Ymd\THis\Z');
|
||||||
$utcDateTime->setTimezone(new DateTimeZone('UTC'));
|
|
||||||
return $utcDateTime->format('Ymd\THis\Z');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatDateTimeLocal(DateTimeInterface $dateTime): string
|
private function formatDateTimeLocal(DateTimeInterface $dateTime): string
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user