Lines
87.09%
27 / 31
Methods
83.33%
5 / 6
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 3 / 3 | 100.00% | 1 / 1 | 1 | ||
| openSession | 100.00% | 3 / 3 | 100.00% | 1 / 1 | 1 | ||
| relinkAfterSessionEnded | 100.00% | 10 / 10 | 100.00% | 1 / 1 | 3 | ||
| markLinkLost | 100.00% | 3 / 3 | 100.00% | 1 / 1 | 1 | ||
| relinkUntilSettled | 55.55% | 5 / 9 | 0.00% | 0 / 1 | 9.16 | ||
| hasSettled | 100.00% | 3 / 3 | 100.00% | 1 / 1 | 3 | ||
| 20 | final class ComponentSession | |
| 21 | { | |
| 22 | private ?DeferredCancellation $relinking = null; | |
| 23 | ||
| 24 | public function __construct( | |
| 25 | private readonly ComponentLink $link, | |
| 26 | private readonly ExposureLink $exposures, | |
| 27 | private readonly ExposureReconciler $reconciler, | |
| 28 | private readonly ComponentTracker $tracker, | |
| 29 | private readonly StewartConfig $config, | |
| 30 | private readonly Deadlines $deadlines, | |
| 31 | private readonly LoggerInterface $logger, | |
| 32 | ) { | |
| 33 | $link->onCommand($exposures->receiveCommand(...)); | |
| 34 | $link->onSessionEnded($this->relinkAfterSessionEnded(...)); | |
| 35 | $exposures->onSessionLost($this->relinkAfterSessionEnded(...)); | |
| 36 | } | |
| 37 | ||
| 38 | /** @throws HaClientException */ | |
| 39 | public function openSession(): void | |
| 40 | { | |
| 41 | $this->link->establishLink(); | |
| 42 | $this->exposures->replayAll(); | |
| 43 | $this->reconciler->reconcileIfDue(); | |
| 44 | } | |
| 45 | ||
| 46 | public function relinkAfterSessionEnded(): void | |
| 47 | { | |
| 48 | // Also when a relink runs, so a session lost during its replay keeps it going. | |
| 49 | $this->link->markLinkLost(); | |
| 50 | ||
| 51 | if ($this->relinking !== null) { | |
| 52 | return; | |
| 53 | } | |
| 54 | ||
| 55 | $this->logger->info('The stewart Home Assistant integration session ended; opening a new one'); | |
| 56 | $relinking = $this->relinking = new DeferredCancellation(); | |
| 57 | ||
| 58 | async(function () use ($relinking): void { | |
| 59 | try { | |
| 60 | $this->relinkUntilSettled($relinking->getCancellation()); | |
| 61 | } finally { | |
| 62 | if ($this->relinking === $relinking) { | |
| 63 | $this->relinking = null; | |
| 64 | } | |
| 65 | } | |
| 66 | })->ignore(); | |
| 67 | } | |
| 68 | ||
| 69 | public function markLinkLost(): void | |
| 70 | { | |
| 71 | $this->relinking?->cancel(); | |
| 72 | $this->relinking = null; | |
| 73 | $this->link->markLinkLost(); | |
| 74 | } | |
| 75 | ||
| 76 | private function relinkUntilSettled(Cancellation $stop): void | |
| 77 | { | |
| 78 | for ($attempt = 1; ; ++$attempt) { | |
| 79 | try { | |
| 80 | $this->openSession(); | |
| 81 | } catch (HaClientException $e) { | |
| 82 | $this->logger->debug('Could not open the stewart integration session yet', ['exception' => $e, 'attempt' => $attempt]); | |
| 83 | } | |
| 84 | ||
| 85 | if ($stop->isRequested() || $this->hasSettled()) { | |
| 86 | return; | |
| 87 | } | |
| 88 | ||
| 89 | try { | |
| 90 | $this->deadlines->delay($this->config->reconnectBackoff->delayFor($attempt), $stop); | |
| 91 | } catch (CancelledException) { | |
| 92 | return; | |
| 93 | } | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | // A missing component is usually one that Home Assistant is reloading. | |
| 98 | private function hasSettled(): bool | |
| 99 | { | |
| 100 | return match ($this->tracker->detection->state) { | |
| 101 | ComponentState::Unchecked, ComponentState::Missing => false, | |
| 102 | ComponentState::ProtocolMismatch, ComponentState::Refused, ComponentState::Replaced, ComponentState::Active => true, | |
| 103 | }; | |
| 104 | } | |
| 105 | } |