Passed
Push — master ( 487089...443b76 )
by Chris
03:30
created

MessageEncoder   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 421
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 421
ccs 0
cts 196
cp 0
rs 8.6
c 0
b 0
f 0
wmc 37

34 Methods

Rating   Name   Duplication   Size   Complexity  
A encodeStateInfo() 0 5 1
A encodeGroup() 0 7 1
A encodeStateHostFirmware() 0 3 1
A encodeStateLightPower() 0 3 1
A encodeSetWaveform() 0 8 1
A encodeUnknownMessage() 0 3 1
A encodeFirmware() 0 7 1
A encodeSetLightPower() 0 5 1
A encodeStateInfrared() 0 3 1
A encodeStateLocation() 0 3 1
A encodeEchoRequest() 0 3 1
A signedShortToUnsignedShort() 0 7 2
A encodeHsbkColor() 0 3 1
A encodeSetColor() 0 5 1
A encodeSetLabel() 0 3 1
A encodeNetworkInfo() 0 8 1
A encodeState() 0 10 1
A encodeStateWifiFirmware() 0 3 1
A encodeEchoResponse() 0 3 1
A encodeMessage() 0 5 2
A encodeLocation() 0 7 1
A encodeSetInfrared() 0 3 1
A dateTimeToNanoseconds() 0 9 2
A encodeStateService() 0 5 1
A encodeStateGroup() 0 3 1
A encodeSetWaveformOptional() 0 18 1
A encodeSetGroup() 0 3 1
A encodeStateDevicePower() 0 3 1
A encodeStateLabel() 0 3 1
A encodeStateWifiInfo() 0 3 1
A encodeStateVersion() 0 5 1
A encodeSetLocation() 0 3 1
A encodeSetDevicePower() 0 3 1
A encodeStateHostInfo() 0 3 1
1
<?php declare(strict_types=1);
2
3
namespace DaveRandom\LibLifxLan\Encoding;
4
5
use DaveRandom\LibLifxLan\DataTypes as DeviceDataTypes;
6
use DaveRandom\LibLifxLan\DataTypes\Light as LightDataTypes;
7
use DaveRandom\LibLifxLan\Encoding\Exceptions\InvalidMessageException;
8
use DaveRandom\LibLifxLan\Messages\Device\Commands as DeviceCommands;
9
use DaveRandom\LibLifxLan\Messages\Device\Requests as DeviceRequests;
10
use DaveRandom\LibLifxLan\Messages\Device\Responses as DeviceResponses;
11
use DaveRandom\LibLifxLan\Messages\Light\Commands as LightCommands;
12
use DaveRandom\LibLifxLan\Messages\Light\Responses as LightResponses;
13
use DaveRandom\LibLifxLan\Messages\Message;
14
use DaveRandom\LibLifxLan\Messages\UnknownMessage;
15
use const DaveRandom\LibLifxLan\FLOAT32_CODE;
16
17
final class MessageEncoder
18
{
19
    /**
20
     * @uses encodeUnknownMessage
21
     * @uses encodeEchoRequest
22
     * @uses encodeEchoResponse
23
     * @uses encodeSetGroup
24
     * @uses encodeStateGroup
25
     * @uses encodeStateHostFirmware
26
     * @uses encodeStateHostInfo
27
     * @uses encodeStateInfo
28
     * @uses encodeSetLabel
29
     * @uses encodeStateLabel
30
     * @uses encodeSetLocation
31
     * @uses encodeStateLocation
32
     * @uses encodeSetDevicePower
33
     * @uses encodeStateDevicePower
34
     * @uses encodeStateService
35
     * @uses encodeStateVersion
36
     * @uses encodeStateWifiFirmware
37
     * @uses encodeStateWifiInfo
38
     * @uses encodeSetColor
39
     * @uses encodeSetWaveform
40
     * @uses encodeSetWaveformOptional
41
     * @uses encodeState
42
     * @uses encodeSetInfrared
43
     * @uses encodeStateInfrared
44
     * @uses encodeSetLightPower
45
     * @uses encodeStateLightPower
46
     */
47
    private const ENCODING_ROUTINES = [
48
        UnknownMessage::class => 'UnknownMessage',
49
50
        // Device command messages
51
        DeviceCommands\SetGroup::class => 'SetGroup',
52
        DeviceCommands\SetLabel::class => 'SetLabel',
53
        DeviceCommands\SetLocation::class => 'SetLocation',
54
        DeviceCommands\SetPower::class => 'SetDevicePower',
55
56
        // Device request messages
57
        DeviceRequests\EchoRequest::class => 'EchoRequest',
58
59
        // Device response messages
60
        DeviceResponses\EchoResponse::class => 'EchoResponse',
61
        DeviceResponses\StateGroup::class => 'StateGroup',
62
        DeviceResponses\StateHostFirmware::class => 'StateHostFirmware',
63
        DeviceResponses\StateHostInfo::class => 'StateHostInfo',
64
        DeviceResponses\StateInfo::class => 'StateInfo',
65
        DeviceResponses\StateLabel::class => 'StateLabel',
66
        DeviceResponses\StateLocation::class => 'StateLocation',
67
        DeviceResponses\StatePower::class => 'StateDevicePower',
68
        DeviceResponses\StateService::class => 'StateService',
69
        DeviceResponses\StateVersion::class => 'StateVersion',
70
        DeviceResponses\StateWifiFirmware::class => 'StateWifiFirmware',
71
        DeviceResponses\StateWifiInfo::class => 'StateWifiInfo',
72
73
        // Light command messages
74
        LightCommands\SetColor::class => 'SetColor',
75
        LightCommands\SetInfrared::class => 'SetInfrared',
76
        LightCommands\SetPower::class => 'SetLightPower',
77
        LightCommands\SetWaveform::class => 'SetWaveform',
78
        LightCommands\SetWaveformOptional::class => 'SetWaveformOptional',
79
80
        // Light response messages
81
        LightResponses\State::class => 'State',
82
        LightResponses\StateInfrared::class => 'StateInfrared',
83
        LightResponses\StatePower::class => 'StateLightPower',
84
    ];
85
86
    private function signedShortToUnsignedShort(int $signed): int
87
    {
88
        if ($signed >= 0) {
89
            return $signed & 0x7fff;
90
        }
91
92
        return 0x8000 | (($signed & 0x7fff) + 1);
93
    }
94
95
    private function encodeHsbkColor(LightDataTypes\HsbkColor $color): string
96
    {
97
        return \pack('v4', $color->getHue(), $color->getSaturation(), $color->getBrightness(), $color->getTemperature());
98
    }
99
100
    /**
101
     * @param DeviceDataTypes\Location $location
102
     * @return string
103
     * @throws InvalidMessageException
104
     */
105
    private function encodeLocation(DeviceDataTypes\Location $location): string
106
    {
107
        return \pack(
108
            'a16a32P',
109
            $location->getGuid()->getBytes(),
110
            $location->getLabel()->getValue(),
111
            $this->dateTimeToNanoseconds($location->getUpdatedAt())
112
        );
113
    }
114
115
    /**
116
     * @param DeviceDataTypes\Group $group
117
     * @return string
118
     * @throws InvalidMessageException
119
     */
120
    private function encodeGroup(DeviceDataTypes\Group $group): string
121
    {
122
        return \pack(
123
            'a16a32P',
124
            $group->getGuid()->getBytes(),
125
            $group->getLabel()->getValue(),
126
            $this->dateTimeToNanoseconds($group->getUpdatedAt())
127
        );
128
    }
129
130
    /**
131
     * @param DeviceDataTypes\Firmware $firmware
132
     * @return string
133
     * @throws InvalidMessageException
134
     */
135
    private function encodeFirmware(DeviceDataTypes\Firmware $firmware)
136
    {
137
        return \pack(
138
            'PPV',
139
            $this->dateTimeToNanoseconds($firmware->getBuild()),
140
            0, // reserved
141
            $firmware->getVersion()
142
        );
143
    }
144
145
    private function encodeNetworkInfo(DeviceDataTypes\NetworkInfo $info): string
146
    {
147
        return \pack(
148
            FLOAT32_CODE . 'VVv',
149
            $info->getSignal(),
150
            $info->getTx(),
151
            $info->getRx(),
152
            0 // reserved
153
        );
154
    }
155
156
    /**
157
     * @param \DateTimeInterface $dateTime
158
     * @return int
159
     * @throws InvalidMessageException
160
     */
161
    private function dateTimeToNanoseconds(\DateTimeInterface $dateTime): int
162
    {
163
        $result = ($dateTime->format('U') * 1000000000) + ($dateTime->format('u') * 1000);
164
165
        if ($result < 0) {
166
            throw new InvalidMessageException("Timestamp {$dateTime->format('Y-m-d H:i:s.u')} is negative");
167
        }
168
169
        return $result;
170
    }
171
172
    private function encodeUnknownMessage(UnknownMessage $message): string
173
    {
174
        return $message->getData();
175
    }
176
177
    private function encodeStateVersion(DeviceResponses\StateVersion $message): string
178
    {
179
        $version = $message->getVersion();
180
181
        return \pack('VVV', $version->getVendor(), $version->getProduct(), $version->getVersion());
182
    }
183
184
    private function encodeStateService(DeviceResponses\StateService $message): string
185
    {
186
        $service = $message->getService();
187
188
        return \pack('CV', $service->getTypeId(), $service->getPort());
189
    }
190
191
    /**
192
     * @param DeviceResponses\StateInfo $message
193
     * @return string
194
     * @throws InvalidMessageException
195
     */
196
    private function encodeStateInfo(DeviceResponses\StateInfo $message): string
197
    {
198
        $info = $message->getInfo();
199
200
        return \pack('PPP', $this->dateTimeToNanoseconds($info->getTime()), $info->getUptime(), $info->getDowntime());
201
    }
202
203
    /**
204
     * @param DeviceResponses\StateHostFirmware $message
205
     * @return string
206
     * @throws InvalidMessageException
207
     */
208
    private function encodeStateHostFirmware(DeviceResponses\StateHostFirmware $message): string
209
    {
210
        return $this->encodeFirmware($message->getHostFirmware());
211
    }
212
213
    private function encodeStateHostInfo(DeviceResponses\StateHostInfo $message): string
214
    {
215
        return $this->encodeNetworkInfo($message->getHostInfo());
216
    }
217
218
    /**
219
     * @param DeviceResponses\StateWifiFirmware $message
220
     * @return string
221
     * @throws InvalidMessageException
222
     */
223
    private function encodeStateWifiFirmware(DeviceResponses\StateWifiFirmware $message): string
224
    {
225
        return $this->encodeFirmware($message->getWifiFirmware());
226
    }
227
228
    private function encodeStateWifiInfo(DeviceResponses\StateWifiInfo $message): string
229
    {
230
        return $this->encodeNetworkInfo($message->getWifiInfo());
231
    }
232
233
    /**
234
     * @param DeviceCommands\SetGroup $message
235
     * @return string
236
     * @throws InvalidMessageException
237
     */
238
    private function encodeSetGroup(DeviceCommands\SetGroup $message): string
239
    {
240
        return $this->encodeGroup($message->getGroup());
241
    }
242
243
    /**
244
     * @param DeviceResponses\StateGroup $message
245
     * @return string
246
     * @throws InvalidMessageException
247
     */
248
    private function encodeStateGroup(DeviceResponses\StateGroup $message): string
249
    {
250
        return $this->encodeGroup($message->getGroup());
251
    }
252
253
    /**
254
     * @param DeviceCommands\SetLabel $message
255
     * @return string
256
     */
257
    private function encodeSetLabel(DeviceCommands\SetLabel $message): string
258
    {
259
        return \pack('a32', $message->getLabel()->getValue());
260
    }
261
262
    /**
263
     * @param DeviceResponses\StateLabel $message
264
     * @return string
265
     */
266
    private function encodeStateLabel(DeviceResponses\StateLabel $message): string
267
    {
268
        return \pack('a32', $message->getLabel()->getValue());
269
    }
270
271
    /**
272
     * @param DeviceCommands\SetLocation $message
273
     * @return string
274
     * @throws InvalidMessageException
275
     */
276
    private function encodeSetLocation(DeviceCommands\SetLocation $message): string
277
    {
278
        return $this->encodeLocation($message->getLocation());
279
    }
280
281
    /**
282
     * @param DeviceResponses\StateLocation $message
283
     * @return string
284
     * @throws InvalidMessageException
285
     */
286
    private function encodeStateLocation(DeviceResponses\StateLocation $message): string
287
    {
288
        return $this->encodeLocation($message->getLocation());
289
    }
290
291
    /**
292
     * @param DeviceCommands\SetPower $message
293
     * @return string
294
     */
295
    private function encodeSetDevicePower(DeviceCommands\SetPower $message): string
296
    {
297
        return \pack('v', $message->getLevel());
298
    }
299
300
    /**
301
     * @param DeviceResponses\StatePower $message
302
     * @return string
303
     */
304
    private function encodeStateDevicePower(DeviceResponses\StatePower $message): string
305
    {
306
        return \pack('v', $message->getLevel());
307
    }
308
309
    /**
310
     * @param DeviceRequests\EchoRequest $message
311
     * @return string
312
     */
313
    private function encodeEchoRequest(DeviceRequests\EchoRequest $message): string
314
    {
315
        return $message->getPayload();
316
    }
317
318
    /**
319
     * @param DeviceResponses\EchoResponse $message
320
     * @return string
321
     */
322
    private function encodeEchoResponse(DeviceResponses\EchoResponse $message): string
323
    {
324
        return $message->getPayload();
325
    }
326
327
    /**
328
     * @param LightCommands\SetColor $message
329
     * @return string
330
     */
331
    private function encodeSetColor(LightCommands\SetColor $message): string
332
    {
333
        $transition = $message->getColorTransition();
334
335
        return "\x00" . $this->encodeHsbkColor($transition->getColor()) . \pack('V', $transition->getDuration());
336
    }
337
338
    /**
339
     * @param LightCommands\SetInfrared $message
340
     * @return string
341
     */
342
    private function encodeSetInfrared(LightCommands\SetInfrared $message): string
343
    {
344
        return \pack('v', $message->getBrightness());
345
    }
346
347
    /**
348
     * @param LightResponses\StateInfrared $message
349
     * @return string
350
     */
351
    private function encodeStateInfrared(LightResponses\StateInfrared $message): string
352
    {
353
        return \pack('v', $message->getBrightness());
354
    }
355
356
    /**
357
     * @param LightCommands\SetPower $message
358
     * @return string
359
     */
360
    private function encodeSetLightPower(LightCommands\SetPower $message): string
361
    {
362
        $transition = $message->getPowerTransition();
363
364
        return \pack('vV', $transition->getLevel(), $transition->getDuration());
365
    }
366
367
    /**
368
     * @param LightResponses\StatePower $message
369
     * @return string
370
     */
371
    private function encodeStateLightPower(LightResponses\StatePower $message): string
372
    {
373
        return \pack('v', $message->getLevel());
374
    }
375
376
    /**
377
     * @param LightResponses\State $message
378
     * @return string
379
     */
380
    private function encodeState(LightResponses\State $message): string
381
    {
382
        $state = $message->getState();
383
384
        return $this->encodeHsbkColor($state->getColor()) . \pack(
385
            'v2a32P',
386
            0, // reserved
387
            $state->getPower(),
388
            $state->getLabel()->getValue(),
389
            0  // reserved
390
        );
391
    }
392
393
    /**
394
     * @param LightCommands\SetWaveform $message
395
     * @return string
396
     */
397
    private function encodeSetWaveform(LightCommands\SetWaveform $message): string
398
    {
399
        $effect = $message->getEffect();
400
        $skew = $this->signedShortToUnsignedShort($effect->getSkewRatio());
401
402
        return "\x00" . \chr((int)$effect->isTransient())
403
            . $this->encodeHsbkColor($effect->getColor())
404
            . \pack('V' . FLOAT32_CODE . 'vC', $effect->getPeriod(), $effect->getCycles(), $skew, $effect->getWaveform())
405
        ;
406
    }
407
408
    /**
409
     * @param LightCommands\SetWaveformOptional $message
410
     * @return string
411
     */
412
    private function encodeSetWaveformOptional(LightCommands\SetWaveformOptional $message): string
413
    {
414
        $effect = $message->getEffect();
415
        $skew = $this->signedShortToUnsignedShort($effect->getSkewRatio());
416
417
        $options = $effect->getOptions();
418
        $optionData = \pack(
419
            'C4',
420
            (int)(bool)($options & LightDataTypes\Effect::SET_HUE),
421
            (int)(bool)($options & LightDataTypes\Effect::SET_SATURATION),
422
            (int)(bool)($options & LightDataTypes\Effect::SET_BRIGHTNESS),
423
            (int)(bool)($options & LightDataTypes\Effect::SET_TEMPERATURE)
424
        );
425
426
        return "\x00" . \chr((int)$effect->isTransient())
427
            . $this->encodeHsbkColor($effect->getColor())
428
            . \pack('V' . FLOAT32_CODE . 'vC', $effect->getPeriod(), $effect->getCycles(), $skew, $effect->getWaveform())
429
            . $optionData
430
        ;
431
    }
432
433
    public function encodeMessage(Message $message): string
434
    {
435
        return \array_key_exists($class = \get_class($message), self::ENCODING_ROUTINES)
436
            ? $this->{'encode' . self::ENCODING_ROUTINES[$class]}($message)
437
            : '';
438
    }
439
}
440