diff --git a/src/API/LanApi.php b/src/API/LanApi.php index 99f6fe8..ac14bd7 100644 --- a/src/API/LanApi.php +++ b/src/API/LanApi.php @@ -896,7 +896,7 @@ private function callRequest( 'type' => 'lan-api', 'request' => [ 'method' => $request->getMethod(), - 'url' => $request->getUri(), + 'url' => strval($request->getUri()), 'headers' => $request->getHeaders(), 'body' => $request->getContent(), ], @@ -935,7 +935,7 @@ function (Message\ResponseInterface $response) use ($deferred, $request): void { 'type' => 'lan-api', 'request' => [ 'method' => $request->getMethod(), - 'url' => $request->getUri(), + 'url' => strval($request->getUri()), 'headers' => $request->getHeaders(), 'body' => $request->getContent(), ], @@ -993,7 +993,7 @@ static function (Throwable $ex) use ($deferred, $request): void { 'type' => 'lan-api', 'request' => [ 'method' => $request->getMethod(), - 'url' => $request->getUri(), + 'url' => strval($request->getUri()), 'headers' => $request->getHeaders(), 'body' => $request->getContent(), ], diff --git a/src/Clients/Client.php b/src/Clients/Client.php index 70c8bfe..717c4d9 100644 --- a/src/Clients/Client.php +++ b/src/Clients/Client.php @@ -15,11 +15,6 @@ namespace FastyBird\Connector\NsPanel\Clients; -use FastyBird\Connector\NsPanel\Entities; -use FastyBird\Library\Metadata\Entities as MetadataEntities; -use FastyBird\Module\Devices\Entities as DevicesEntities; -use React\Promise; - /** * Connector client interface * @@ -41,14 +36,4 @@ public function connect(): void; */ public function disconnect(): void; - /** - * Write data to device - */ - public function writeChannelProperty( - Entities\NsPanelDevice $device, - Entities\NsPanelChannel $channel, - // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong - DevicesEntities\Channels\Properties\Dynamic|DevicesEntities\Channels\Properties\Mapped|MetadataEntities\DevicesModule\ChannelDynamicProperty|MetadataEntities\DevicesModule\ChannelMappedProperty $property, - ): Promise\PromiseInterface; - } diff --git a/src/Clients/Device.php b/src/Clients/Device.php index d06e268..ed90571 100644 --- a/src/Clients/Device.php +++ b/src/Clients/Device.php @@ -17,15 +17,13 @@ use FastyBird\Connector\NsPanel; use FastyBird\Connector\NsPanel\API; -use FastyBird\Connector\NsPanel\Consumers; use FastyBird\Connector\NsPanel\Entities; use FastyBird\Connector\NsPanel\Exceptions; use FastyBird\Connector\NsPanel\Helpers; use FastyBird\Connector\NsPanel\Queries; +use FastyBird\Connector\NsPanel\Queue; use FastyBird\Connector\NsPanel\Types; -use FastyBird\Connector\NsPanel\Writers; use FastyBird\Library\Bootstrap\Helpers as BootstrapHelpers; -use FastyBird\Library\Metadata\Entities as MetadataEntities; use FastyBird\Library\Metadata\Exceptions as MetadataExceptions; use FastyBird\Library\Metadata\Types as MetadataTypes; use FastyBird\Module\Devices\Entities as DevicesEntities; @@ -42,6 +40,7 @@ use function is_string; use function preg_match; use function sprintf; +use function strval; /** * Connector third-party device client @@ -54,33 +53,26 @@ final class Device implements Client { - use TPropertiesMapper; use Nette\SmartObject; private API\LanApi $lanApiApi; public function __construct( - protected readonly Helpers\Property $propertyStateHelper, - protected readonly DevicesModels\Channels\Properties\PropertiesRepository $channelsPropertiesRepository, private readonly Entities\NsPanelConnector $connector, - private readonly Consumers\Messages $consumer, + private readonly Queue\Queue $queue, private readonly Helpers\Loader $loader, private readonly Helpers\Entity $entityHelper, - private readonly Writers\Writer $writer, private readonly NsPanel\Logger $logger, private readonly DevicesModels\Devices\DevicesRepository $devicesRepository, API\LanApiFactory $lanApiApiFactory, ) { - $this->lanApiApi = $lanApiApiFactory->create( - $this->connector->getIdentifier(), - ); + $this->lanApiApi = $lanApiApiFactory->create($this->connector->getIdentifier()); } /** * @throws DevicesExceptions\InvalidState * @throws DevicesExceptions\Terminate - * @throws Exceptions\InvalidArgument * @throws Exceptions\InvalidState * @throws Exceptions\Runtime * @throws MetadataExceptions\InvalidArgument @@ -115,9 +107,9 @@ public function connect(): void if ( !array_key_exists($device->getDisplayCategory()->getValue(), (array) $categoriesMetadata) ) { - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $device->getGateway()->getIdentifier(), @@ -145,7 +137,6 @@ public function connect(): void $deviceCapabilities = []; $capabilities = []; - $state = []; $tags = []; foreach ($device->getChannels() as $channel) { @@ -170,12 +161,6 @@ public function connect(): void 'name' => $capabilityName, ]; - $mapped = $this->mapChannelToState($channel); - - if ($mapped !== null) { - $state = array_merge($state, $mapped); - } - foreach ($channel->getProperties() as $property) { if ( $property instanceof DevicesEntities\Channels\Properties\Variable @@ -207,9 +192,9 @@ public function connect(): void // Device have to have configured all required capabilities if (array_diff($requiredCapabilities, $deviceCapabilities) !== []) { - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $device->getIdentifier(), @@ -226,15 +211,16 @@ public function connect(): void 'name' => $device->getName() ?? $device->getIdentifier(), 'display_category' => $device->getDisplayCategory()->getValue(), 'capabilities' => $capabilities, - 'state' => $state, + 'state' => [], 'tags' => $tags, 'manufacturer' => $device->getManufacturer(), 'model' => $device->getModel(), 'firmware_version' => $device->getFirmwareVersion(), 'service_address' => sprintf( - 'http://%s:%d/do-directive/%s', + 'http://%s:%d/do-directive/%s/%s', Helpers\Network::getLocalAddress(), $device->getConnector()->getPort(), + $device->getGateway()->getId()->toString(), $device->getId()->toString(), ), 'online' => true, // Virtual device is always online @@ -279,9 +265,9 @@ public function connect(): void ); if ($device !== null) { - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $device->getIdentifier(), @@ -290,11 +276,12 @@ public function connect(): void ), ); - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceSynchronisation::class, + Entities\Messages\StoreThirdPartyDevice::class, [ 'connector' => $this->connector->getId()->toString(), + 'gateway' => $gateway->getId()->toString(), 'identifier' => $device->getIdentifier(), 'gateway_identifier' => $endpoint->getSerialNumber(), ], @@ -328,6 +315,10 @@ public function connect(): void if ($ex instanceof Exceptions\LanApiCall) { $extra = [ 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval( + $ex->getRequest()->getUri(), + ) : null, 'body' => $ex->getRequest()?->getBody()->getContents(), ], 'response' => [ @@ -335,9 +326,9 @@ public function connect(): void ], ]; - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -347,9 +338,9 @@ public function connect(): void ); } else { - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -434,6 +425,10 @@ function (Entities\API\Response\GetSubDevices $response) use ($gateway, $ipAddre if ($ex instanceof Exceptions\LanApiCall) { $extra = [ 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval( + $ex->getRequest()->getUri(), + ) : null, 'body' => $ex->getRequest()?->getBody()->getContents(), ], 'response' => [ @@ -441,9 +436,9 @@ function (Entities\API\Response\GetSubDevices $response) use ($gateway, $ipAddre ], ]; - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -453,9 +448,9 @@ function (Entities\API\Response\GetSubDevices $response) use ($gateway, $ipAddre ); } else { - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -495,6 +490,10 @@ function (Entities\API\Response\GetSubDevices $response) use ($gateway, $ipAddre if ($ex instanceof Exceptions\LanApiCall) { $extra = [ 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval( + $ex->getRequest()->getUri(), + ) : null, 'body' => $ex->getRequest()?->getBody()->getContents(), ], 'response' => [ @@ -502,9 +501,9 @@ function (Entities\API\Response\GetSubDevices $response) use ($gateway, $ipAddre ], ]; - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -514,9 +513,9 @@ function (Entities\API\Response\GetSubDevices $response) use ($gateway, $ipAddre ); } else { - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -546,10 +545,9 @@ function (Entities\API\Response\GetSubDevices $response) use ($gateway, $ipAddre }); }); - $this->writer->connect($this->connector, $this); } catch (Throwable $ex) { $this->logger->error( - 'An unhandled error occur', + 'An unhandled error occurred', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, 'type' => 'device-client', @@ -563,9 +561,9 @@ function (Entities\API\Response\GetSubDevices $response) use ($gateway, $ipAddre ], ); - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -585,8 +583,6 @@ function (Entities\API\Response\GetSubDevices $response) use ($gateway, $ipAddre */ public function disconnect(): void { - $this->writer->disconnect($this->connector, $this); - $findDevicesQuery = new Queries\FindGatewayDevices(); $findDevicesQuery->forConnector($this->connector); @@ -606,9 +602,9 @@ public function disconnect(): void $findDevicesQuery, Entities\Devices\ThirdPartyDevice::class, ) as $device) { - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $device->getIdentifier(), @@ -658,6 +654,10 @@ public function disconnect(): void if ($ex instanceof Exceptions\LanApiCall) { $extra = [ 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval( + $ex->getRequest()->getUri(), + ) : null, 'body' => $ex->getRequest()?->getBody()->getContents(), ], 'response' => [ @@ -686,7 +686,7 @@ public function disconnect(): void }); } catch (Throwable $ex) { $this->logger->error( - 'An unhandled error occur', + 'An unhandled error occurred', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, 'type' => 'device-client', @@ -702,9 +702,9 @@ public function disconnect(): void } } - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -715,81 +715,4 @@ public function disconnect(): void } } - /** - * @throws DevicesExceptions\InvalidState - * @throws Exceptions\InvalidArgument - * @throws Exceptions\InvalidState - * @throws Exceptions\Runtime - * @throws MetadataExceptions\InvalidArgument - * @throws MetadataExceptions\InvalidState - */ - public function writeChannelProperty( - Entities\NsPanelDevice $device, - Entities\NsPanelChannel $channel, - // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong - DevicesEntities\Channels\Properties\Dynamic|DevicesEntities\Channels\Properties\Mapped|MetadataEntities\DevicesModule\ChannelDynamicProperty|MetadataEntities\DevicesModule\ChannelMappedProperty $property, - ): Promise\PromiseInterface - { - if (!$device instanceof Entities\Devices\ThirdPartyDevice) { - return Promise\reject( - new Exceptions\InvalidArgument('Only third-party device could be updated'), - ); - } - - if ($device->getGateway()->getIpAddress() === null || $device->getGateway()->getAccessToken() === null) { - $this->consumer->append( - $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, - [ - 'connector' => $this->connector->getId()->toString(), - 'identifier' => $device->getGateway()->getIdentifier(), - 'state' => MetadataTypes\ConnectionState::STATE_ALERT, - ], - ), - ); - - return Promise\reject( - new Exceptions\InvalidArgument('Device assigned NS Panel is not configured'), - ); - } - - try { - $serialNumber = $device->getGatewayIdentifier(); - - if ($serialNumber === null) { - $this->consumer->append( - $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, - [ - 'connector' => $this->connector->getId()->toString(), - 'identifier' => $device->getIdentifier(), - 'state' => MetadataTypes\ConnectionState::STATE_ALERT, - ], - ), - ); - - return Promise\reject(new Exceptions\InvalidState('Device gateway identifier is not configured')); - } - } catch (Throwable) { - return Promise\reject(new Exceptions\InvalidState('Could not get device gateway identifier')); - } - - $mapped = $this->mapChannelToState($channel); - - if ($mapped === null) { - return Promise\reject(new Exceptions\InvalidState('Device capability state could not be created')); - } - - try { - return $this->lanApiApi->reportDeviceState( - $serialNumber, - $mapped, - $device->getGateway()->getIpAddress(), - $device->getGateway()->getAccessToken(), - ); - } catch (Throwable $ex) { - return Promise\reject(new Exceptions\InvalidState('Request could not be handled', $ex->getCode(), $ex)); - } - } - } diff --git a/src/Clients/Discovery.php b/src/Clients/Discovery.php index 525dc9e..80fdab6 100644 --- a/src/Clients/Discovery.php +++ b/src/Clients/Discovery.php @@ -18,11 +18,11 @@ use Evenement; use FastyBird\Connector\NsPanel; use FastyBird\Connector\NsPanel\API; -use FastyBird\Connector\NsPanel\Consumers; use FastyBird\Connector\NsPanel\Entities; use FastyBird\Connector\NsPanel\Exceptions; use FastyBird\Connector\NsPanel\Helpers; use FastyBird\Connector\NsPanel\Queries; +use FastyBird\Connector\NsPanel\Queue; use FastyBird\Library\Bootstrap\Helpers as BootstrapHelpers; use FastyBird\Library\Metadata\Exceptions as MetadataExceptions; use FastyBird\Library\Metadata\Types as MetadataTypes; @@ -56,11 +56,11 @@ final class Discovery implements Evenement\EventEmitterInterface public function __construct( private readonly Entities\NsPanelConnector $connector, private readonly API\LanApiFactory $lanApiApiFactory, - private readonly Consumers\Messages $consumer, + private readonly Queue\Queue $queue, private readonly Helpers\Entity $entityHelper, + private readonly NsPanel\Logger $logger, private readonly DevicesModels\Devices\DevicesRepository $devicesRepository, private readonly EventLoop\LoopInterface $eventLoop, - private readonly NsPanel\Logger $logger, ) { } @@ -226,9 +226,9 @@ private function handleFoundSubDevices( ), ); - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DiscoveredSubDevice::class, + Entities\Messages\StoreSubDevice::class, array_merge( [ 'connector' => $this->connector->getId()->toString(), diff --git a/src/Clients/Gateway.php b/src/Clients/Gateway.php index ae6ad7a..b7d7261 100644 --- a/src/Clients/Gateway.php +++ b/src/Clients/Gateway.php @@ -18,29 +18,26 @@ use DateTimeInterface; use FastyBird\Connector\NsPanel; use FastyBird\Connector\NsPanel\API; -use FastyBird\Connector\NsPanel\Consumers; use FastyBird\Connector\NsPanel\Entities; use FastyBird\Connector\NsPanel\Exceptions; use FastyBird\Connector\NsPanel\Helpers; use FastyBird\Connector\NsPanel\Queries; -use FastyBird\Connector\NsPanel\Writers; +use FastyBird\Connector\NsPanel\Queue; use FastyBird\DateTimeFactory; use FastyBird\Library\Bootstrap\Helpers as BootstrapHelpers; -use FastyBird\Library\Metadata\Entities as MetadataEntities; use FastyBird\Library\Metadata\Exceptions as MetadataExceptions; use FastyBird\Library\Metadata\Types as MetadataTypes; -use FastyBird\Module\Devices\Entities as DevicesEntities; use FastyBird\Module\Devices\Exceptions as DevicesExceptions; use FastyBird\Module\Devices\Models as DevicesModels; use FastyBird\Module\Devices\Utilities as DevicesUtilities; use Nette; use React\EventLoop; -use React\Promise; use Throwable; use function array_key_exists; use function in_array; use function is_string; use function preg_match; +use function strval; /** * Connector gateway client @@ -53,7 +50,6 @@ final class Gateway implements Client { - use TPropertiesMapper; use Nette\SmartObject; private const HANDLER_START_DELAY = 2.0; @@ -77,16 +73,12 @@ final class Gateway implements Client private EventLoop\TimerInterface|null $handlerTimer = null; public function __construct( - protected readonly Helpers\Property $propertyStateHelper, - protected readonly Helpers\Entity $entityHelper, - protected readonly DevicesModels\Channels\Properties\PropertiesRepository $channelsPropertiesRepository, private readonly Entities\NsPanelConnector $connector, - private readonly Consumers\Messages $consumer, - private readonly Writers\Writer $writer, + private readonly Queue\Queue $queue, + private readonly Helpers\Entity $entityHelper, private readonly NsPanel\Logger $logger, private readonly DevicesModels\Devices\DevicesRepository $devicesRepository, private readonly DevicesUtilities\DeviceConnection $deviceConnectionManager, - private readonly DevicesUtilities\ChannelPropertiesStates $channelPropertiesStates, private readonly DateTimeFactory\Factory $dateTimeFactory, private readonly EventLoop\LoopInterface $eventLoop, API\LanApiFactory $lanApiApiFactory, @@ -110,8 +102,6 @@ function (): void { $this->registerLoopHandler(); }, ); - - $this->writer->connect($this->connector, $this); } public function disconnect(): void @@ -121,82 +111,6 @@ public function disconnect(): void $this->handlerTimer = null; } - - $this->writer->disconnect($this->connector, $this); - } - - /** - * @throws DevicesExceptions\InvalidState - * @throws Exceptions\InvalidArgument - * @throws Exceptions\InvalidState - * @throws MetadataExceptions\InvalidArgument - * @throws MetadataExceptions\InvalidState - */ - public function writeChannelProperty( - Entities\NsPanelDevice $device, - Entities\NsPanelChannel $channel, - // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong - DevicesEntities\Channels\Properties\Dynamic|DevicesEntities\Channels\Properties\Mapped|MetadataEntities\DevicesModule\ChannelDynamicProperty|MetadataEntities\DevicesModule\ChannelMappedProperty $property, - ): Promise\PromiseInterface - { - if (!$device instanceof Entities\Devices\SubDevice) { - return Promise\reject( - new Exceptions\InvalidArgument('Only sub-device could be updated'), - ); - } - - if (!$property instanceof DevicesEntities\Channels\Properties\Dynamic) { - return Promise\reject( - new Exceptions\InvalidArgument('Only dynamic properties could be updated'), - ); - } - - if (!$property->isSettable()) { - return Promise\reject(new Exceptions\InvalidArgument('Provided property is not writable')); - } - - if ($device->getGateway()->getIpAddress() === null || $device->getGateway()->getAccessToken() === null) { - return Promise\reject( - new Exceptions\InvalidArgument('Device assigned NS Panel is not configured'), - ); - } - - $state = $this->channelPropertiesStates->getValue($property); - - if ($state === null) { - return Promise\reject( - new Exceptions\InvalidArgument('Property state could not be found. Nothing to write'), - ); - } - - $expectedValue = DevicesUtilities\ValueHelper::flattenValue($state->getExpectedValue()); - - if ($expectedValue === null) { - return Promise\reject( - new Exceptions\InvalidArgument('Property expected value is not set. Nothing to write'), - ); - } - - $mapped = $this->mapChannelToState($channel); - - if ($mapped === null) { - return Promise\reject(new Exceptions\InvalidArgument('Device capability state could not be created')); - } - - if ($state->isPending() === true) { - try { - return $this->lanApiApi->setSubDeviceState( - $device->getIdentifier(), - $mapped, - $device->getGateway()->getIpAddress(), - $device->getGateway()->getAccessToken(), - ); - } catch (Throwable $ex) { - return Promise\reject(new Exceptions\InvalidState('Request could not be handled', $ex->getCode(), $ex)); - } - } - - return Promise\reject(new Exceptions\InvalidArgument('Provided property state is in invalid state')); } /** @@ -259,9 +173,9 @@ private function readDeviceInformation(Entities\Devices\Gateway $gateway): bool $gateway->getIpAddress() === null || $gateway->getAccessToken() === null ) { - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -297,9 +211,9 @@ private function readDeviceInformation(Entities\Devices\Gateway $gateway): bool ->then(function () use ($gateway): void { $this->processedDevicesCommands[$gateway->getIdentifier()][self::CMD_HEARTBEAT] = $this->dateTimeFactory->getNow(); - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -323,6 +237,8 @@ private function readDeviceInformation(Entities\Devices\Gateway $gateway): bool 'id' => $gateway->getId()->toString(), ], 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval($ex->getRequest()->getUri()) : null, 'body' => $ex->getRequest()?->getBody()->getContents(), ], 'response' => [ @@ -331,9 +247,9 @@ private function readDeviceInformation(Entities\Devices\Gateway $gateway): bool ], ); - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -360,9 +276,9 @@ private function readDeviceInformation(Entities\Devices\Gateway $gateway): bool ], ); - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -373,7 +289,7 @@ private function readDeviceInformation(Entities\Devices\Gateway $gateway): bool }); } catch (Throwable $ex) { $this->logger->error( - 'An unhandled error occur', + 'An unhandled error occurred', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, 'type' => 'gateway-client', @@ -405,9 +321,9 @@ private function readDeviceState(Entities\Devices\Gateway $gateway): bool $gateway->getIpAddress() === null || $gateway->getAccessToken() === null ) { - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -443,9 +359,9 @@ private function readDeviceState(Entities\Devices\Gateway $gateway): bool ->then(function (Entities\API\Response\GetSubDevices $subDevices) use ($gateway): void { $this->processedDevicesCommands[$gateway->getIdentifier()][self::CMD_STATE] = $this->dateTimeFactory->getNow(); - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -460,9 +376,9 @@ private function readDeviceState(Entities\Devices\Gateway $gateway): bool continue; } - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $subDevice->getSerialNumber(), @@ -496,11 +412,12 @@ private function readDeviceState(Entities\Devices\Gateway $gateway): bool } } - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceState::class, + Entities\Messages\StoreDeviceState::class, [ 'connector' => $this->connector->getId()->toString(), + 'gateway' => $gateway->getId()->toString(), 'identifier' => $subDevice->getSerialNumber(), 'state' => $state, ], @@ -523,6 +440,8 @@ private function readDeviceState(Entities\Devices\Gateway $gateway): bool 'id' => $gateway->getId()->toString(), ], 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval($ex->getRequest()->getUri()) : null, 'body' => $ex->getRequest()?->getBody()->getContents(), ], 'response' => [ @@ -531,9 +450,9 @@ private function readDeviceState(Entities\Devices\Gateway $gateway): bool ], ); - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -560,9 +479,9 @@ private function readDeviceState(Entities\Devices\Gateway $gateway): bool ], ); - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceOnline::class, + Entities\Messages\StoreDeviceConnectionState::class, [ 'connector' => $this->connector->getId()->toString(), 'identifier' => $gateway->getIdentifier(), @@ -573,7 +492,7 @@ private function readDeviceState(Entities\Devices\Gateway $gateway): bool }); } catch (Throwable $ex) { $this->logger->error( - 'An unhandled error occur', + 'An unhandled error occurred', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, 'type' => 'gateway-client', diff --git a/src/Commands/Devices.php b/src/Commands/Devices.php index 05ea15f..18c3695 100644 --- a/src/Commands/Devices.php +++ b/src/Commands/Devices.php @@ -260,6 +260,8 @@ private function createGateway( 'type' => 'devices-cmd', 'exception' => BootstrapHelpers\Logger::buildException($ex), 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval($ex->getRequest()->getUri()) : null, 'body' => $ex->getRequest()?->getBody()->getContents(), ], 'connector' => [ @@ -491,6 +493,8 @@ private function editGateway( 'type' => 'devices-cmd', 'exception' => BootstrapHelpers\Logger::buildException($ex), 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval($ex->getRequest()->getUri()) : null, 'body' => $ex->getRequest()?->getBody()->getContents(), ], 'connector' => [ @@ -3412,6 +3416,8 @@ function (string|null $answer) use ($connector): Entities\Commands\GatewayInfo { 'type' => 'devices-cmd', 'exception' => BootstrapHelpers\Logger::buildException($ex), 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval($ex->getRequest()->getUri()) : null, 'body' => $ex->getRequest()?->getBody()->getContents(), ], 'connector' => [ diff --git a/src/Connector/Connector.php b/src/Connector/Connector.php index 9c407b5..738a4af 100644 --- a/src/Connector/Connector.php +++ b/src/Connector/Connector.php @@ -17,16 +17,21 @@ use FastyBird\Connector\NsPanel; use FastyBird\Connector\NsPanel\Clients; -use FastyBird\Connector\NsPanel\Consumers; use FastyBird\Connector\NsPanel\Entities; use FastyBird\Connector\NsPanel\Exceptions; +use FastyBird\Connector\NsPanel\Helpers; +use FastyBird\Connector\NsPanel\Queries; +use FastyBird\Connector\NsPanel\Queue; use FastyBird\Connector\NsPanel\Servers; +use FastyBird\Connector\NsPanel\Writers; +use FastyBird\Library\Bootstrap\Helpers as BootstrapHelpers; use FastyBird\Library\Metadata\Exceptions as MetadataExceptions; use FastyBird\Library\Metadata\Types as MetadataTypes; use FastyBird\Module\Devices\Connectors as DevicesConnectors; use FastyBird\Module\Devices\Entities as DevicesEntities; use FastyBird\Module\Devices\Events as DevicesEvents; use FastyBird\Module\Devices\Exceptions as DevicesExceptions; +use FastyBird\Module\Devices\Models as DevicesModels; use Nette; use Psr\EventDispatcher as PsrEventDispatcher; use React\EventLoop; @@ -55,7 +60,9 @@ final class Connector implements DevicesConnectors\Connector private Servers\Server|null $server = null; - private EventLoop\TimerInterface|null $consumerTimer = null; + private Writers\Writer|null $writer = null; + + private EventLoop\TimerInterface|null $consumersTimer = null; /** * @param array $clientsFactories @@ -65,8 +72,13 @@ public function __construct( private readonly array $clientsFactories, private readonly Clients\DiscoveryFactory $discoveryClientFactory, private readonly Servers\ServerFactory $serverFactory, + private readonly Writers\WriterFactory $writerFactory, + private readonly Helpers\Entity $entityHelper, + private readonly Queue\Queue $queue, + private readonly Queue\Consumers $consumers, private readonly NsPanel\Logger $logger, - private readonly Consumers\Messages $consumer, + private readonly DevicesModels\Devices\DevicesRepository $devicesRepository, + private readonly DevicesModels\Channels\ChannelsRepository $channelsRepository, private readonly EventLoop\LoopInterface $eventLoop, private readonly PsrEventDispatcher\EventDispatcherInterface|null $dispatcher = null, ) @@ -85,7 +97,7 @@ public function execute(): void $mode = $this->connector->getClientMode(); - $this->logger->debug( + $this->logger->info( 'Starting NS Panel connector service', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, @@ -122,14 +134,78 @@ public function execute(): void $this->server->connect(); } - $this->consumerTimer = $this->eventLoop->addPeriodicTimer( + $this->writer = $this->writerFactory->create($this->connector); + $this->writer->connect(); + + $this->consumersTimer = $this->eventLoop->addPeriodicTimer( self::QUEUE_PROCESSING_INTERVAL, async(function (): void { - $this->consumer->consume(); + $this->consumers->consume(); }), ); - $this->logger->debug( + if ( + $mode->equalsValue(NsPanel\Types\ClientMode::BOTH) + || $mode->equalsValue(NsPanel\Types\ClientMode::DEVICE) + ) { + $this->eventLoop->addTimer(1, function (): void { + $findDevicesQuery = new Queries\FindThirdPartyDevices(); + $findDevicesQuery->forConnector($this->connector); + + $devices = $this->devicesRepository->findAllBy( + $findDevicesQuery, + Entities\Devices\ThirdPartyDevice::class, + ); + + foreach ($devices as $device) { + $findChannelsQuery = new Queries\FindChannels(); + $findChannelsQuery->forDevice($device); + + $channels = $this->channelsRepository->findAllBy( + $findChannelsQuery, + Entities\NsPanelChannel::class, + ); + + foreach ($channels as $channel) { + try { + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\WriteThirdPartyDeviceState::class, + [ + 'connector' => $this->connector->getId()->toString(), + 'device' => $device->getId()->toString(), + 'channel' => $channel->getId()->toString(), + ], + ), + ); + } catch (Exceptions\Runtime $ex) { + $this->logger->error( + 'Could report device initial state to NS Panel', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-third-party-device-state-message-consumer', + 'exception' => BootstrapHelpers\Logger::buildException($ex), + 'connector' => [ + 'id' => $this->connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $channel->getId()->toString(), + ], + ], + ); + } + } + } + }); + } + + $this->logger->info( 'NS Panel connector service has been started', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, @@ -150,7 +226,7 @@ public function discover(): void { assert($this->connector instanceof Entities\NsPanelConnector); - $this->logger->debug( + $this->logger->info( 'Starting NS Panel connector discovery', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, @@ -176,14 +252,14 @@ public function discover(): void $this->clients[] = $client; - $this->consumerTimer = $this->eventLoop->addPeriodicTimer( + $this->consumersTimer = $this->eventLoop->addPeriodicTimer( self::QUEUE_PROCESSING_INTERVAL, async(function (): void { - $this->consumer->consume(); + $this->consumers->consume(); }), ); - $this->logger->debug( + $this->logger->info( 'NS Panel connector discovery has been started', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, @@ -203,11 +279,15 @@ public function terminate(): void $client->disconnect(); } - if ($this->consumerTimer !== null && $this->consumer->isEmpty()) { - $this->eventLoop->cancelTimer($this->consumerTimer); + $this->server?->disconnect(); + + $this->writer?->disconnect(); + + if ($this->consumersTimer !== null && $this->queue->isEmpty()) { + $this->eventLoop->cancelTimer($this->consumersTimer); } - $this->logger->debug( + $this->logger->info( 'NS Panel connector has been terminated', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, @@ -221,7 +301,7 @@ public function terminate(): void public function hasUnfinishedTasks(): bool { - return !$this->consumer->isEmpty() && $this->consumerTimer !== null; + return !$this->queue->isEmpty() && $this->consumersTimer !== null; } } diff --git a/src/Controllers/DirectiveController.php b/src/Controllers/DirectiveController.php index ebba2f3..51637d7 100644 --- a/src/Controllers/DirectiveController.php +++ b/src/Controllers/DirectiveController.php @@ -16,11 +16,11 @@ namespace FastyBird\Connector\NsPanel\Controllers; use FastyBird\Connector\NsPanel; -use FastyBird\Connector\NsPanel\Consumers; use FastyBird\Connector\NsPanel\Entities; use FastyBird\Connector\NsPanel\Exceptions; use FastyBird\Connector\NsPanel\Helpers; use FastyBird\Connector\NsPanel\Queries; +use FastyBird\Connector\NsPanel\Queue; use FastyBird\Connector\NsPanel\Router; use FastyBird\Connector\NsPanel\Servers; use FastyBird\Connector\NsPanel\Types; @@ -54,7 +54,7 @@ final class DirectiveController extends BaseController private const SET_DEVICE_STATE_MESSAGE_SCHEMA_FILENAME = 'set_device_state.json'; public function __construct( - private readonly Consumers\Messages $consumer, + private readonly Queue\Queue $queue, private readonly Helpers\Entity $entityHelper, private readonly DevicesModels\Devices\DevicesRepository $devicesRepository, private readonly MetadataSchemas\Validator $schemaValidator, @@ -82,6 +82,7 @@ public function process( 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, 'type' => 'characteristics-controller', 'request' => [ + 'method' => $request->getMethod(), 'address' => $request->getServerParams()['REMOTE_ADDR'], 'path' => $request->getUri()->getPath(), 'query' => $request->getQueryParams(), @@ -106,8 +107,11 @@ public function process( $body = $request->getBody()->getContents(); + // At first, try to load gateway + $gateway = $this->findGateway($request, $connectorId); + // At first, try to load device - $device = $this->findDevice($request); + $device = $this->findDevice($request, $connectorId, $gateway); try { $body = $this->schemaValidator->validate( @@ -178,11 +182,12 @@ public function process( } } - $this->consumer->append( + $this->queue->append( $this->entityHelper->create( - Entities\Messages\DeviceState::class, + Entities\Messages\StoreDeviceState::class, [ 'connector' => $connectorId->toString(), + 'gateway' => $gateway->getId()->toString(), 'identifier' => $device->getIdentifier(), 'state' => $state, ], @@ -237,24 +242,55 @@ public function process( * @throws DevicesExceptions\InvalidState * @throws Exceptions\ServerRequestError */ - private function findDevice(Message\ServerRequestInterface $request): Entities\Devices\ThirdPartyDevice + private function findGateway( + Message\ServerRequestInterface $request, + Uuid\UuidInterface $connectorId, + ): Entities\Devices\Gateway { - $id = strval($request->getAttribute(Router\Router::URL_DEVICE_ID)); + $id = strval($request->getAttribute(Router\Router::URL_GATEWAY_ID)); - $connectorId = strval($request->getAttribute(Servers\Http::REQUEST_ATTRIBUTE_CONNECTOR)); + try { + $findQuery = new Queries\FindGatewayDevices(); + $findQuery->byId(Uuid\Uuid::fromString($id)); + $findQuery->byConnectorId($connectorId); - if (!Uuid\Uuid::isValid($connectorId)) { + $gateway = $this->devicesRepository->findOneBy($findQuery, Entities\Devices\Gateway::class); + + if ($gateway === null) { + throw new Exceptions\ServerRequestError( + $request, + Types\ServerStatus::get(Types\ServerStatus::ENDPOINT_UNREACHABLE), + 'Device gateway could could not be found', + ); + } + } catch (Uuid\Exception\InvalidUuidStringException) { throw new Exceptions\ServerRequestError( $request, Types\ServerStatus::get(Types\ServerStatus::ENDPOINT_UNREACHABLE), - 'Connector id could not be determined', + 'Device gateway could could not be found', ); } + return $gateway; + } + + /** + * @throws DevicesExceptions\InvalidState + * @throws Exceptions\ServerRequestError + */ + private function findDevice( + Message\ServerRequestInterface $request, + Uuid\UuidInterface $connectorId, + Entities\Devices\Gateway $gateway, + ): Entities\Devices\ThirdPartyDevice + { + $id = strval($request->getAttribute(Router\Router::URL_DEVICE_ID)); + try { $findQuery = new Queries\FindThirdPartyDevices(); $findQuery->byId(Uuid\Uuid::fromString($id)); - $findQuery->byConnectorId(Uuid\Uuid::fromString($connectorId)); + $findQuery->byConnectorId($connectorId); + $findQuery->forParent($gateway); $device = $this->devicesRepository->findOneBy($findQuery, Entities\Devices\ThirdPartyDevice::class); diff --git a/src/DI/NsPanelExtension.php b/src/DI/NsPanelExtension.php index a3b9c64..eb8c2e5 100644 --- a/src/DI/NsPanelExtension.php +++ b/src/DI/NsPanelExtension.php @@ -21,19 +21,18 @@ use FastyBird\Connector\NsPanel\Clients; use FastyBird\Connector\NsPanel\Commands; use FastyBird\Connector\NsPanel\Connector; -use FastyBird\Connector\NsPanel\Consumers; use FastyBird\Connector\NsPanel\Controllers; use FastyBird\Connector\NsPanel\Entities; use FastyBird\Connector\NsPanel\Helpers; use FastyBird\Connector\NsPanel\Hydrators; use FastyBird\Connector\NsPanel\Middleware; +use FastyBird\Connector\NsPanel\Queue; use FastyBird\Connector\NsPanel\Router; use FastyBird\Connector\NsPanel\Schemas; use FastyBird\Connector\NsPanel\Servers; use FastyBird\Connector\NsPanel\Subscribers; use FastyBird\Connector\NsPanel\Writers; use FastyBird\Library\Bootstrap\Boot as BootstrapBoot; -use FastyBird\Library\Exchange\DI as ExchangeDI; use FastyBird\Module\Devices\DI as DevicesDI; use Nette; use Nette\DI; @@ -92,38 +91,36 @@ public function loadConfiguration(): void ->setType(NsPanel\Logger::class) ->setAutowired(false); - $writer = null; + /** + * WRITERS + */ if ($configuration->writer === Writers\Event::NAME) { - $writer = $builder->addDefinition($this->prefix('writers.event'), new DI\Definitions\ServiceDefinition()) - ->setType(Writers\Event::class) - ->setArguments([ - 'logger' => $logger, - ]) - ->setAutowired(false); + $builder->addFactoryDefinition($this->prefix('writers.event')) + ->setImplement(Writers\EventFactory::class) + ->getResultDefinition() + ->setType(Writers\Event::class); } elseif ($configuration->writer === Writers\Exchange::NAME) { - $writer = $builder->addDefinition($this->prefix('writers.exchange'), new DI\Definitions\ServiceDefinition()) - ->setType(Writers\Exchange::class) - ->setArguments([ - 'logger' => $logger, - ]) - ->setAutowired(false) - ->addTag(ExchangeDI\ExchangeExtension::CONSUMER_STATE, false); + $builder->addFactoryDefinition($this->prefix('writers.exchange')) + ->setImplement(Writers\ExchangeFactory::class) + ->getResultDefinition() + ->setType(Writers\Exchange::class); } elseif ($configuration->writer === Writers\Periodic::NAME) { - $writer = $builder->addDefinition($this->prefix('writers.periodic'), new DI\Definitions\ServiceDefinition()) - ->setType(Writers\Periodic::class) - ->setArguments([ - 'logger' => $logger, - ]) - ->setAutowired(false); + $builder->addFactoryDefinition($this->prefix('writers.periodic')) + ->setImplement(Writers\PeriodicFactory::class) + ->getResultDefinition() + ->setType(Writers\Periodic::class); } + /** + * CLIENTS + */ + $builder->addFactoryDefinition($this->prefix('clients.gateway')) ->setImplement(Clients\GatewayFactory::class) ->getResultDefinition() ->setType(Clients\Gateway::class) ->setArguments([ - 'writer' => $writer, 'logger' => $logger, ]); @@ -132,7 +129,6 @@ public function loadConfiguration(): void ->getResultDefinition() ->setType(Clients\Device::class) ->setArguments([ - 'writer' => $writer, 'logger' => $logger, ]); @@ -144,6 +140,10 @@ public function loadConfiguration(): void 'logger' => $logger, ]); + /** + * API + */ + $builder->addFactoryDefinition($this->prefix('api.lanApi')) ->setImplement(API\LanApiFactory::class) ->getResultDefinition() @@ -163,59 +163,98 @@ public function loadConfiguration(): void 'logger' => $logger, ]); + /** + * MESSAGES QUEUE + */ + $builder->addDefinition( - $this->prefix('consumers.messages.deviceState'), + $this->prefix('queue.consumers.store.deviceState'), new DI\Definitions\ServiceDefinition(), ) - ->setType(Consumers\Messages\DeviceState::class) + ->setType(Queue\Consumers\StoreDeviceState::class) ->setArguments([ 'useExchange' => $configuration->writer === Writers\Exchange::NAME, 'logger' => $logger, ]); $builder->addDefinition( - $this->prefix('consumers.messages.deviceOnline'), + $this->prefix('queue.consumers.store.deviceConnectionState'), + new DI\Definitions\ServiceDefinition(), + ) + ->setType(Queue\Consumers\StoreDeviceConnectionState::class) + ->setArguments([ + 'logger' => $logger, + ]); + + $builder->addDefinition( + $this->prefix('queue.consumers.store.thirdPartyDevice'), new DI\Definitions\ServiceDefinition(), ) - ->setType(Consumers\Messages\DeviceOnline::class) + ->setType(Queue\Consumers\StoreThirdPartyDevice::class) ->setArguments([ 'logger' => $logger, ]); $builder->addDefinition( - $this->prefix('consumers.messages.deviceSynchronisation'), + $this->prefix('queue.consumers.store.subDevice'), new DI\Definitions\ServiceDefinition(), ) - ->setType(Consumers\Messages\DeviceSynchronisation::class) + ->setType(Queue\Consumers\StoreSubDevice::class) ->setArguments([ 'logger' => $logger, ]); $builder->addDefinition( - $this->prefix('consumers.messages.subDeviceDiscovery'), + $this->prefix('queue.consumers.write.subDeviceState'), new DI\Definitions\ServiceDefinition(), ) - ->setType(Consumers\Messages\DiscoveredSubDevice::class) + ->setType(Queue\Consumers\WriteSubDeviceState::class) ->setArguments([ 'logger' => $logger, ]); $builder->addDefinition( - $this->prefix('consumers.messages'), + $this->prefix('queue.consumers.write.thirdPartyDeviceState'), new DI\Definitions\ServiceDefinition(), ) - ->setType(Consumers\Messages::class) + ->setType(Queue\Consumers\WriteThirdPartyDeviceState::class) ->setArguments([ - 'consumers' => $builder->findByType(Consumers\Consumer::class), 'logger' => $logger, ]); + $builder->addDefinition( + $this->prefix('queue.consumers'), + new DI\Definitions\ServiceDefinition(), + ) + ->setType(Queue\Consumers::class) + ->setArguments([ + 'consumers' => $builder->findByType(Queue\Consumer::class), + 'logger' => $logger, + ]); + + $builder->addDefinition( + $this->prefix('queue.queue'), + new DI\Definitions\ServiceDefinition(), + ) + ->setType(Queue\Queue::class) + ->setArguments([ + 'logger' => $logger, + ]); + + /** + * SUBSCRIBERS + */ + $builder->addDefinition($this->prefix('subscribers.properties'), new DI\Definitions\ServiceDefinition()) ->setType(Subscribers\Properties::class); $builder->addDefinition($this->prefix('subscribers.controls'), new DI\Definitions\ServiceDefinition()) ->setType(Subscribers\Controls::class); + /** + * JSON-API SCHEMAS + */ + $builder->addDefinition( $this->prefix('schemas.connector.nsPanel'), new DI\Definitions\ServiceDefinition(), @@ -246,6 +285,10 @@ public function loadConfiguration(): void ) ->setType(Schemas\NsPanelChannel::class); + /** + * JSON-API HYDRATORS + */ + $builder->addDefinition( $this->prefix('hydrators.connector.nsPanel'), new DI\Definitions\ServiceDefinition(), @@ -273,15 +316,20 @@ public function loadConfiguration(): void $builder->addDefinition($this->prefix('hydrators.channel.nsPanel'), new DI\Definitions\ServiceDefinition()) ->setType(Hydrators\NsPanelChannel::class); + /** + * HELPERS + */ + $builder->addDefinition($this->prefix('helpers.loader'), new DI\Definitions\ServiceDefinition()) ->setType(Helpers\Loader::class); - $builder->addDefinition($this->prefix('helpers.property'), new DI\Definitions\ServiceDefinition()) - ->setType(Helpers\Property::class); - $builder->addDefinition($this->prefix('helpers.entity'), new DI\Definitions\ServiceDefinition()) ->setType(Helpers\Entity::class); + /** + * SERVERS + */ + $router = $builder->addDefinition($this->prefix('http.router'), new DI\Definitions\ServiceDefinition()) ->setType(Router\Router::class) ->setAutowired(false); @@ -298,18 +346,9 @@ public function loadConfiguration(): void ->addSetup('setLogger', [$logger]) ->addTag('nette.inject'); - $builder->addFactoryDefinition($this->prefix('connector')) - ->setImplement(Connector\ConnectorFactory::class) - ->addTag( - DevicesDI\DevicesExtension::CONNECTOR_TYPE_TAG, - Entities\NsPanelConnector::TYPE, - ) - ->getResultDefinition() - ->setType(Connector\Connector::class) - ->setArguments([ - 'clientsFactories' => $builder->findByType(Clients\ClientFactory::class), - 'logger' => $logger, - ]); + /** + * COMMANDS + */ $builder->addDefinition($this->prefix('commands.initialize'), new DI\Definitions\ServiceDefinition()) ->setType(Commands\Initialize::class) @@ -328,6 +367,23 @@ public function loadConfiguration(): void $builder->addDefinition($this->prefix('commands.discovery'), new DI\Definitions\ServiceDefinition()) ->setType(Commands\Discovery::class); + + /** + * CONNECTOR + */ + + $builder->addFactoryDefinition($this->prefix('connector')) + ->setImplement(Connector\ConnectorFactory::class) + ->addTag( + DevicesDI\DevicesExtension::CONNECTOR_TYPE_TAG, + Entities\NsPanelConnector::TYPE, + ) + ->getResultDefinition() + ->setType(Connector\Connector::class) + ->setArguments([ + 'clientsFactories' => $builder->findByType(Clients\ClientFactory::class), + 'logger' => $logger, + ]); } /** diff --git a/src/Entities/Messages/CapabilityDescription.php b/src/Entities/Messages/CapabilityDescription.php index e1e9d6f..93f1209 100644 --- a/src/Entities/Messages/CapabilityDescription.php +++ b/src/Entities/Messages/CapabilityDescription.php @@ -22,7 +22,7 @@ use stdClass; /** - * Device capability definition + * Device capability description definition * * @package FastyBird:NsPanelConnector! * @subpackage Entities diff --git a/src/Entities/Messages/CapabilityState.php b/src/Entities/Messages/CapabilityState.php index ff49551..d56a39b 100644 --- a/src/Entities/Messages/CapabilityState.php +++ b/src/Entities/Messages/CapabilityState.php @@ -22,7 +22,7 @@ use Orisai\ObjectMapper; /** - * Device state definition + * Device capability state definition * * @package FastyBird:NsPanelConnector! * @subpackage Entities @@ -51,7 +51,7 @@ public function __construct( new ObjectMapper\Rules\DateTimeValue(format: DateTimeInterface::ATOM), new ObjectMapper\Rules\NullValue(castEmptyString: true), ])] - // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong + // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong private readonly int|float|string|bool|Types\MotorCalibrationPayload|Types\MotorControlPayload|Types\PowerPayload|Types\PressPayload|Types\StartupPayload|Types\TogglePayload|null $value, #[ObjectMapper\Rules\AnyOf([ new ObjectMapper\Rules\StringValue(notEmpty: true), diff --git a/src/Entities/Messages/DeviceOnline.php b/src/Entities/Messages/StoreDeviceConnectionState.php similarity index 89% rename from src/Entities/Messages/DeviceOnline.php rename to src/Entities/Messages/StoreDeviceConnectionState.php index 34c1156..1add1c5 100644 --- a/src/Entities/Messages/DeviceOnline.php +++ b/src/Entities/Messages/StoreDeviceConnectionState.php @@ -1,7 +1,7 @@ */ -final class DeviceOnline extends Device implements Entity +final class StoreDeviceConnectionState extends Device implements Entity { public function __construct( diff --git a/src/Entities/Messages/DeviceState.php b/src/Entities/Messages/StoreDeviceState.php similarity index 75% rename from src/Entities/Messages/DeviceState.php rename to src/Entities/Messages/StoreDeviceState.php index 77e3f82..1307341 100644 --- a/src/Entities/Messages/DeviceState.php +++ b/src/Entities/Messages/StoreDeviceState.php @@ -1,7 +1,7 @@ */ -final class DeviceState extends Device implements Entity +final class StoreDeviceState extends Device implements Entity { /** @@ -36,6 +37,8 @@ final class DeviceState extends Device implements Entity */ public function __construct( Uuid\UuidInterface $connector, + #[BootstrapObjectMapper\Rules\UuidValue()] + private readonly Uuid\UuidInterface $gateway, string $identifier, #[ObjectMapper\Rules\ArrayOf( new ObjectMapper\Rules\MappedObjectValue(CapabilityState::class), @@ -46,6 +49,11 @@ public function __construct( parent::__construct($connector, $identifier); } + public function getGateway(): Uuid\UuidInterface + { + return $this->gateway; + } + /** * @return array */ @@ -60,6 +68,7 @@ public function getState(): array public function toArray(): array { return array_merge(parent::toArray(), [ + 'gateway' => $this->getGateway()->toString(), 'state' => array_map( static fn (CapabilityState $state): array => $state->toArray(), $this->getState(), diff --git a/src/Entities/Messages/DiscoveredSubDevice.php b/src/Entities/Messages/StoreSubDevice.php similarity index 95% rename from src/Entities/Messages/DiscoveredSubDevice.php rename to src/Entities/Messages/StoreSubDevice.php index 939a4c7..54219f7 100644 --- a/src/Entities/Messages/DiscoveredSubDevice.php +++ b/src/Entities/Messages/StoreSubDevice.php @@ -1,7 +1,7 @@ */ -final class DiscoveredSubDevice implements Entity +final class StoreSubDevice implements Entity { /** @@ -43,7 +43,7 @@ public function __construct( private readonly Uuid\UuidInterface $gateway, #[ObjectMapper\Rules\StringValue(notEmpty: true)] #[ObjectMapper\Modifiers\FieldName('serial_number')] - private readonly string $serialNumber, + private readonly string $identifier, #[ObjectMapper\Rules\StringValue(notEmpty: true)] private readonly string $name, #[ObjectMapper\Rules\StringValue(notEmpty: true)] @@ -129,9 +129,9 @@ public function getGateway(): Uuid\UuidInterface return $this->gateway; } - public function getSerialNumber(): string + public function getIdentifier(): string { - return $this->serialNumber; + return $this->identifier; } public function getName(): string @@ -221,7 +221,7 @@ public function isInSubnet(): bool|null public function toArray(): array { return [ - 'serial_number' => $this->getSerialNumber(), + 'serial_number' => $this->getIdentifier(), 'third_serial_number' => $this->getThirdSerialNumber()?->toString(), 'service_address' => $this->getServiceAddress(), 'name' => $this->getName(), diff --git a/src/Entities/Messages/DeviceSynchronisation.php b/src/Entities/Messages/StoreThirdPartyDevice.php similarity index 72% rename from src/Entities/Messages/DeviceSynchronisation.php rename to src/Entities/Messages/StoreThirdPartyDevice.php index b5addc3..15e6ff1 100644 --- a/src/Entities/Messages/DeviceSynchronisation.php +++ b/src/Entities/Messages/StoreThirdPartyDevice.php @@ -1,7 +1,7 @@ */ -final class DeviceSynchronisation extends Device implements Entity +final class StoreThirdPartyDevice extends Device implements Entity { public function __construct( Uuid\UuidInterface $connector, + #[BootstrapObjectMapper\Rules\UuidValue()] + private readonly Uuid\UuidInterface $gateway, string $identifier, #[ObjectMapper\Rules\StringValue(notEmpty: true)] #[ObjectMapper\Modifiers\FieldName('gateway_identifier')] @@ -41,6 +44,11 @@ public function __construct( parent::__construct($connector, $identifier); } + public function getGateway(): Uuid\UuidInterface + { + return $this->gateway; + } + public function getGatewayIdentifier(): string { return $this->gatewayIdentifier; @@ -52,6 +60,7 @@ public function getGatewayIdentifier(): string public function toArray(): array { return array_merge(parent::toArray(), [ + 'gateway' => $this->getGateway()->toString(), 'gateway_identifier' => $this->getGatewayIdentifier(), ]); } diff --git a/src/Entities/Messages/WriteDeviceState.php b/src/Entities/Messages/WriteDeviceState.php new file mode 100644 index 0000000..b8fa547 --- /dev/null +++ b/src/Entities/Messages/WriteDeviceState.php @@ -0,0 +1,70 @@ + + * @package FastyBird:NsPanelConnector! + * @subpackage Entities + * @since 1.0.0 + * + * @date 09.08.23 + */ + +namespace FastyBird\Connector\NsPanel\Entities\Messages; + +use FastyBird\Library\Bootstrap\ObjectMapper as BootstrapObjectMapper; +use Ramsey\Uuid; + +/** + * Write update device state to hardware message entity + * + * @package FastyBird:NsPanelConnector! + * @subpackage Entities + * + * @author Adam Kadlec + */ +abstract class WriteDeviceState implements Entity +{ + + public function __construct( + #[BootstrapObjectMapper\Rules\UuidValue()] + private readonly Uuid\UuidInterface $connector, + #[BootstrapObjectMapper\Rules\UuidValue()] + private readonly Uuid\UuidInterface $device, + #[BootstrapObjectMapper\Rules\UuidValue()] + private readonly Uuid\UuidInterface $channel, + ) + { + } + + public function getConnector(): Uuid\UuidInterface + { + return $this->connector; + } + + public function getDevice(): Uuid\UuidInterface + { + return $this->device; + } + + public function getChannel(): Uuid\UuidInterface + { + return $this->channel; + } + + /** + * {@inheritDoc} + */ + public function toArray(): array + { + return [ + 'connector' => $this->getConnector()->toString(), + 'device' => $this->getDevice()->toString(), + 'channel' => $this->getChannel()->toString(), + ]; + } + +} diff --git a/src/Entities/Messages/WriteSubDeviceState.php b/src/Entities/Messages/WriteSubDeviceState.php new file mode 100644 index 0000000..17c7e69 --- /dev/null +++ b/src/Entities/Messages/WriteSubDeviceState.php @@ -0,0 +1,29 @@ + + * @package FastyBird:NsPanelConnector! + * @subpackage Entities + * @since 1.0.0 + * + * @date 09.08.23 + */ + +namespace FastyBird\Connector\NsPanel\Entities\Messages; + +/** + * Write update sub-device state to hardware message entity + * + * @package FastyBird:NsPanelConnector! + * @subpackage Entities + * + * @author Adam Kadlec + */ +final class WriteSubDeviceState extends WriteDeviceState +{ + +} diff --git a/src/Entities/Messages/WriteThirdPartyDeviceState.php b/src/Entities/Messages/WriteThirdPartyDeviceState.php new file mode 100644 index 0000000..7f12839 --- /dev/null +++ b/src/Entities/Messages/WriteThirdPartyDeviceState.php @@ -0,0 +1,29 @@ + + * @package FastyBird:NsPanelConnector! + * @subpackage Entities + * @since 1.0.0 + * + * @date 09.08.23 + */ + +namespace FastyBird\Connector\NsPanel\Entities\Messages; + +/** + * Write update third-party device state to hardware message entity + * + * @package FastyBird:NsPanelConnector! + * @subpackage Entities + * + * @author Adam Kadlec + */ +final class WriteThirdPartyDeviceState extends WriteDeviceState +{ + +} diff --git a/src/Helpers/Property.php b/src/Helpers/Property.php deleted file mode 100644 index a06dfa9..0000000 --- a/src/Helpers/Property.php +++ /dev/null @@ -1,94 +0,0 @@ - - * @package FastyBird:NsPanelConnector! - * @subpackage Helpers - * @since 1.0.0 - * - * @date 14.05.23 - */ - -namespace FastyBird\Connector\NsPanel\Helpers; - -use DateTimeInterface; -use FastyBird\Library\Metadata\Exceptions as MetadataExceptions; -use FastyBird\Library\Metadata\Types as MetadataTypes; -use FastyBird\Module\Devices\Entities as DevicesEntities; -use FastyBird\Module\Devices\Exceptions as DevicesExceptions; -use FastyBird\Module\Devices\Utilities as DevicesUtilities; -use Nette; -use Nette\Utils; - -/** - * Useful dynamic property state helpers - * - * @package FastyBird:NsPanelConnector! - * @subpackage Helpers - * - * @author Adam Kadlec - */ -final class Property -{ - - use Nette\SmartObject; - - public function __construct( - private readonly DevicesUtilities\DevicePropertiesStates $devicePropertiesStateManager, - private readonly DevicesUtilities\ChannelPropertiesStates $channelPropertiesStateManager, - ) - { - } - - /** - * @throws DevicesExceptions\InvalidState - * @throws MetadataExceptions\InvalidArgument - * @throws MetadataExceptions\InvalidState - */ - public function setValue( - DevicesEntities\Devices\Properties\Dynamic|DevicesEntities\Channels\Properties\Dynamic $property, - Utils\ArrayHash $data, - ): void - { - if ($property instanceof DevicesEntities\Devices\Properties\Dynamic) { - $this->devicePropertiesStateManager->setValue($property, $data); - } else { - $this->channelPropertiesStateManager->setValue($property, $data); - } - } - - /** - * @throws DevicesExceptions\InvalidState - * @throws MetadataExceptions\InvalidArgument - * @throws MetadataExceptions\InvalidState - */ - public function getActualValue( - // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong - DevicesEntities\Devices\Properties\Dynamic|DevicesEntities\Devices\Properties\Mapped|DevicesEntities\Channels\Properties\Dynamic|DevicesEntities\Channels\Properties\Mapped $property, - ): bool|float|int|string|DateTimeInterface|MetadataTypes\ButtonPayload|MetadataTypes\SwitchPayload|MetadataTypes\CoverPayload|null - { - return $property instanceof DevicesEntities\Devices\Properties\Dynamic || $property instanceof DevicesEntities\Devices\Properties\Mapped - ? $this->devicePropertiesStateManager->getValue($property)?->getActualValue() - : $this->channelPropertiesStateManager->getValue($property)?->getActualValue(); - } - - /** - * @throws DevicesExceptions\InvalidState - * @throws MetadataExceptions\InvalidArgument - * @throws MetadataExceptions\InvalidState - */ - public function getExpectedValue( - // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong - DevicesEntities\Devices\Properties\Dynamic|DevicesEntities\Devices\Properties\Mapped|DevicesEntities\Channels\Properties\Dynamic|DevicesEntities\Channels\Properties\Mapped $property, - ): bool|float|int|string|DateTimeInterface|MetadataTypes\ButtonPayload|MetadataTypes\SwitchPayload|MetadataTypes\CoverPayload|null - { - return $property instanceof DevicesEntities\Devices\Properties\Dynamic || $property instanceof DevicesEntities\Devices\Properties\Mapped - ? $this->devicePropertiesStateManager->getValue($property)?->getExpectedValue() - : $this->channelPropertiesStateManager->getValue($property)?->getExpectedValue(); - } - -} diff --git a/src/Consumers/Consumer.php b/src/Queue/Consumer.php similarity index 93% rename from src/Consumers/Consumer.php rename to src/Queue/Consumer.php index 8666da7..941fbb6 100644 --- a/src/Consumers/Consumer.php +++ b/src/Queue/Consumer.php @@ -13,7 +13,7 @@ * @date 09.07.23 */ -namespace FastyBird\Connector\NsPanel\Consumers; +namespace FastyBird\Connector\NsPanel\Queue; use FastyBird\Connector\NsPanel\Entities; diff --git a/src/Consumers/Messages.php b/src/Queue/Consumers.php similarity index 61% rename from src/Consumers/Messages.php rename to src/Queue/Consumers.php index af45898..1de579a 100644 --- a/src/Consumers/Messages.php +++ b/src/Queue/Consumers.php @@ -1,7 +1,7 @@ */ -final class Messages +final class Consumers { use Nette\SmartObject; @@ -40,57 +36,43 @@ final class Messages /** @var SplObjectStorage */ private SplObjectStorage $consumers; - /** @var SplQueue */ - private SplQueue $queue; - /** * @param array $consumers */ public function __construct( array $consumers, + private readonly Queue $queue, private readonly NsPanel\Logger $logger, ) { $this->consumers = new SplObjectStorage(); - $this->queue = new SplQueue(); foreach ($consumers as $consumer) { - $this->consumers->attach($consumer); + $this->append($consumer); } - - $this->logger->debug( - sprintf('Registered %d messages consumers', count($this->consumers)), - [ - 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'consumer', - ], - ); } - public function append(Entities\Messages\Entity $entity): void + public function append(Consumer $consumer): void { - $this->queue->enqueue($entity); + $this->consumers->attach($consumer); $this->logger->debug( - 'Appended new message into consumers queue', + 'Appended new messages consumer', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'consumer', - 'message' => $entity->toArray(), + 'type' => 'consumers', ], ); } public function consume(): void { - $this->queue->rewind(); + $entity = $this->queue->dequeue(); - if ($this->queue->isEmpty()) { + if ($entity === false) { return; } - $entity = $this->queue->dequeue(); - $this->consumers->rewind(); if ($this->consumers->count() === 0) { @@ -98,13 +80,10 @@ public function consume(): void 'No consumer is registered, messages could not be consumed', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'consumer', + 'type' => 'consumers', ], ); - // Reset queue items - $this->queue = new SplQueue(); - return; } @@ -118,15 +97,10 @@ public function consume(): void 'Message could not be consumed', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'consumer', + 'type' => 'consumers', 'message' => $entity->toArray(), ], ); } - public function isEmpty(): bool - { - return $this->queue->isEmpty(); - } - } diff --git a/src/Consumers/Messages/ConsumeDeviceProperty.php b/src/Queue/Consumers/DeviceProperty.php similarity index 97% rename from src/Consumers/Messages/ConsumeDeviceProperty.php rename to src/Queue/Consumers/DeviceProperty.php index 68fd813..2013ffc 100644 --- a/src/Consumers/Messages/ConsumeDeviceProperty.php +++ b/src/Queue/Consumers/DeviceProperty.php @@ -1,7 +1,7 @@ logger->warning( - 'Device property is not valid type', + 'Stored device property was not of valid type', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, 'type' => 'message-consumer', diff --git a/src/Clients/TPropertiesMapper.php b/src/Queue/Consumers/StateWriter.php similarity index 89% rename from src/Clients/TPropertiesMapper.php rename to src/Queue/Consumers/StateWriter.php index fd3341f..c65a0ca 100644 --- a/src/Clients/TPropertiesMapper.php +++ b/src/Queue/Consumers/StateWriter.php @@ -1,7 +1,7 @@ * - * @property-read Helpers\Property $propertyStateHelper * @property-read DevicesModels\Channels\Properties\PropertiesRepository $channelsPropertiesRepository + * @property-read DevicesUtilities\ChannelPropertiesStates $channelPropertiesStateManager */ -trait TPropertiesMapper +trait StateWriter { /** @@ -55,7 +58,7 @@ trait TPropertiesMapper * @throws MetadataExceptions\InvalidArgument * @throws MetadataExceptions\InvalidState */ - protected function mapChannelToState( + public function mapChannelToState( Entities\NsPanelChannel $channel, ): array|null { @@ -481,8 +484,8 @@ private function getPropertyValue( ): string|int|float|bool|null { if ($property instanceof DevicesEntities\Channels\Properties\Mapped) { - $actualValue = $this->propertyStateHelper->getActualValue($property); - $expectedValue = $this->propertyStateHelper->getExpectedValue($property); + $actualValue = $this->getActualValue($property); + $expectedValue = $this->getExpectedValue($property); $value = $expectedValue ?? $actualValue; } else { @@ -524,4 +527,28 @@ private function findProtocolProperty( return $property; } + /** + * @throws DevicesExceptions\InvalidState + * @throws MetadataExceptions\InvalidArgument + * @throws MetadataExceptions\InvalidState + */ + public function getActualValue( + DevicesEntities\Channels\Properties\Dynamic|DevicesEntities\Channels\Properties\Mapped $property, + ): bool|float|int|string|DateTimeInterface|MetadataTypes\ButtonPayload|MetadataTypes\SwitchPayload|MetadataTypes\CoverPayload|null + { + return $this->channelPropertiesStateManager->readValue($property)?->getActualValue(); + } + + /** + * @throws DevicesExceptions\InvalidState + * @throws MetadataExceptions\InvalidArgument + * @throws MetadataExceptions\InvalidState + */ + public function getExpectedValue( + DevicesEntities\Channels\Properties\Dynamic|DevicesEntities\Channels\Properties\Mapped $property, + ): bool|float|int|string|DateTimeInterface|MetadataTypes\ButtonPayload|MetadataTypes\SwitchPayload|MetadataTypes\CoverPayload|null + { + return $this->channelPropertiesStateManager->readValue($property)?->getExpectedValue(); + } + } diff --git a/src/Consumers/Messages/DeviceOnline.php b/src/Queue/Consumers/StoreDeviceConnectionState.php similarity index 85% rename from src/Consumers/Messages/DeviceOnline.php rename to src/Queue/Consumers/StoreDeviceConnectionState.php index 4db2c69..1a977d5 100644 --- a/src/Consumers/Messages/DeviceOnline.php +++ b/src/Queue/Consumers/StoreDeviceConnectionState.php @@ -1,7 +1,7 @@ */ -final class DeviceOnline implements Consumers\Consumer +final class StoreDeviceConnectionState implements Queue\Consumer { use Nette\SmartObject; public function __construct( - private readonly Helpers\Property $propertyStateHelper, private readonly DevicesModels\Devices\DevicesRepository $devicesRepository, private readonly DevicesModels\Devices\Properties\PropertiesRepository $devicesPropertiesRepository, private readonly DevicesModels\Channels\ChannelsRepository $channelsRepository, private readonly DevicesModels\Channels\Properties\PropertiesRepository $channelsPropertiesRepository, private readonly DevicesUtilities\DeviceConnection $deviceConnectionManager, + private readonly DevicesUtilities\DevicePropertiesStates $devicePropertiesStateManager, + private readonly DevicesUtilities\ChannelPropertiesStates $channelPropertiesStateManager, private readonly NsPanel\Logger $logger, ) { @@ -63,7 +63,7 @@ public function __construct( */ public function consume(Entities\Messages\Entity $entity): bool { - if (!$entity instanceof Entities\Messages\DeviceOnline) { + if (!$entity instanceof Entities\Messages\StoreDeviceConnectionState) { return false; } @@ -74,6 +74,21 @@ public function consume(Entities\Messages\Entity $entity): bool $device = $this->devicesRepository->findOneBy($findDeviceQuery, Entities\NsPanelDevice::class); if ($device === null) { + $this->logger->error( + 'Device could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'device-online-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'device' => [ + 'identifier' => $entity->getIdentifier(), + ], + 'data' => $entity->toArray(), + ], + ); + return true; } @@ -100,7 +115,7 @@ public function consume(Entities\Messages\Entity $entity): bool $findDevicePropertiesQuery, DevicesEntities\Devices\Properties\Dynamic::class, ) as $property) { - $this->propertyStateHelper->setValue( + $this->devicePropertiesStateManager->setValue( $property, Nette\Utils\ArrayHash::from([ DevicesStates\Property::VALID_KEY => false, @@ -121,7 +136,7 @@ public function consume(Entities\Messages\Entity $entity): bool $findChannelPropertiesQuery, DevicesEntities\Channels\Properties\Dynamic::class, ) as $property) { - $this->propertyStateHelper->setValue( + $this->channelPropertiesStateManager->setValue( $property, Nette\Utils\ArrayHash::from([ DevicesStates\Property::VALID_KEY => false, @@ -159,7 +174,7 @@ public function consume(Entities\Messages\Entity $entity): bool $findDevicePropertiesQuery, DevicesEntities\Devices\Properties\Dynamic::class, ) as $property) { - $this->propertyStateHelper->setValue( + $this->devicePropertiesStateManager->setValue( $property, Nette\Utils\ArrayHash::from([ DevicesStates\Property::VALID_KEY => false, @@ -183,7 +198,7 @@ public function consume(Entities\Messages\Entity $entity): bool $findChannelPropertiesQuery, DevicesEntities\Channels\Properties\Dynamic::class, ) as $property) { - $this->propertyStateHelper->setValue( + $this->channelPropertiesStateManager->setValue( $property, Nette\Utils\ArrayHash::from([ DevicesStates\Property::VALID_KEY => false, @@ -218,6 +233,9 @@ public function consume(Entities\Messages\Entity $entity): bool [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, 'type' => 'device-online-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], 'device' => [ 'id' => $device->getId()->toString(), ], diff --git a/src/Consumers/Messages/DeviceState.php b/src/Queue/Consumers/StoreDeviceState.php similarity index 88% rename from src/Consumers/Messages/DeviceState.php rename to src/Queue/Consumers/StoreDeviceState.php index 7daa730..15e10f9 100644 --- a/src/Consumers/Messages/DeviceState.php +++ b/src/Queue/Consumers/StoreDeviceState.php @@ -1,7 +1,7 @@ */ -final class DeviceState implements Consumers\Consumer +final class StoreDeviceState implements Queue\Consumer { use Nette\SmartObject; public function __construct( private readonly bool $useExchange, - private readonly Helpers\Property $propertyStateHelper, private readonly NsPanel\Logger $logger, private readonly DevicesModels\Devices\DevicesRepository $devicesRepository, private readonly DevicesModels\Channels\ChannelsRepository $channelsRepository, @@ -82,7 +81,7 @@ public function __construct( */ public function consume(Entities\Messages\Entity $entity): bool { - if (!$entity instanceof Entities\Messages\DeviceState) { + if (!$entity instanceof Entities\Messages\StoreDeviceState) { return false; } @@ -93,6 +92,21 @@ public function consume(Entities\Messages\Entity $entity): bool $device = $this->devicesRepository->findOneBy($findDeviceQuery, Entities\NsPanelDevice::class); if ($device === null) { + $this->logger->error( + 'Device could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'store-device-state-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'device' => [ + 'identifier' => $entity->getIdentifier(), + ], + 'data' => $entity->toArray(), + ], + ); + return true; } @@ -103,10 +117,13 @@ public function consume(Entities\Messages\Entity $entity): bool } $this->logger->debug( - 'Consumed device state message', + 'Consumed store device state message', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'device-state-message-consumer', + 'type' => 'store-device-state-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], 'device' => [ 'id' => $device->getId()->toString(), ], @@ -158,14 +175,17 @@ private function processSubDevice( continue; } - $this->propertyStateHelper->setValue($property, Utils\ArrayHash::from([ - DevicesStates\Property::ACTUAL_VALUE_KEY => Helpers\Transformer::transformValueFromDevice( - $property->getDataType(), - $property->getFormat(), - $item->getValue(), - ), - DevicesStates\Property::VALID_KEY => true, - ])); + $this->channelPropertiesStateManager->writeValue( + $property, + Utils\ArrayHash::from([ + DevicesStates\Property::ACTUAL_VALUE_KEY => Helpers\Transformer::transformValueFromDevice( + $property->getDataType(), + $property->getFormat(), + $item->getValue(), + ), + DevicesStates\Property::VALID_KEY => true, + ]), + ); } } diff --git a/src/Consumers/Messages/DiscoveredSubDevice.php b/src/Queue/Consumers/StoreSubDevice.php similarity index 73% rename from src/Consumers/Messages/DiscoveredSubDevice.php rename to src/Queue/Consumers/StoreSubDevice.php index e4e6caa..dd9aaf0 100644 --- a/src/Consumers/Messages/DiscoveredSubDevice.php +++ b/src/Queue/Consumers/StoreSubDevice.php @@ -1,7 +1,7 @@ */ -final class DiscoveredSubDevice implements Consumers\Consumer +final class StoreSubDevice implements Queue\Consumer { - use ConsumeDeviceProperty; + use DeviceProperty; use Nette\SmartObject; public function __construct( @@ -69,7 +69,7 @@ public function __construct( */ public function consume(Entities\Messages\Entity $entity): bool { - if (!$entity instanceof Entities\Messages\DiscoveredSubDevice) { + if (!$entity instanceof Entities\Messages\StoreSubDevice) { return false; } @@ -80,13 +80,31 @@ public function consume(Entities\Messages\Entity $entity): bool $parent = $this->devicesRepository->findOneBy($findDeviceQuery, Entities\Devices\Gateway::class); if ($parent === null) { + $this->logger->error( + 'Device could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'store-sub-device-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'gateway' => [ + 'id' => $entity->getGateway(), + ], + 'device' => [ + 'identifier' => $entity->getIdentifier(), + ], + 'data' => $entity->toArray(), + ], + ); + return true; } $findDeviceQuery = new Queries\FindSubDevices(); $findDeviceQuery->byConnectorId($entity->getConnector()); $findDeviceQuery->forParent($parent); - $findDeviceQuery->byIdentifier($entity->getSerialNumber()); + $findDeviceQuery->byIdentifier($entity->getIdentifier()); $device = $this->devicesRepository->findOneBy($findDeviceQuery, Entities\Devices\SubDevice::class); @@ -100,6 +118,24 @@ public function consume(Entities\Messages\Entity $entity): bool ); if ($connector === null) { + $this->logger->error( + 'Connector could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'store-sub-device-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'gateway' => [ + 'id' => $entity->getGateway(), + ], + 'device' => [ + 'identifier' => $entity->getIdentifier(), + ], + 'data' => $entity->toArray(), + ], + ); + return true; } @@ -109,7 +145,7 @@ function () use ($entity, $connector, $parent): Entities\Devices\SubDevice { 'entity' => Entities\Devices\SubDevice::class, 'connector' => $connector, 'parent' => $parent, - 'identifier' => $entity->getSerialNumber(), + 'identifier' => $entity->getIdentifier(), 'name' => $entity->getName(), ])); assert($device instanceof Entities\Devices\SubDevice); @@ -119,13 +155,19 @@ function () use ($entity, $connector, $parent): Entities\Devices\SubDevice { ); $this->logger->info( - 'Creating new sub-device', + 'Sub-device was created', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'discovered-sub-device-message-consumer', + 'type' => 'store-sub-device-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'gateway' => [ + 'id' => $parent->getId()->toString(), + ], 'device' => [ 'id' => $device->getId()->toString(), - 'identifier' => $entity->getSerialNumber(), + 'identifier' => $entity->getIdentifier(), 'protocol' => $entity->getProtocol(), ], ], @@ -142,14 +184,20 @@ function () use ($entity, $device): Entities\Devices\SubDevice { }, ); - $this->logger->debug( + $this->logger->info( 'Sub-device was updated', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'discovered-sub-device-message-consumer', + 'type' => 'store-sub-device-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'gateway' => [ + 'id' => $parent->getId()->toString(), + ], 'device' => [ 'id' => $device->getId()->toString(), - 'identifier' => $entity->getSerialNumber(), + 'identifier' => $entity->getIdentifier(), 'protocol' => $entity->getProtocol(), ], ], @@ -193,7 +241,7 @@ function () use ($entity, $device): Entities\Devices\SubDevice { ); foreach ($entity->getCapabilities() as $capability) { - $this->databaseHelper->transaction(function () use ($device, $capability): bool { + $this->databaseHelper->transaction(function () use ($entity, $parent, $device, $capability): bool { $identifier = Helpers\Name::convertCapabilityToChannel($capability->getCapability()); if ( @@ -217,10 +265,16 @@ function () use ($entity, $device): Entities\Devices\SubDevice { ])); $this->logger->debug( - 'Creating new device channel', + 'Device channel was created', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'discovered-sub-device-message-consumer', + 'type' => 'store-sub-device-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'gateway' => [ + 'id' => $parent->getId()->toString(), + ], 'device' => [ 'id' => $device->getId()->toString(), ], @@ -237,7 +291,7 @@ function () use ($entity, $device): Entities\Devices\SubDevice { foreach ($entity->getTags() as $tag => $value) { if ($tag === Types\Capability::TOGGLE && is_array($value)) { - $this->databaseHelper->transaction(function () use ($device, $value): void { + $this->databaseHelper->transaction(function () use ($entity, $parent, $device, $value): void { foreach ($value as $key => $name) { $findChannelQuery = new Queries\FindChannels(); $findChannelQuery->byIdentifier( @@ -259,10 +313,16 @@ function () use ($entity, $device): Entities\Devices\SubDevice { ])); $this->logger->debug( - 'Set toggle channel name', + 'Toggle channel name was set', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'discovered-sub-device-message-consumer', + 'type' => 'store-sub-device-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'gateway' => [ + 'id' => $parent->getId()->toString(), + ], 'device' => [ 'id' => $device->getId()->toString(), ], @@ -278,10 +338,16 @@ function () use ($entity, $device): Entities\Devices\SubDevice { } $this->logger->debug( - 'Consumed sub-device found message', + 'Consumed store sub-device message', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'discovered-sub-device-message-consumer', + 'type' => 'store-sub-device-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'gateway' => [ + 'id' => $parent->getId()->toString(), + ], 'device' => [ 'id' => $device->getId()->toString(), ], diff --git a/src/Consumers/Messages/DeviceSynchronisation.php b/src/Queue/Consumers/StoreThirdPartyDevice.php similarity index 74% rename from src/Consumers/Messages/DeviceSynchronisation.php rename to src/Queue/Consumers/StoreThirdPartyDevice.php index d4a734d..a3d7493 100644 --- a/src/Consumers/Messages/DeviceSynchronisation.php +++ b/src/Queue/Consumers/StoreThirdPartyDevice.php @@ -1,7 +1,7 @@ */ -final class DeviceSynchronisation implements Consumers\Consumer +final class StoreThirdPartyDevice implements Queue\Consumer { - use ConsumeDeviceProperty; + use DeviceProperty; use Nette\SmartObject; public function __construct( @@ -62,7 +62,7 @@ public function __construct( */ public function consume(Entities\Messages\Entity $entity): bool { - if (!$entity instanceof Entities\Messages\DeviceSynchronisation) { + if (!$entity instanceof Entities\Messages\StoreThirdPartyDevice) { return false; } @@ -73,6 +73,24 @@ public function consume(Entities\Messages\Entity $entity): bool $device = $this->devicesRepository->findOneBy($findDeviceQuery, Entities\Devices\ThirdPartyDevice::class); if ($device === null) { + $this->logger->error( + 'Device could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'store-sub-device-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'gateway' => [ + 'id' => $entity->getGateway(), + ], + 'device' => [ + 'identifier' => $entity->getIdentifier(), + ], + 'data' => $entity->toArray(), + ], + ); + return true; } @@ -85,10 +103,10 @@ public function consume(Entities\Messages\Entity $entity): bool ); $this->logger->debug( - 'Consumed device synchronisation message', + 'Consumed store third-party device state message', [ 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'device-synchronisation-message-consumer', + 'type' => 'store-third-party-device-message-consumer', 'device' => [ 'id' => $device->getId()->toString(), ], diff --git a/src/Queue/Consumers/WriteSubDeviceState.php b/src/Queue/Consumers/WriteSubDeviceState.php new file mode 100644 index 0000000..158834d --- /dev/null +++ b/src/Queue/Consumers/WriteSubDeviceState.php @@ -0,0 +1,410 @@ + + * @package FastyBird:NsPanelConnector! + * @subpackage Consumers + * @since 1.0.0 + * + * @date 18.07.23 + */ + +namespace FastyBird\Connector\NsPanel\Queue\Consumers; + +use DateTimeInterface; +use FastyBird\Connector\NsPanel; +use FastyBird\Connector\NsPanel\API; +use FastyBird\Connector\NsPanel\Entities; +use FastyBird\Connector\NsPanel\Exceptions; +use FastyBird\Connector\NsPanel\Helpers; +use FastyBird\Connector\NsPanel\Queries; +use FastyBird\Connector\NsPanel\Queue; +use FastyBird\DateTimeFactory; +use FastyBird\Library\Bootstrap\Helpers as BootstrapHelpers; +use FastyBird\Library\Metadata\Exceptions as MetadataExceptions; +use FastyBird\Library\Metadata\Types as MetadataTypes; +use FastyBird\Module\Devices\Entities as DevicesEntities; +use FastyBird\Module\Devices\Exceptions as DevicesExceptions; +use FastyBird\Module\Devices\Models as DevicesModels; +use FastyBird\Module\Devices\States as DevicesStates; +use FastyBird\Module\Devices\Utilities as DevicesUtilities; +use Nette; +use Nette\Utils; +use Throwable; +use function array_merge; +use function strval; + +/** + * Write sub-device state to hardware message consumer + * + * @package FastyBird:NsPanelConnector! + * @subpackage Consumers + * + * @author Adam Kadlec + */ +final class WriteSubDeviceState implements Queue\Consumer +{ + + use StateWriter; + use Nette\SmartObject; + + private API\LanApi|null $lanApiApi = null; + + public function __construct( + protected readonly DevicesModels\Channels\Properties\PropertiesRepository $channelsPropertiesRepository, + protected readonly DevicesUtilities\ChannelPropertiesStates $channelPropertiesStateManager, + private readonly Queue\Queue $queue, + private readonly API\LanApiFactory $lanApiApiFactory, + private readonly Helpers\Entity $entityHelper, + private readonly DevicesModels\Connectors\ConnectorsRepository $connectorsRepository, + private readonly DevicesModels\Devices\DevicesRepository $devicesRepository, + private readonly DevicesModels\Channels\ChannelsRepository $channelsRepository, + private readonly DevicesUtilities\ChannelPropertiesStates $channelPropertiesStates, + private readonly DateTimeFactory\Factory $dateTimeFactory, + private readonly NsPanel\Logger $logger, + ) + { + } + + /** + * @throws DevicesExceptions\InvalidState + * @throws Exceptions\InvalidArgument + * @throws Exceptions\InvalidState + * @throws Exceptions\Runtime + * @throws MetadataExceptions\InvalidArgument + * @throws MetadataExceptions\InvalidState + */ + public function consume(Entities\Messages\Entity $entity): bool + { + if (!$entity instanceof Entities\Messages\WriteSubDeviceState) { + return false; + } + + $findConnectorQuery = new Queries\FindConnectors(); + $findConnectorQuery->byId($entity->getConnector()); + + $connector = $this->connectorsRepository->findOneBy($findConnectorQuery, Entities\NsPanelConnector::class); + + if ($connector === null) { + $this->logger->error( + 'Connector could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-sub-device-state-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'device' => [ + 'id' => $entity->getDevice()->toString(), + ], + 'channel' => [ + 'id' => $entity->getChannel()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + $findDeviceQuery = new Queries\FindSubDevices(); + $findDeviceQuery->forConnector($connector); + $findDeviceQuery->byId($entity->getDevice()); + + $device = $this->devicesRepository->findOneBy($findDeviceQuery, Entities\Devices\SubDevice::class); + + if ($device === null) { + $this->logger->error( + 'Device could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-sub-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'device' => [ + 'id' => $entity->getDevice()->toString(), + ], + 'channel' => [ + 'id' => $entity->getChannel()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + $ipAddress = $device->getGateway()->getIpAddress(); + $accessToken = $device->getGateway()->getAccessToken(); + + if ($ipAddress === null || $accessToken === null) { + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\StoreDeviceConnectionState::class, + [ + 'connector' => $connector->getId()->toString(), + 'identifier' => $device->getGateway()->getIdentifier(), + 'state' => MetadataTypes\ConnectionState::STATE_ALERT, + ], + ), + ); + + $this->logger->error( + 'Device owning NS Panel is not configured', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-sub-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $entity->getChannel()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + $findChannelQuery = new Queries\FindChannels(); + $findChannelQuery->forDevice($device); + $findChannelQuery->byId($entity->getChannel()); + + $channel = $this->channelsRepository->findOneBy($findChannelQuery, Entities\NsPanelChannel::class); + + if ($channel === null) { + $this->logger->error( + 'Channel could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-sub-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $entity->getChannel()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + if (!$channel->getCapability()->hasReadWritePermission()) { + $this->logger->error( + 'Device state is not writable', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-sub-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $channel->getId()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + $mapped = $this->mapChannelToState($channel); + + if ($mapped === null) { + $this->logger->error( + 'Device state could not be created', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-sub-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $channel->getId()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + try { + $this->getApiClient($connector)->setSubDeviceState( + $device->getIdentifier(), + $mapped, + $ipAddress, + $accessToken, + ) + ->then(function () use ($channel): void { + $now = $this->dateTimeFactory->getNow(); + + foreach ($channel->getProperties() as $property) { + if ($property instanceof DevicesEntities\Channels\Properties\Dynamic) { + $state = $this->channelPropertiesStates->getValue($property); + + if ($state?->getExpectedValue() !== null) { + $this->channelPropertiesStateManager->setValue( + $property, + Utils\ArrayHash::from([ + DevicesStates\Property::PENDING_KEY => $now->format(DateTimeInterface::ATOM), + ]), + ); + } + } + } + }) + ->otherwise(function (Throwable $ex) use ($entity, $connector, $device, $channel): void { + foreach ($channel->getProperties() as $property) { + if ($property instanceof DevicesEntities\Channels\Properties\Dynamic) { + $this->channelPropertiesStateManager->setValue( + $property, + Utils\ArrayHash::from([ + DevicesStates\Property::EXPECTED_VALUE_KEY => null, + DevicesStates\Property::PENDING_KEY => false, + ]), + ); + } + } + + $extra = []; + + if ($ex instanceof Exceptions\LanApiCall) { + $extra = [ + 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval($ex->getRequest()->getUri()) : null, + 'body' => $ex->getRequest()?->getBody()->getContents(), + ], + 'response' => [ + 'body' => $ex->getRequest()?->getBody()->getContents(), + ], + ]; + + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\StoreDeviceConnectionState::class, + [ + 'connector' => $connector->getId()->toString(), + 'identifier' => $device->getGateway()->getIdentifier(), + 'state' => MetadataTypes\ConnectionState::STATE_DISCONNECTED, + ], + ), + ); + + } else { + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\StoreDeviceConnectionState::class, + [ + 'connector' => $connector->getId()->toString(), + 'identifier' => $device->getGateway()->getIdentifier(), + 'state' => MetadataTypes\ConnectionState::STATE_LOST, + ], + ), + ); + } + + $this->logger->error( + 'Could not report device state to NS Panel', + array_merge( + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-sub-device-state-message-consumer', + 'exception' => BootstrapHelpers\Logger::buildException($ex), + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $channel->getId()->toString(), + ], + 'data' => $entity->toArray(), + ], + $extra, + ), + ); + }); + } catch (Throwable $ex) { + $this->logger->error( + 'An unhandled error occurred', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-sub-device-state-message-consumer', + 'exception' => BootstrapHelpers\Logger::buildException($ex), + 'data' => $entity->toArray(), + ], + ); + } + + $this->logger->debug( + 'Consumed write sub device state message', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-sub-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $channel->getId()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + private function getApiClient(Entities\NsPanelConnector $connector): API\LanApi + { + if ($this->lanApiApi === null) { + $this->lanApiApi = $this->lanApiApiFactory->create($connector->getIdentifier()); + } + + return $this->lanApiApi; + } + +} diff --git a/src/Queue/Consumers/WriteThirdPartyDeviceState.php b/src/Queue/Consumers/WriteThirdPartyDeviceState.php new file mode 100644 index 0000000..0012850 --- /dev/null +++ b/src/Queue/Consumers/WriteThirdPartyDeviceState.php @@ -0,0 +1,423 @@ + + * @package FastyBird:NsPanelConnector! + * @subpackage Consumers + * @since 1.0.0 + * + * @date 18.07.23 + */ + +namespace FastyBird\Connector\NsPanel\Queue\Consumers; + +use DateTimeInterface; +use FastyBird\Connector\NsPanel; +use FastyBird\Connector\NsPanel\API; +use FastyBird\Connector\NsPanel\Entities; +use FastyBird\Connector\NsPanel\Exceptions; +use FastyBird\Connector\NsPanel\Helpers; +use FastyBird\Connector\NsPanel\Queries; +use FastyBird\Connector\NsPanel\Queue; +use FastyBird\DateTimeFactory; +use FastyBird\Library\Bootstrap\Helpers as BootstrapHelpers; +use FastyBird\Library\Metadata\Exceptions as MetadataExceptions; +use FastyBird\Library\Metadata\Types as MetadataTypes; +use FastyBird\Module\Devices\Entities as DevicesEntities; +use FastyBird\Module\Devices\Exceptions as DevicesExceptions; +use FastyBird\Module\Devices\Models as DevicesModels; +use FastyBird\Module\Devices\States as DevicesStates; +use FastyBird\Module\Devices\Utilities as DevicesUtilities; +use Nette; +use Nette\Utils; +use Throwable; +use function array_merge; +use function strval; + +/** + * Write third-party device state to hardware message consumer + * + * @package FastyBird:NsPanelConnector! + * @subpackage Consumers + * + * @author Adam Kadlec + */ +final class WriteThirdPartyDeviceState implements Queue\Consumer +{ + + use StateWriter; + use Nette\SmartObject; + + private API\LanApi|null $lanApiApi = null; + + public function __construct( + protected readonly DevicesModels\Channels\Properties\PropertiesRepository $channelsPropertiesRepository, + protected readonly DevicesUtilities\ChannelPropertiesStates $channelPropertiesStateManager, + private readonly Queue\Queue $queue, + private readonly API\LanApiFactory $lanApiApiFactory, + private readonly Helpers\Entity $entityHelper, + private readonly DevicesModels\Connectors\ConnectorsRepository $connectorsRepository, + private readonly DevicesModels\Devices\DevicesRepository $devicesRepository, + private readonly DevicesModels\Channels\ChannelsRepository $channelsRepository, + private readonly DevicesUtilities\ChannelPropertiesStates $channelPropertiesStates, + private readonly DateTimeFactory\Factory $dateTimeFactory, + private readonly NsPanel\Logger $logger, + ) + { + } + + /** + * @throws DevicesExceptions\InvalidState + * @throws Exceptions\InvalidArgument + * @throws Exceptions\InvalidState + * @throws Exceptions\Runtime + * @throws MetadataExceptions\InvalidArgument + * @throws MetadataExceptions\InvalidState + */ + public function consume(Entities\Messages\Entity $entity): bool + { + if (!$entity instanceof Entities\Messages\WriteThirdPartyDeviceState) { + return false; + } + + $findConnectorQuery = new Queries\FindConnectors(); + $findConnectorQuery->byId($entity->getConnector()); + + $connector = $this->connectorsRepository->findOneBy($findConnectorQuery, Entities\NsPanelConnector::class); + + if ($connector === null) { + $this->logger->error( + 'Connector could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-third-party-device-state-message-consumer', + 'connector' => [ + 'id' => $entity->getConnector()->toString(), + ], + 'device' => [ + 'id' => $entity->getDevice()->toString(), + ], + 'channel' => [ + 'id' => $entity->getChannel()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + $findDeviceQuery = new Queries\FindThirdPartyDevices(); + $findDeviceQuery->forConnector($connector); + $findDeviceQuery->byId($entity->getDevice()); + + $device = $this->devicesRepository->findOneBy($findDeviceQuery, Entities\Devices\ThirdPartyDevice::class); + + if ($device === null) { + $this->logger->error( + 'Device could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-third-party-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'device' => [ + 'id' => $entity->getDevice()->toString(), + ], + 'channel' => [ + 'id' => $entity->getChannel()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + $ipAddress = $device->getGateway()->getIpAddress(); + $accessToken = $device->getGateway()->getAccessToken(); + + if ($ipAddress === null || $accessToken === null) { + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\StoreDeviceConnectionState::class, + [ + 'connector' => $connector->getId()->toString(), + 'identifier' => $device->getGateway()->getIdentifier(), + 'state' => MetadataTypes\ConnectionState::STATE_ALERT, + ], + ), + ); + + $this->logger->error( + 'Device owning NS Panel is not configured', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-third-party-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $entity->getChannel()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + $serialNumber = $device->getGatewayIdentifier(); + + if ($serialNumber === null) { + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\StoreDeviceConnectionState::class, + [ + 'connector' => $connector->getId()->toString(), + 'identifier' => $device->getIdentifier(), + 'state' => MetadataTypes\ConnectionState::STATE_ALERT, + ], + ), + ); + + $this->logger->error( + 'Device is not synchronised with NS Panel', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-third-party-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $entity->getChannel()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + $findChannelQuery = new Queries\FindChannels(); + $findChannelQuery->forDevice($device); + $findChannelQuery->byId($entity->getChannel()); + + $channel = $this->channelsRepository->findOneBy($findChannelQuery, Entities\NsPanelChannel::class); + + if ($channel === null) { + $this->logger->error( + 'Channel could not be loaded', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-third-party-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $entity->getChannel()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + $mapped = $this->mapChannelToState($channel); + + if ($mapped === null) { + $this->logger->error( + 'Device state could not be created', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-third-party-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $channel->getId()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + try { + $this->getApiClient($connector)->reportDeviceState( + $serialNumber, + $mapped, + $ipAddress, + $accessToken, + ) + ->then(function () use ($channel): void { + $now = $this->dateTimeFactory->getNow(); + + foreach ($channel->getProperties() as $property) { + if ($property instanceof DevicesEntities\Channels\Properties\Dynamic) { + $state = $this->channelPropertiesStates->getValue($property); + + if ($state?->getExpectedValue() !== null) { + $this->channelPropertiesStateManager->setValue( + $property, + Utils\ArrayHash::from([ + DevicesStates\Property::PENDING_KEY => $now->format(DateTimeInterface::ATOM), + ]), + ); + } + } + } + }) + ->otherwise(function (Throwable $ex) use ($entity, $connector, $device, $channel): void { + foreach ($channel->getProperties() as $property) { + if ($property instanceof DevicesEntities\Channels\Properties\Dynamic) { + $this->channelPropertiesStateManager->setValue( + $property, + Utils\ArrayHash::from([ + DevicesStates\Property::EXPECTED_VALUE_KEY => null, + DevicesStates\Property::PENDING_KEY => false, + ]), + ); + } + } + + $extra = []; + + if ($ex instanceof Exceptions\LanApiCall) { + $extra = [ + 'request' => [ + 'method' => $ex->getRequest()?->getMethod(), + 'url' => $ex->getRequest() !== null ? strval($ex->getRequest()->getUri()) : null, + 'body' => $ex->getRequest()?->getBody()->getContents(), + ], + 'response' => [ + 'body' => $ex->getRequest()?->getBody()->getContents(), + ], + ]; + + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\StoreDeviceConnectionState::class, + [ + 'connector' => $connector->getId()->toString(), + 'identifier' => $device->getGateway()->getIdentifier(), + 'state' => MetadataTypes\ConnectionState::STATE_DISCONNECTED, + ], + ), + ); + + } else { + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\StoreDeviceConnectionState::class, + [ + 'connector' => $connector->getId()->toString(), + 'identifier' => $device->getGateway()->getIdentifier(), + 'state' => MetadataTypes\ConnectionState::STATE_LOST, + ], + ), + ); + } + + $this->logger->error( + 'Could not report device state to NS Panel', + array_merge( + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-third-party-device-state-message-consumer', + 'exception' => BootstrapHelpers\Logger::buildException($ex), + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $channel->getId()->toString(), + ], + 'data' => $entity->toArray(), + ], + $extra, + ), + ); + }); + } catch (Throwable $ex) { + $this->logger->error( + 'An unhandled error occurred', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-third-party-device-state-message-consumer', + 'exception' => BootstrapHelpers\Logger::buildException($ex), + 'data' => $entity->toArray(), + ], + ); + } + + $this->logger->debug( + 'Consumed write third-party device state message', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'write-third-party-device-state-message-consumer', + 'connector' => [ + 'id' => $connector->getId()->toString(), + ], + 'gateway' => [ + 'id' => $device->getGateway()->getId()->toString(), + ], + 'device' => [ + 'id' => $device->getId()->toString(), + ], + 'channel' => [ + 'id' => $channel->getId()->toString(), + ], + 'data' => $entity->toArray(), + ], + ); + + return true; + } + + private function getApiClient(Entities\NsPanelConnector $connector): API\LanApi + { + if ($this->lanApiApi === null) { + $this->lanApiApi = $this->lanApiApiFactory->create($connector->getIdentifier()); + } + + return $this->lanApiApi; + } + +} diff --git a/src/Queue/Queue.php b/src/Queue/Queue.php new file mode 100644 index 0000000..d97f56e --- /dev/null +++ b/src/Queue/Queue.php @@ -0,0 +1,75 @@ + + * @package FastyBird:NsPanelConnector! + * @subpackage Consumers + * @since 1.0.0 + * + * @date 09.07.23 + */ + +namespace FastyBird\Connector\NsPanel\Queue; + +use FastyBird\Connector\NsPanel; +use FastyBird\Connector\NsPanel\Entities; +use FastyBird\Library\Metadata\Types as MetadataTypes; +use Nette; +use SplQueue; + +/** + * Clients messages queue + * + * @package FastyBird:NsPanelConnector! + * @subpackage Consumers + * + * @author Adam Kadlec + */ +final class Queue +{ + + use Nette\SmartObject; + + /** @var SplQueue */ + private SplQueue $queue; + + public function __construct(private readonly NsPanel\Logger $logger) + { + $this->queue = new SplQueue(); + } + + public function append(Entities\Messages\Entity $entity): void + { + $this->queue->enqueue($entity); + + $this->logger->debug( + 'Appended new message into messages queue', + [ + 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, + 'type' => 'queue', + 'message' => $entity->toArray(), + ], + ); + } + + public function dequeue(): Entities\Messages\Entity|false + { + $this->queue->rewind(); + + if ($this->queue->isEmpty()) { + return false; + } + + return $this->queue->dequeue(); + } + + public function isEmpty(): bool + { + return $this->queue->isEmpty(); + } + +} diff --git a/src/Router/Router.php b/src/Router/Router.php index af20985..995bd5a 100644 --- a/src/Router/Router.php +++ b/src/Router/Router.php @@ -29,6 +29,8 @@ class Router extends Routing\Router { + public const URL_GATEWAY_ID = 'gateway'; + public const URL_DEVICE_ID = 'device'; public function __construct( @@ -37,7 +39,10 @@ public function __construct( { parent::__construct(); - $this->post('/do-directive/{' . self::URL_DEVICE_ID . '}', [$directiveController, 'process']); + $this->post( + '/do-directive/{' . self::URL_GATEWAY_ID . '}/{' . self::URL_DEVICE_ID . '}', + [$directiveController, 'process'], + ); } } diff --git a/src/Translations/ns-panel-connector.en_us.neon b/src/Translations/ns-panel-connector.en_us.neon index dbd33e2..5fcece9 100644 --- a/src/Translations/ns-panel-connector.en_us.neon +++ b/src/Translations/ns-panel-connector.en_us.neon @@ -275,7 +275,7 @@ cmd: notFound: "Connector was not found in system." disabled: "Connector is disabled. Disabled connector could not be executed." noConnectors: "No NS Panel connectors registered in system." - error: "Something went wrong, capability could not be processed." + error: "Something went wrong, connector was terminated." discovery: title: "NS Panel connector - devices discovery" diff --git a/src/Writers/Event.php b/src/Writers/Event.php index 334d2d1..8c3d7ba 100644 --- a/src/Writers/Event.php +++ b/src/Writers/Event.php @@ -15,15 +15,10 @@ namespace FastyBird\Connector\NsPanel\Writers; -use DateTimeInterface; -use FastyBird\Connector\NsPanel; -use FastyBird\Connector\NsPanel\Clients; use FastyBird\Connector\NsPanel\Entities; +use FastyBird\Connector\NsPanel\Exceptions; use FastyBird\Connector\NsPanel\Helpers; -use FastyBird\Connector\NsPanel\Queries; -use FastyBird\DateTimeFactory; -use FastyBird\Library\Bootstrap\Helpers as BootstrapHelpers; -use FastyBird\Library\Metadata\Entities as MetadataEntities; +use FastyBird\Connector\NsPanel\Queue; use FastyBird\Library\Metadata\Exceptions as MetadataExceptions; use FastyBird\Library\Metadata\Types as MetadataTypes; use FastyBird\Module\Devices\Entities as DevicesEntities; @@ -31,13 +26,8 @@ use FastyBird\Module\Devices\Exceptions as DevicesExceptions; use FastyBird\Module\Devices\Models as DevicesModels; use FastyBird\Module\Devices\Queries as DevicesQueries; -use FastyBird\Module\Devices\States as DevicesStates; -use FastyBird\Module\Devices\Utilities as DevicesUtilities; use Nette; -use Nette\Utils; -use Ramsey\Uuid; use Symfony\Component\EventDispatcher; -use Throwable; use function assert; /** @@ -55,17 +45,11 @@ class Event implements Writer, EventDispatcher\EventSubscriberInterface public const NAME = 'event'; - /** @var array */ - private array $clients = []; - public function __construct( - private readonly Helpers\Property $propertyStateHelper, - private readonly DateTimeFactory\Factory $dateTimeFactory, + private readonly Entities\NsPanelConnector $connector, + private readonly Helpers\Entity $entityHelper, + private readonly Queue\Queue $queue, private readonly DevicesModels\Channels\ChannelsRepository $channelsRepository, - private readonly DevicesModels\Channels\Properties\PropertiesRepository $channelsPropertiesRepository, - private readonly DevicesUtilities\DeviceConnection $deviceConnectionManager, - private readonly DevicesUtilities\ChannelPropertiesStates $channelPropertiesStates, - private readonly NsPanel\Logger $logger, ) { } @@ -78,216 +62,125 @@ public static function getSubscribedEvents(): array ]; } - public function connect( - Entities\NsPanelConnector $connector, - Clients\Client $client, - ): void + public function connect(): void { - $this->clients[$connector->getId()->toString()] = $client; + // Nothing to do here } - public function disconnect( - Entities\NsPanelConnector $connector, - Clients\Client $client, - ): void + public function disconnect(): void { - unset($this->clients[$connector->getId()->toString()]); + // Nothing to do here } /** * @throws DevicesExceptions\InvalidState + * @throws Exceptions\Runtime * @throws MetadataExceptions\InvalidArgument * @throws MetadataExceptions\InvalidState */ public function stateChanged( DevicesEvents\ChannelPropertyStateEntityCreated|DevicesEvents\ChannelPropertyStateEntityUpdated $event, ): void - { - foreach ($this->clients as $id => $client) { - if ($client instanceof Clients\Gateway) { - $this->processGatewayClient(Uuid\Uuid::fromString($id), $event, $client); - } elseif ($client instanceof Clients\Device) { - $this->processDeviceClient(Uuid\Uuid::fromString($id), $event, $client); - } - } - } - - /** - * @throws DevicesExceptions\InvalidState - * @throws MetadataExceptions\InvalidArgument - * @throws MetadataExceptions\InvalidState - */ - public function processGatewayClient( - Uuid\UuidInterface $connectorId, - DevicesEvents\ChannelPropertyStateEntityCreated|DevicesEvents\ChannelPropertyStateEntityUpdated $event, - Clients\Client $client, - ): void { $property = $event->getProperty(); $state = $event->getState(); - if ($state->getExpectedValue() === null || $state->getPending() !== true) { - return; - } - if ($property->getChannel() instanceof DevicesEntities\Channels\Channel) { $channel = $property->getChannel(); - assert($channel instanceof Entities\NsPanelChannel); } else { - $findChannelQuery = new Queries\FindChannels(); + $findChannelQuery = new DevicesQueries\FindChannels(); $findChannelQuery->byId($property->getChannel()); - $channel = $this->channelsRepository->findOneBy($findChannelQuery, Entities\NsPanelChannel::class); + $channel = $this->channelsRepository->findOneBy($findChannelQuery); } if ($channel === null) { return; } - if (!$channel->getDevice()->getConnector()->getId()->equals($connectorId)) { - return; - } - $device = $channel->getDevice(); - assert($device instanceof Entities\Devices\SubDevice); - - if ( - $this->deviceConnectionManager->getState($device)->equalsValue(MetadataTypes\ConnectionState::STATE_ALERT) - ) { + if (!$device->getConnector()->getId()->equals($this->connector->getId())) { return; } - $this->writeChannelProperty($client, $connectorId, $device, $channel, $property); - } - - /** - * @throws DevicesExceptions\InvalidState - * @throws MetadataExceptions\InvalidArgument - * @throws MetadataExceptions\InvalidState - */ - public function processDeviceClient( - Uuid\UuidInterface $connectorId, - DevicesEvents\ChannelPropertyStateEntityCreated|DevicesEvents\ChannelPropertyStateEntityUpdated $event, - Clients\Client $client, - ): void - { - $property = $event->getProperty(); - - foreach ($this->findChildren($property) as $child) { - $channel = $property->getChannel(); + if ($device instanceof Entities\Devices\SubDevice) { assert($channel instanceof Entities\NsPanelChannel); - if (!$channel->getDevice()->getConnector()->getId()->equals($connectorId)) { - continue; + if ($state->getExpectedValue() === null || $state->getPending() !== true) { + return; } - $state = $event->getState(); + $this->writeSubDeviceChannelProperty($device, $channel); - if (!$state->isValid() === false) { - continue; - } - - $device = $channel->getDevice(); - - assert($device instanceof Entities\Devices\ThirdPartyDevice); + } elseif ($device instanceof Entities\Devices\ThirdPartyDevice) { + assert($channel instanceof Entities\NsPanelChannel); - if ( - $this->deviceConnectionManager->getState($device)->equalsValue( - MetadataTypes\ConnectionState::STATE_ALERT, - ) - ) { - continue; + if ($state->isValid() !== true) { + return; } - $this->writeChannelProperty($client, $connectorId, $device, $channel, $child); + $this->writeThirdPartyDeviceChannelProperty($device, $channel); } } - private function writeChannelProperty( - Clients\Client $client, - Uuid\UuidInterface $connectorId, - Entities\NsPanelDevice $device, + /** + * @throws Exceptions\Runtime + */ + public function writeSubDeviceChannelProperty( + Entities\Devices\SubDevice $device, Entities\NsPanelChannel $channel, - // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong - DevicesEntities\Channels\Properties\Dynamic|DevicesEntities\Channels\Properties\Mapped|MetadataEntities\DevicesModule\ChannelDynamicProperty|MetadataEntities\DevicesModule\ChannelMappedProperty $property, ): void { - $now = $this->dateTimeFactory->getNow(); - - $client->writeChannelProperty($device, $channel, $property) - ->then(function () use ($property, $now): void { - if ($property instanceof DevicesEntities\Channels\Properties\Dynamic) { - $state = $this->channelPropertiesStates->getValue($property); - - if ($state?->getExpectedValue() !== null) { - $this->propertyStateHelper->setValue( - $property, - Utils\ArrayHash::from([ - DevicesStates\Property::PENDING_KEY => $now->format(DateTimeInterface::ATOM), - ]), - ); - } - } - }) - ->otherwise(function (Throwable $ex) use ($connectorId, $device, $channel, $property): void { - $this->logger->error( - 'Could not write property state', - [ - 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'event-writer', - 'exception' => BootstrapHelpers\Logger::buildException($ex), - 'connector' => [ - 'id' => $connectorId->toString(), - ], - 'device' => [ - 'id' => $device->getId()->toString(), - ], - 'channel' => [ - 'id' => $channel->getId()->toString(), - ], - 'property' => [ - 'id' => $property->getId()->toString(), - ], - ], - ); - - if ($property instanceof DevicesEntities\Channels\Properties\Dynamic) { - $this->propertyStateHelper->setValue( - $property, - Utils\ArrayHash::from([ - DevicesStates\Property::EXPECTED_VALUE_KEY => null, - DevicesStates\Property::PENDING_KEY => false, - ]), - ); - } - }); + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\WriteSubDeviceState::class, + [ + 'connector' => $this->connector->getId()->toString(), + 'device' => $device->getId()->toString(), + 'channel' => $channel->getId()->toString(), + ], + ), + ); } /** - * @return array - * * @throws DevicesExceptions\InvalidState + * @throws Exceptions\Runtime + * @throws MetadataExceptions\InvalidArgument + * @throws MetadataExceptions\InvalidState */ - private function findChildren( - // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong - DevicesEntities\Channels\Properties\Dynamic|MetadataEntities\DevicesModule\ChannelDynamicProperty $property, - ): array + public function writeThirdPartyDeviceChannelProperty( + Entities\Devices\ThirdPartyDevice $device, + Entities\NsPanelChannel $channel, + ): void { - $findPropertyQuery = new DevicesQueries\FindChannelMappedProperties(); - - if ($property instanceof DevicesEntities\Channels\Properties\Dynamic) { - $findPropertyQuery->forParent($property); + if ($device->getGatewayIdentifier() === null) { + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\StoreDeviceConnectionState::class, + [ + 'connector' => $this->connector->getId()->toString(), + 'identifier' => $device->getIdentifier(), + 'state' => MetadataTypes\ConnectionState::STATE_ALERT, + ], + ), + ); - } else { - $findPropertyQuery->byParentId($property->getId()); + return; } - return $this->channelsPropertiesRepository->findAllBy( - $findPropertyQuery, - DevicesEntities\Channels\Properties\Mapped::class, + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\WriteThirdPartyDeviceState::class, + [ + 'connector' => $this->connector->getId()->toString(), + 'device' => $device->getId()->toString(), + 'channel' => $channel->getId()->toString(), + ], + ), ); } diff --git a/src/Writers/EventFactory.php b/src/Writers/EventFactory.php new file mode 100644 index 0000000..732c0db --- /dev/null +++ b/src/Writers/EventFactory.php @@ -0,0 +1,33 @@ + + * @package FastyBird:NsPanelConnector! + * @subpackage Clients + * @since 1.0.0 + * + * @date 09.08.23 + */ + +namespace FastyBird\Connector\NsPanel\Writers; + +use FastyBird\Connector\NsPanel\Entities; + +/** + * System event device state periodic writer factory + * + * @package FastyBird:NsPanelConnector! + * @subpackage Clients + * + * @author Adam Kadlec + */ +interface EventFactory extends WriterFactory +{ + + public function create(Entities\NsPanelConnector $connector): Event; + +} diff --git a/src/Writers/Exchange.php b/src/Writers/Exchange.php index 4cf1592..269146f 100644 --- a/src/Writers/Exchange.php +++ b/src/Writers/Exchange.php @@ -15,28 +15,18 @@ namespace FastyBird\Connector\NsPanel\Writers; -use DateTimeInterface; -use Exception; -use FastyBird\Connector\NsPanel; -use FastyBird\Connector\NsPanel\Clients; use FastyBird\Connector\NsPanel\Entities; +use FastyBird\Connector\NsPanel\Exceptions; use FastyBird\Connector\NsPanel\Helpers; -use FastyBird\DateTimeFactory; -use FastyBird\Library\Bootstrap\Helpers as BootstrapHelpers; +use FastyBird\Connector\NsPanel\Queue; use FastyBird\Library\Exchange\Consumers as ExchangeConsumers; use FastyBird\Library\Metadata\Entities as MetadataEntities; use FastyBird\Library\Metadata\Exceptions as MetadataExceptions; use FastyBird\Library\Metadata\Types as MetadataTypes; -use FastyBird\Module\Devices\Entities as DevicesEntities; use FastyBird\Module\Devices\Exceptions as DevicesExceptions; use FastyBird\Module\Devices\Models as DevicesModels; use FastyBird\Module\Devices\Queries as DevicesQueries; -use FastyBird\Module\Devices\States as DevicesStates; -use FastyBird\Module\Devices\Utilities as DevicesUtilities; use Nette; -use Nette\Utils; -use Ramsey\Uuid; -use Throwable; use function assert; /** @@ -54,46 +44,29 @@ class Exchange implements Writer, ExchangeConsumers\Consumer public const NAME = 'exchange'; - /** @var array */ - private array $clients = []; - public function __construct( - private readonly Helpers\Property $propertyStateHelper, - private readonly DateTimeFactory\Factory $dateTimeFactory, - private readonly DevicesModels\Channels\Properties\PropertiesRepository $channelsPropertiesRepository, - private readonly DevicesUtilities\DeviceConnection $deviceConnectionManager, - private readonly DevicesUtilities\ChannelPropertiesStates $channelPropertiesStates, - private readonly ExchangeConsumers\Container $consumer, - private readonly NsPanel\Logger $logger, + private readonly Entities\NsPanelConnector $connector, + private readonly Helpers\Entity $entityHelper, + private readonly Queue\Queue $queue, + private readonly DevicesModels\Channels\ChannelsRepository $channelsRepository, + private readonly ExchangeConsumers\Container $exchangeConsumer, ) { } - public function connect( - Entities\NsPanelConnector $connector, - Clients\Client $client, - ): void + public function connect(): void { - $this->clients[$connector->getId()->toString()] = $client; - - $this->consumer->enable(self::class); + $this->exchangeConsumer->enable(self::class); } - public function disconnect( - Entities\NsPanelConnector $connector, - Clients\Client $client, - ): void + public function disconnect(): void { - unset($this->clients[$connector->getId()->toString()]); - - if ($this->clients === []) { - $this->consumer->disable(self::class); - } + $this->exchangeConsumer->disable(self::class); } /** * @throws DevicesExceptions\InvalidState - * @throws Exception + * @throws Exceptions\Runtime * @throws MetadataExceptions\InvalidArgument * @throws MetadataExceptions\InvalidState */ @@ -103,175 +76,102 @@ public function consume( MetadataEntities\Entity|null $entity, ): void { - foreach ($this->clients as $id => $client) { - if ($client instanceof Clients\Gateway) { - $this->processGatewayClient(Uuid\Uuid::fromString($id), $source, $entity, $client); - } elseif ($client instanceof Clients\Device) { - $this->processDeviceClient(Uuid\Uuid::fromString($id), $source, $entity, $client); - } - } - } + if ( + $entity instanceof MetadataEntities\DevicesModule\ChannelDynamicProperty + || $entity instanceof MetadataEntities\DevicesModule\ChannelMappedProperty + ) { + $findChannelQuery = new DevicesQueries\FindChannels(); + $findChannelQuery->byId($entity->getChannel()); - /** - * @throws DevicesExceptions\InvalidState - * @throws Exception - * @throws MetadataExceptions\InvalidArgument - * @throws MetadataExceptions\InvalidState - */ - public function processGatewayClient( - Uuid\UuidInterface $connectorId, - MetadataTypes\ModuleSource|MetadataTypes\PluginSource|MetadataTypes\ConnectorSource|MetadataTypes\AutomatorSource $source, - MetadataEntities\Entity|null $entity, - Clients\Gateway $client, - ): void - { - if ($entity instanceof MetadataEntities\DevicesModule\ChannelDynamicProperty) { - if ($entity->getExpectedValue() === null || $entity->getPending() !== true) { + $channel = $this->channelsRepository->findOneBy($findChannelQuery); + + if ($channel === null) { return; } - $findPropertyQuery = new DevicesQueries\FindChannelDynamicProperties(); - $findPropertyQuery->byId($entity->getId()); + $device = $channel->getDevice(); - $property = $this->channelsPropertiesRepository->findOneBy( - $findPropertyQuery, - DevicesEntities\Channels\Properties\Dynamic::class, - ); - - if ($property === null) { + if (!$device->getConnector()->getId()->equals($this->connector->getId())) { return; } - if (!$property->getChannel()->getDevice()->getConnector()->getId()->equals($connectorId)) { - return; - } + if ($device instanceof Entities\Devices\SubDevice) { + assert($channel instanceof Entities\NsPanelChannel); - $device = $property->getChannel()->getDevice(); - $channel = $property->getChannel(); + if ($entity->getExpectedValue() === null || $entity->getPending() !== true) { + return; + } - assert($device instanceof Entities\Devices\SubDevice); - assert($channel instanceof Entities\NsPanelChannel); + $this->writeSubDeviceChannelProperty($device, $channel); - if ( - $this->deviceConnectionManager->getState($device)->equalsValue( - MetadataTypes\ConnectionState::STATE_ALERT, - ) - ) { - return; - } + } elseif ($device instanceof Entities\Devices\ThirdPartyDevice) { + assert($channel instanceof Entities\NsPanelChannel); + + if ($entity->isValid() !== true) { + return; + } - $this->writeChannelProperty($client, $connectorId, $device, $channel, $property); + $this->writeThirdPartyDeviceChannelProperty($device, $channel); + } } } /** - * @throws DevicesExceptions\InvalidState - * @throws Exception - * @throws MetadataExceptions\InvalidArgument - * @throws MetadataExceptions\InvalidState + * @throws Exceptions\Runtime */ - public function processDeviceClient( - Uuid\UuidInterface $connectorId, - MetadataTypes\ModuleSource|MetadataTypes\PluginSource|MetadataTypes\ConnectorSource|MetadataTypes\AutomatorSource $source, - MetadataEntities\Entity|null $entity, - Clients\Device $client, + public function writeSubDeviceChannelProperty( + Entities\Devices\SubDevice $device, + Entities\NsPanelChannel $channel, ): void { - if ( - $entity instanceof MetadataEntities\DevicesModule\ChannelMappedProperty - && $entity->isValid() !== false - ) { - $findPropertyQuery = new DevicesQueries\FindChannelMappedProperties(); - $findPropertyQuery->byId($entity->getId()); - - $property = $this->channelsPropertiesRepository->findOneBy( - $findPropertyQuery, - DevicesEntities\Channels\Properties\Mapped::class, - ); - - if ($property === null) { - return; - } - - if (!$property->getChannel()->getDevice()->getConnector()->getId()->equals($connectorId)) { - return; - } - - $device = $property->getChannel()->getDevice(); - $channel = $property->getChannel(); - - assert($device instanceof Entities\Devices\ThirdPartyDevice); - assert($channel instanceof Entities\NsPanelChannel); - - if ( - $this->deviceConnectionManager->getState($device)->equalsValue( - MetadataTypes\ConnectionState::STATE_ALERT, - ) - ) { - return; - } - - $this->writeChannelProperty($client, $connectorId, $device, $channel, $property); - } + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\WriteSubDeviceState::class, + [ + 'connector' => $this->connector->getId()->toString(), + 'device' => $device->getId()->toString(), + 'channel' => $channel->getId()->toString(), + ], + ), + ); } - private function writeChannelProperty( - Clients\Client $client, - Uuid\UuidInterface $connectorId, - Entities\NsPanelDevice $device, + /** + * @throws DevicesExceptions\InvalidState + * @throws Exceptions\Runtime + * @throws MetadataExceptions\InvalidArgument + * @throws MetadataExceptions\InvalidState + */ + public function writeThirdPartyDeviceChannelProperty( + Entities\Devices\ThirdPartyDevice $device, Entities\NsPanelChannel $channel, - DevicesEntities\Channels\Properties\Dynamic|DevicesEntities\Channels\Properties\Mapped $property, ): void { - $now = $this->dateTimeFactory->getNow(); - - $client->writeChannelProperty($device, $channel, $property) - ->then(function () use ($property, $now): void { - if ($property instanceof DevicesEntities\Channels\Properties\Dynamic) { - $state = $this->channelPropertiesStates->getValue($property); - - if ($state?->getExpectedValue() !== null) { - $this->propertyStateHelper->setValue( - $property, - Utils\ArrayHash::from([ - DevicesStates\Property::PENDING_KEY => $now->format(DateTimeInterface::ATOM), - ]), - ); - } - } - }) - ->otherwise(function (Throwable $ex) use ($connectorId, $device, $channel, $property): void { - $this->logger->error( - 'Could not write property state', + if ($device->getGatewayIdentifier() === null) { + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\StoreDeviceConnectionState::class, [ - 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'exchange-writer', - 'exception' => BootstrapHelpers\Logger::buildException($ex), - 'connector' => [ - 'id' => $connectorId->toString(), - ], - 'device' => [ - 'id' => $device->getId()->toString(), - ], - 'channel' => [ - 'id' => $channel->getId()->toString(), - ], - 'property' => [ - 'id' => $property->getId()->toString(), - ], + 'connector' => $this->connector->getId()->toString(), + 'identifier' => $device->getIdentifier(), + 'state' => MetadataTypes\ConnectionState::STATE_ALERT, ], - ); + ), + ); - if ($property instanceof DevicesEntities\Channels\Properties\Dynamic) { - $this->propertyStateHelper->setValue( - $property, - Utils\ArrayHash::from([ - DevicesStates\Property::EXPECTED_VALUE_KEY => null, - DevicesStates\Property::PENDING_KEY => false, - ]), - ); - } - }); + return; + } + + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\WriteThirdPartyDeviceState::class, + [ + 'connector' => $this->connector->getId()->toString(), + 'device' => $device->getId()->toString(), + 'channel' => $channel->getId()->toString(), + ], + ), + ); } } diff --git a/src/Writers/ExchangeFactory.php b/src/Writers/ExchangeFactory.php new file mode 100644 index 0000000..7b739aa --- /dev/null +++ b/src/Writers/ExchangeFactory.php @@ -0,0 +1,33 @@ + + * @package FastyBird:NsPanelConnector! + * @subpackage Clients + * @since 1.0.0 + * + * @date 09.08.23 + */ + +namespace FastyBird\Connector\NsPanel\Writers; + +use FastyBird\Connector\NsPanel\Entities; + +/** + * Event bus exchange device state periodic writer factory + * + * @package FastyBird:NsPanelConnector! + * @subpackage Clients + * + * @author Adam Kadlec + */ +interface ExchangeFactory extends WriterFactory +{ + + public function create(Entities\NsPanelConnector $connector): Exchange; + +} diff --git a/src/Writers/Periodic.php b/src/Writers/Periodic.php index e36b6f7..d3ab7e9 100644 --- a/src/Writers/Periodic.php +++ b/src/Writers/Periodic.php @@ -16,28 +16,23 @@ namespace FastyBird\Connector\NsPanel\Writers; use DateTimeInterface; -use FastyBird\Connector\NsPanel; -use FastyBird\Connector\NsPanel\Clients; use FastyBird\Connector\NsPanel\Entities; use FastyBird\Connector\NsPanel\Exceptions; use FastyBird\Connector\NsPanel\Helpers; use FastyBird\Connector\NsPanel\Queries; +use FastyBird\Connector\NsPanel\Queue; use FastyBird\DateTimeFactory; -use FastyBird\Library\Bootstrap\Helpers as BootstrapHelpers; use FastyBird\Library\Metadata\Exceptions as MetadataExceptions; use FastyBird\Library\Metadata\Types as MetadataTypes; use FastyBird\Module\Devices\Entities as DevicesEntities; use FastyBird\Module\Devices\Exceptions as DevicesExceptions; use FastyBird\Module\Devices\Models as DevicesModels; use FastyBird\Module\Devices\Queries as DevicesQueries; -use FastyBird\Module\Devices\States as DevicesStates; use FastyBird\Module\Devices\Utilities as DevicesUtilities; use Nette; -use Nette\Utils; -use Ramsey\Uuid; use React\EventLoop; -use Throwable; use function array_key_exists; +use function array_merge; use function in_array; /** @@ -72,13 +67,12 @@ class Periodic implements Writer /** @var array */ private array $lastReportedValue = []; - /** @var array */ - private array $clients = []; - private EventLoop\TimerInterface|null $handlerTimer = null; public function __construct( - private readonly Helpers\Property $propertyStateHelper, + private readonly Entities\NsPanelConnector $connector, + private readonly Helpers\Entity $entityHelper, + private readonly Queue\Queue $queue, private readonly DevicesModels\Devices\DevicesRepository $devicesRepository, private readonly DevicesModels\Channels\ChannelsRepository $channelsRepository, private readonly DevicesModels\Channels\Properties\PropertiesRepository $channelsPropertiesRepository, @@ -86,18 +80,12 @@ public function __construct( private readonly DevicesUtilities\ChannelPropertiesStates $channelPropertiesStates, private readonly DateTimeFactory\Factory $dateTimeFactory, private readonly EventLoop\LoopInterface $eventLoop, - private readonly NsPanel\Logger $logger, ) { } - public function connect( - Entities\NsPanelConnector $connector, - Clients\Client $client, - ): void + public function connect(): void { - $this->clients[$connector->getId()->toString()] = $client; - $this->processedDevices = []; $this->processedProperties = []; $this->lastReportedValue = []; @@ -110,14 +98,9 @@ function (): void { ); } - public function disconnect( - Entities\NsPanelConnector $connector, - Clients\Client $client, - ): void + public function disconnect(): void { - unset($this->clients[$connector->getId()->toString()]); - - if ($this->clients === [] && $this->handlerTimer !== null) { + if ($this->handlerTimer !== null) { $this->eventLoop->cancelTimer($this->handlerTimer); $this->handlerTimer = null; @@ -126,63 +109,53 @@ public function disconnect( /** * @throws DevicesExceptions\InvalidState - * @throws Exceptions\InvalidArgument - * @throws Exceptions\InvalidState * @throws Exceptions\Runtime * @throws MetadataExceptions\InvalidArgument * @throws MetadataExceptions\InvalidState */ private function handleCommunication(): void { - foreach ($this->clients as $id => $client) { - $devices = []; + $findDevicesQuery = new Queries\FindSubDevices(); + $findDevicesQuery->forConnector($this->connector); - if ($client instanceof Clients\Gateway) { - $findDevicesQuery = new Queries\FindSubDevices(); - $findDevicesQuery->byConnectorId(Uuid\Uuid::fromString($id)); + $subDevices = $this->devicesRepository->findAllBy( + $findDevicesQuery, + Entities\Devices\SubDevice::class, + ); - $devices = $this->devicesRepository->findAllBy( - $findDevicesQuery, - Entities\Devices\SubDevice::class, - ); - } elseif ($client instanceof Clients\Device) { - $findDevicesQuery = new Queries\FindThirdPartyDevices(); - $findDevicesQuery->byConnectorId(Uuid\Uuid::fromString($id)); + $findDevicesQuery = new Queries\FindThirdPartyDevices(); + $findDevicesQuery->forConnector($this->connector); - $devices = $this->devicesRepository->findAllBy( - $findDevicesQuery, - Entities\Devices\ThirdPartyDevice::class, - ); - } + $thirdPartyDevices = $this->devicesRepository->findAllBy( + $findDevicesQuery, + Entities\Devices\ThirdPartyDevice::class, + ); - foreach ($devices as $device) { - if (!in_array($device->getId()->toString(), $this->processedDevices, true)) { - $this->processedDevices[] = $device->getId()->toString(); + foreach (array_merge($subDevices, $thirdPartyDevices) as $device) { + if (!in_array($device->getId()->toString(), $this->processedDevices, true)) { + $this->processedDevices[] = $device->getId()->toString(); - if ( - $client instanceof Clients\Gateway - && $device instanceof Entities\Devices\SubDevice - && !$this->deviceConnectionManager->getState($device)->equalsValue( - MetadataTypes\ConnectionState::STATE_ALERT, - ) - ) { - if ($this->writeSubDeviceChannelProperty($client, $device)) { - $this->registerLoopHandler(); - - return; - } - } elseif ( - $client instanceof Clients\Device - && $device instanceof Entities\Devices\ThirdPartyDevice - && !$this->deviceConnectionManager->getState($device)->equalsValue( - MetadataTypes\ConnectionState::STATE_ALERT, - ) - ) { - if ($this->writeThirdPartyDeviceChannelProperty($client, $device)) { - $this->registerLoopHandler(); + if ( + $device instanceof Entities\Devices\SubDevice + && !$this->deviceConnectionManager->getState($device)->equalsValue( + MetadataTypes\ConnectionState::STATE_ALERT, + ) + ) { + if ($this->writeSubDeviceChannelProperty($device)) { + $this->registerLoopHandler(); - return; - } + return; + } + } elseif ( + $device instanceof Entities\Devices\ThirdPartyDevice + && !$this->deviceConnectionManager->getState($device)->equalsValue( + MetadataTypes\ConnectionState::STATE_ALERT, + ) + ) { + if ($this->writeThirdPartyDeviceChannelProperty($device)) { + $this->registerLoopHandler(); + + return; } } } @@ -195,13 +168,11 @@ private function handleCommunication(): void /** * @throws DevicesExceptions\InvalidState - * @throws Exceptions\InvalidArgument - * @throws Exceptions\InvalidState + * @throws Exceptions\Runtime * @throws MetadataExceptions\InvalidArgument * @throws MetadataExceptions\InvalidState */ private function writeSubDeviceChannelProperty( - Clients\Gateway $client, Entities\Devices\SubDevice $device, ): bool { @@ -264,55 +235,16 @@ private function writeSubDeviceChannelProperty( ) { $this->processedProperties[$property->getId()->toString()] = $now; - $client->writeChannelProperty($device, $channel, $property) - ->then(function () use ($property, $now): void { - unset($this->processedProperties[$property->getId()->toString()]); - - $state = $this->channelPropertiesStates->getValue($property); - - if ($state?->getExpectedValue() !== null) { - $this->propertyStateHelper->setValue( - $property, - Utils\ArrayHash::from([ - DevicesStates\Property::PENDING_KEY => $now->format( - DateTimeInterface::ATOM, - ), - ]), - ); - } - }) - ->otherwise(function (Throwable $ex) use ($device, $channel, $property): void { - $this->logger->error( - 'Could not write property state', - [ - 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'periodic-writer', - 'exception' => BootstrapHelpers\Logger::buildException($ex), - 'connector' => [ - 'id' => $device->getConnector()->getId()->toString(), - ], - 'device' => [ - 'id' => $device->getId()->toString(), - ], - 'channel' => [ - 'id' => $channel->getId()->toString(), - ], - 'property' => [ - 'id' => $property->getId()->toString(), - ], - ], - ); - - $this->propertyStateHelper->setValue( - $property, - Utils\ArrayHash::from([ - DevicesStates\Property::EXPECTED_VALUE_KEY => null, - DevicesStates\Property::PENDING_KEY => false, - ]), - ); - }); - - return true; + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\WriteSubDeviceState::class, + [ + 'connector' => $this->connector->getId()->toString(), + 'device' => $device->getId()->toString(), + 'channel' => $channel->getId()->toString(), + ], + ), + ); } } } @@ -323,19 +255,33 @@ private function writeSubDeviceChannelProperty( /** * @throws DevicesExceptions\InvalidState - * @throws Exceptions\InvalidArgument - * @throws Exceptions\InvalidState * @throws Exceptions\Runtime * @throws MetadataExceptions\InvalidArgument * @throws MetadataExceptions\InvalidState */ private function writeThirdPartyDeviceChannelProperty( - Clients\Device $client, Entities\Devices\ThirdPartyDevice $device, ): bool { $now = $this->dateTimeFactory->getNow(); + $serialNumber = $device->getGatewayIdentifier(); + + if ($serialNumber === null) { + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\StoreDeviceConnectionState::class, + [ + 'connector' => $this->connector->getId()->toString(), + 'identifier' => $device->getIdentifier(), + 'state' => MetadataTypes\ConnectionState::STATE_ALERT, + ], + ), + ); + + return false; + } + $findChannelsQuery = new Queries\FindChannels(); $findChannelsQuery->forDevice($device); @@ -395,32 +341,16 @@ private function writeThirdPartyDeviceChannelProperty( $this->processedProperties[$property->getId()->toString()] = $now; $this->lastReportedValue[$property->getId()->toString()] = $propertyValue; - $client->writeChannelProperty($device, $channel, $property) - ->then(function () use ($property): void { - unset($this->processedProperties[$property->getId()->toString()]); - }) - ->otherwise(function (Throwable $ex) use ($device, $channel, $property): void { - $this->logger->error( - 'Could not write property state', - [ - 'source' => MetadataTypes\ConnectorSource::SOURCE_CONNECTOR_NS_PANEL, - 'type' => 'periodic-writer', - 'exception' => BootstrapHelpers\Logger::buildException($ex), - 'connector' => [ - 'id' => $device->getConnector()->getId()->toString(), - ], - 'device' => [ - 'id' => $device->getId()->toString(), - ], - 'channel' => [ - 'id' => $channel->getId()->toString(), - ], - 'property' => [ - 'id' => $property->getId()->toString(), - ], - ], - ); - }); + $this->queue->append( + $this->entityHelper->create( + Entities\Messages\WriteThirdPartyDeviceState::class, + [ + 'connector' => $this->connector->getId()->toString(), + 'device' => $device->getId()->toString(), + 'channel' => $channel->getId()->toString(), + ], + ), + ); return true; } diff --git a/src/Writers/PeriodicFactory.php b/src/Writers/PeriodicFactory.php new file mode 100644 index 0000000..5343e5a --- /dev/null +++ b/src/Writers/PeriodicFactory.php @@ -0,0 +1,33 @@ + + * @package FastyBird:NsPanelConnector! + * @subpackage Clients + * @since 1.0.0 + * + * @date 09.08.23 + */ + +namespace FastyBird\Connector\NsPanel\Writers; + +use FastyBird\Connector\NsPanel\Entities; + +/** + * Event loop device state periodic writer factory + * + * @package FastyBird:NsPanelConnector! + * @subpackage Clients + * + * @author Adam Kadlec + */ +interface PeriodicFactory extends WriterFactory +{ + + public function create(Entities\NsPanelConnector $connector): Periodic; + +} diff --git a/src/Writers/Writer.php b/src/Writers/Writer.php index 7b8330a..f0c6a09 100644 --- a/src/Writers/Writer.php +++ b/src/Writers/Writer.php @@ -15,9 +15,6 @@ namespace FastyBird\Connector\NsPanel\Writers; -use FastyBird\Connector\NsPanel\Clients; -use FastyBird\Connector\NsPanel\Entities; - /** * Properties writer interface * @@ -29,14 +26,8 @@ interface Writer { - public function connect( - Entities\NsPanelConnector $connector, - Clients\Client $client, - ): void; + public function connect(): void; - public function disconnect( - Entities\NsPanelConnector $connector, - Clients\Client $client, - ): void; + public function disconnect(): void; } diff --git a/src/Writers/WriterFactory.php b/src/Writers/WriterFactory.php new file mode 100644 index 0000000..bd3e2e0 --- /dev/null +++ b/src/Writers/WriterFactory.php @@ -0,0 +1,33 @@ + + * @package FastyBird:NsPanelConnector! + * @subpackage Clients + * @since 1.0.0 + * + * @date 09.08.23 + */ + +namespace FastyBird\Connector\NsPanel\Writers; + +use FastyBird\Connector\NsPanel\Entities; + +/** + * Device state writer interface factory + * + * @package FastyBird:NsPanelConnector! + * @subpackage Clients + * + * @author Adam Kadlec + */ +interface WriterFactory +{ + + public function create(Entities\NsPanelConnector $connector): Writer; + +} diff --git a/tests/cases/unit/Clients/DiscoveryTest.php b/tests/cases/unit/Clients/DiscoveryTest.php index 5120002..8bf0d37 100644 --- a/tests/cases/unit/Clients/DiscoveryTest.php +++ b/tests/cases/unit/Clients/DiscoveryTest.php @@ -5,10 +5,10 @@ use Error; use FastyBird\Connector\NsPanel\API; use FastyBird\Connector\NsPanel\Clients; -use FastyBird\Connector\NsPanel\Consumers; use FastyBird\Connector\NsPanel\Entities; use FastyBird\Connector\NsPanel\Exceptions; use FastyBird\Connector\NsPanel\Queries; +use FastyBird\Connector\NsPanel\Queue; use FastyBird\Connector\NsPanel\Tests\Cases\Unit\DbTestCase; use FastyBird\Connector\NsPanel\Types; use FastyBird\Library\Bootstrap\Exceptions as BootstrapExceptions; @@ -160,11 +160,13 @@ public function testDiscover(): void $eventLoop->run(); - $consumer = $this->getContainer()->getByType(Consumers\Messages::class); + $queue = $this->getContainer()->getByType(Queue\Queue::class); - self::assertFalse($consumer->isEmpty()); + self::assertFalse($queue->isEmpty()); - $consumer->consume(); + $consumers = $this->getContainer()->getByType(Queue\Consumers::class); + + $consumers->consume(); $devicesRepository = $this->getContainer()->getByType(DevicesModels\Devices\DevicesRepository::class);