Completed
Push — master ( 23013c...52630b )
by Chris
04:20
created

MessageDecoder::unsignedShortToSignedShort()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
ccs 0
cts 6
cp 0
crap 6
rs 9.4285
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A MessageDecoder::decodeAcknowledgement() 0 3 1
1
<?php declare(strict_types=1);
2
3
namespace DaveRandom\LibLifxLan\Decoding;
4
5
use DaveRandom\LibLifxLan\DataTypes as DeviceDataTypes;
6
use DaveRandom\LibLifxLan\DataTypes\Light as LightDataTypes;
7
use DaveRandom\LibLifxLan\Decoding\Exceptions\DecodingException;
8
use DaveRandom\LibLifxLan\Decoding\Exceptions\InvalidMessagePayloadLengthException;
9
use DaveRandom\LibLifxLan\Decoding\Exceptions\MalformedMessagePayloadException;
10
use DaveRandom\LibLifxLan\Exceptions\InvalidValueException;
11
use DaveRandom\LibLifxLan\Messages\Device\Commands as DeviceCommands;
12
use DaveRandom\LibLifxLan\Messages\Device\Requests as DeviceRequests;
13
use DaveRandom\LibLifxLan\Messages\Device\Responses as DeviceResponses;
14
use DaveRandom\LibLifxLan\Messages\Light\Commands as LightCommmands;
15
use DaveRandom\LibLifxLan\Messages\Light\Requests as LightRequests;
16
use DaveRandom\LibLifxLan\Messages\Light\Responses as LightResponses;
17
use DaveRandom\LibLifxLan\Messages\Message;
18
use DaveRandom\LibLifxLan\Messages\UnknownMessage;
19
use function DaveRandom\LibLifxLan\nanotime_to_datetimeimmutable;
1 ignored issue
show
introduced by
The function DaveRandom\LibLifxLan\na...me_to_datetimeimmutable was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
20
use function DaveRandom\LibLifxLan\uint16_to_int16;
1 ignored issue
show
introduced by
The function DaveRandom\LibLifxLan\uint16_to_int16 was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
21
use Ramsey\Uuid\UuidFactory;
22
use Ramsey\Uuid\UuidFactoryInterface;
23
24
final class MessageDecoder
25
{
26
    private const HSBK_FORMAT = 'vhue/vsaturation/vbrightness/vtemperature';
27
28
    /**
29
     * @uses decodeAcknowledgement
30
     * @uses decodeEchoRequest
31
     * @uses decodeEchoResponse
32
     * @uses decodeGetGroup
33
     * @uses decodeSetGroup
34
     * @uses decodeStateGroup
35
     * @uses decodeGetHostFirmware
36
     * @uses decodeStateHostFirmware
37
     * @uses decodeGetHostInfo
38
     * @uses decodeStateHostInfo
39
     * @uses decodeGetInfo
40
     * @uses decodeStateInfo
41
     * @uses decodeGetLabel
42
     * @uses decodeSetLabel
43
     * @uses decodeStateLabel
44
     * @uses decodeGetLocation
45
     * @uses decodeSetLocation
46
     * @uses decodeStateLocation
47
     * @uses decodeGetDevicePower
48
     * @uses decodeSetDevicePower
49
     * @uses decodeStateDevicePower
50
     * @uses decodeGetService
51
     * @uses decodeStateService
52
     * @uses decodeGetVersion
53
     * @uses decodeStateVersion
54
     * @uses decodeGetWifiFirmware
55
     * @uses decodeStateWifiFirmware
56
     * @uses decodeGetWifiInfo
57
     * @uses decodeStateWifiInfo
58
     * @uses decodeGet
59
     * @uses decodeSetColor
60
     * @uses decodeSetWaveform
61
     * @uses decodeSetWaveformOptional
62
     * @uses decodeState
63
     * @uses decodeGetInfrared
64
     * @uses decodeSetInfrared
65
     * @uses decodeStateInfrared
66
     * @uses decodeGetLightPower
67
     * @uses decodeSetLightPower
68
     * @uses decodeStateLightPower
69
     */
70
    private const MESSAGE_INFO = [
71
        // Device command messages
72
        DeviceCommands\SetGroup::MESSAGE_TYPE_ID => [DeviceCommands\SetGroup::WIRE_SIZE, 'SetGroup'],
73
        DeviceCommands\SetLabel::MESSAGE_TYPE_ID => [DeviceCommands\SetLabel::WIRE_SIZE, 'SetLabel'],
74
        DeviceCommands\SetLocation::MESSAGE_TYPE_ID => [DeviceCommands\SetLocation::WIRE_SIZE, 'SetLocation'],
75
        DeviceCommands\SetPower::MESSAGE_TYPE_ID => [DeviceCommands\SetPower::WIRE_SIZE, 'SetDevicePower'],
76
77
        // Device request messages
78
        DeviceRequests\EchoRequest::MESSAGE_TYPE_ID => [DeviceRequests\EchoRequest::WIRE_SIZE, 'EchoRequest'],
79
        DeviceRequests\GetGroup::MESSAGE_TYPE_ID => [DeviceRequests\GetGroup::WIRE_SIZE, 'GetGroup'],
80
        DeviceRequests\GetHostFirmware::MESSAGE_TYPE_ID => [DeviceRequests\GetHostFirmware::WIRE_SIZE, 'GetHostFirmware'],
81
        DeviceRequests\GetHostInfo::MESSAGE_TYPE_ID => [DeviceRequests\GetHostInfo::WIRE_SIZE, 'GetHostInfo'],
82
        DeviceRequests\GetInfo::MESSAGE_TYPE_ID => [DeviceRequests\GetInfo::WIRE_SIZE, 'GetInfo'],
83
        DeviceRequests\GetLabel::MESSAGE_TYPE_ID => [DeviceRequests\GetLabel::WIRE_SIZE, 'GetLabel'],
84
        DeviceRequests\GetLocation::MESSAGE_TYPE_ID => [DeviceRequests\GetLocation::WIRE_SIZE, 'GetLocation'],
85
        DeviceRequests\GetPower::MESSAGE_TYPE_ID => [DeviceRequests\GetPower::WIRE_SIZE, 'GetDevicePower'],
86
        DeviceRequests\GetService::MESSAGE_TYPE_ID => [DeviceRequests\GetService::WIRE_SIZE, 'GetService'],
87
        DeviceRequests\GetVersion::MESSAGE_TYPE_ID => [DeviceRequests\GetVersion::WIRE_SIZE, 'GetVersion'],
88
        DeviceRequests\GetWifiFirmware::MESSAGE_TYPE_ID => [DeviceRequests\GetWifiFirmware::WIRE_SIZE, 'GetWifiFirmware'],
89
        DeviceRequests\GetWifiInfo::MESSAGE_TYPE_ID => [DeviceRequests\GetWifiInfo::WIRE_SIZE, 'GetWifiInfo'],
90
91
        // Device response messages
92
        DeviceResponses\Acknowledgement::MESSAGE_TYPE_ID => [DeviceResponses\Acknowledgement::WIRE_SIZE, 'Acknowledgement'],
93
        DeviceResponses\EchoResponse::MESSAGE_TYPE_ID => [DeviceResponses\EchoResponse::WIRE_SIZE, 'EchoResponse'],
94
        DeviceResponses\StateGroup::MESSAGE_TYPE_ID => [DeviceResponses\StateGroup::WIRE_SIZE, 'StateGroup'],
95
        DeviceResponses\StateHostFirmware::MESSAGE_TYPE_ID => [DeviceResponses\StateHostFirmware::WIRE_SIZE, 'StateHostFirmware'],
96
        DeviceResponses\StateHostInfo::MESSAGE_TYPE_ID => [DeviceResponses\StateHostInfo::WIRE_SIZE, 'StateHostInfo'],
97
        DeviceResponses\StateInfo::MESSAGE_TYPE_ID => [DeviceResponses\StateInfo::WIRE_SIZE, 'StateInfo'],
98
        DeviceResponses\StateLabel::MESSAGE_TYPE_ID => [DeviceResponses\StateLabel::WIRE_SIZE, 'StateLabel'],
99
        DeviceResponses\StateLocation::MESSAGE_TYPE_ID => [DeviceResponses\StateLocation::WIRE_SIZE, 'StateLocation'],
100
        DeviceResponses\StatePower::MESSAGE_TYPE_ID => [DeviceResponses\StatePower::WIRE_SIZE, 'StateDevicePower'],
101
        DeviceResponses\StateService::MESSAGE_TYPE_ID => [DeviceResponses\StateService::WIRE_SIZE, 'StateService'],
102
        DeviceResponses\StateVersion::MESSAGE_TYPE_ID => [DeviceResponses\StateVersion::WIRE_SIZE, 'StateVersion'],
103
        DeviceResponses\StateWifiFirmware::MESSAGE_TYPE_ID => [DeviceResponses\StateWifiFirmware::WIRE_SIZE, 'StateWifiFirmware'],
104
        DeviceResponses\StateWifiInfo::MESSAGE_TYPE_ID => [DeviceResponses\StateWifiInfo::WIRE_SIZE, 'StateWifiInfo'],
105
106
        // Light command messages
107
        LightCommmands\SetColor::MESSAGE_TYPE_ID => [LightCommmands\SetColor::WIRE_SIZE, 'SetColor'],
108
        LightCommmands\SetInfrared::MESSAGE_TYPE_ID => [LightCommmands\SetInfrared::WIRE_SIZE, 'SetInfrared'],
109
        LightCommmands\SetPower::MESSAGE_TYPE_ID => [LightCommmands\SetPower::WIRE_SIZE, 'SetLightPower'],
110
        LightCommmands\SetWaveform::MESSAGE_TYPE_ID => [LightCommmands\SetWaveform::WIRE_SIZE, 'SetWaveform'],
111
        LightCommmands\SetWaveformOptional::MESSAGE_TYPE_ID => [LightCommmands\SetWaveformOptional::WIRE_SIZE, 'SetWaveformOptional'],
112
113
        // Light request messages
114
        LightRequests\Get::MESSAGE_TYPE_ID => [LightRequests\Get::WIRE_SIZE, 'Get'],
115
        LightRequests\GetInfrared::MESSAGE_TYPE_ID => [LightRequests\GetInfrared::WIRE_SIZE, 'GetInfrared'],
116
        LightRequests\GetPower::MESSAGE_TYPE_ID => [LightRequests\GetPower::WIRE_SIZE, 'GetLightPower'],
117
118
        // Light response messages
119
        LightResponses\State::MESSAGE_TYPE_ID => [LightResponses\State::WIRE_SIZE, 'State'],
120
        LightResponses\StateInfrared::MESSAGE_TYPE_ID => [LightResponses\StateInfrared::WIRE_SIZE, 'StateInfrared'],
121
        LightResponses\StatePower::MESSAGE_TYPE_ID => [LightResponses\StatePower::WIRE_SIZE, 'StateLightPower'],
122
    ];
123
124
    private $uuidFactory;
125
126
    private function decodeAcknowledgement(): DeviceResponses\Acknowledgement
127
    {
128
        return new DeviceResponses\Acknowledgement();
129
    }
130
131
    private function decodeEchoRequest(string $data, int $offset): DeviceRequests\EchoRequest
132
    {
133
        return new DeviceRequests\EchoRequest(\substr($data, $offset));
134
    }
135
136
    private function decodeEchoResponse(string $data, int $offset): DeviceResponses\EchoResponse
137
    {
138
        return new DeviceResponses\EchoResponse(\substr($data, $offset));
139
    }
140
141
    private function decodeGetGroup(): DeviceRequests\GetGroup
142
    {
143
        return new DeviceRequests\GetGroup;
144
    }
145
146
    /**
147
     * @param string $data
148
     * @param int $offset
149
     * @return DeviceCommands\SetGroup
150
     * @throws InvalidValueException
151
     */
152
    private function decodeSetGroup(string $data, int $offset): DeviceCommands\SetGroup
153
    {
154
        [
155
            'guid'    => $guid,
156
            'label'   => $label,
157
            'updated' => $updatedAt,
158
        ] = \unpack('a16guid/a32label/Pupdated', $data, $offset);
159
160
        $guid = $this->uuidFactory->fromBytes($guid);
161
        $updatedAt = nanotime_to_datetimeimmutable($updatedAt);
162
        $label = new DeviceDataTypes\Label(\rtrim($label, "\x00"));
163
164
        return new DeviceCommands\SetGroup(new DeviceDataTypes\Group($guid, $label, $updatedAt));
165
    }
166
167
    /**
168
     * @param string $data
169
     * @param int $offset
170
     * @return DeviceResponses\StateGroup
171
     * @throws InvalidValueException
172
     */
173
    private function decodeStateGroup(string $data, int $offset): DeviceResponses\StateGroup
174
    {
175
        [
176
            'guid'    => $guid,
177
            'label'   => $label,
178
            'updated' => $updatedAt,
179
        ] = \unpack('a16guid/a32label/Pupdated', $data, $offset);
180
181
        $guid = $this->uuidFactory->fromBytes($guid);
182
        $updatedAt = nanotime_to_datetimeimmutable($updatedAt);
183
        $label = new DeviceDataTypes\Label(\rtrim($label, "\x00"));
184
185
        return new DeviceResponses\StateGroup(new DeviceDataTypes\Group($guid, $label, $updatedAt));
186
    }
187
188
    private function decodeGetHostFirmware(): DeviceRequests\GetHostFirmware
189
    {
190
        return new DeviceRequests\GetHostFirmware;
191
    }
192
193
    /**
194
     * @param string $data
195
     * @param int $offset
196
     * @return DeviceResponses\StateHostFirmware
197
     * @throws InvalidValueException
198
     */
199
    private function decodeStateHostFirmware(string $data, int $offset): DeviceResponses\StateHostFirmware
200
    {
201
        [
202
            'build'    => $build,
203
            'version'  => $version,
204
        ] = \unpack('Pbuild/Preserved/Vversion', $data, $offset);
205
206
        $build = nanotime_to_datetimeimmutable($build);
207
208
        return new DeviceResponses\StateHostFirmware(new DeviceDataTypes\HostFirmware($build, $version));
209
    }
210
211
    private function decodeGetHostInfo(): DeviceRequests\GetHostInfo
212
    {
213
        return new DeviceRequests\GetHostInfo;
214
    }
215
216
    /**
217
     * @param string $data
218
     * @param int $offset
219
     * @return DeviceResponses\StateHostInfo
220
     */
221
    private function decodeStateHostInfo(string $data, int $offset): DeviceResponses\StateHostInfo
222
    {
223
        [
224
            'signal'   => $signal,
225
            'tx'       => $tx,
226
            'rx'       => $rx,
227
        ] = \unpack(\DaveRandom\LibLifxLan\FLOAT32_CODE . 'signal/Vtx/Vrx/vreserved', $data, $offset);
228
229
        return new DeviceResponses\StateHostInfo(new DeviceDataTypes\HostInfo($signal, $tx, $rx));
230
    }
231
232
    private function decodeGetInfo(): DeviceRequests\GetInfo
233
    {
234
        return new DeviceRequests\GetInfo;
235
    }
236
237
    /**
238
     * @param string $data
239
     * @param int $offset
240
     * @return DeviceResponses\StateInfo
241
     * @throws InvalidValueException
242
     */
243
    private function decodeStateInfo(string $data, int $offset): DeviceResponses\StateInfo
244
    {
245
        [
246
            'time'     => $time,
247
            'uptime'   => $uptime,
248
            'downtime' => $downtime,
249
        ] = \unpack('Ptime/Puptime/Pdowntime', $data, $offset);
250
251
        $time = nanotime_to_datetimeimmutable($time);
252
253
        return new DeviceResponses\StateInfo(new DeviceDataTypes\TimeInfo($time, $uptime, $downtime));
254
    }
255
256
    private function decodeGetLabel(): DeviceRequests\GetLabel
257
    {
258
        return new DeviceRequests\GetLabel;
259
    }
260
261
    private function decodeSetLabel(string $data, int $offset): DeviceCommands\SetLabel
262
    {
263
        return new DeviceCommands\SetLabel(new DeviceDataTypes\Label(\rtrim(\substr($data, $offset), "\x00")));
264
    }
265
266
    private function decodeStateLabel(string $data, int $offset): DeviceResponses\StateLabel
267
    {
268
        return new DeviceResponses\StateLabel(new DeviceDataTypes\Label(\rtrim(\substr($data, $offset), "\x00")));
269
    }
270
271
    private function decodeGetLocation(): DeviceRequests\GetLocation
272
    {
273
        return new DeviceRequests\GetLocation;
274
    }
275
276
    /**
277
     * @param string $data
278
     * @param int $offset
279
     * @return DeviceCommands\SetLocation
280
     * @throws InvalidValueException
281
     */
282
    private function decodeSetLocation(string $data, int $offset): DeviceCommands\SetLocation
283
    {
284
        [
285
            'guid'    => $guid,
286
            'label'   => $label,
287
            'updated' => $updatedAt,
288
        ] = \unpack('a16guid/a32label/Pupdated', $data, $offset);
289
290
        $guid = $this->uuidFactory->fromBytes($guid);
291
        $updatedAt = nanotime_to_datetimeimmutable($updatedAt);
292
        $label = new DeviceDataTypes\Label(\rtrim($label, "\x00"));
293
294
        return new DeviceCommands\SetLocation(new DeviceDataTypes\Location($guid, $label, $updatedAt));
295
    }
296
297
    /**
298
     * @param string $data
299
     * @param int $offset
300
     * @return DeviceResponses\StateLocation
301
     * @throws InvalidValueException
302
     */
303
    private function decodeStateLocation(string $data, int $offset): DeviceResponses\StateLocation
304
    {
305
        [
306
            'guid'    => $guid,
307
            'label'   => $label,
308
            'updated' => $updatedAt,
309
        ] = \unpack('a16guid/a32label/Pupdated', $data, $offset);
310
311
        $guid = $this->uuidFactory->fromBytes($guid);
312
        $updatedAt = nanotime_to_datetimeimmutable($updatedAt);
313
        $label = new DeviceDataTypes\Label(\rtrim($label, "\x00"));
314
315
        return new DeviceResponses\StateLocation(new DeviceDataTypes\Location($guid, $label, $updatedAt));
316
    }
317
318
    private function decodeGetDevicePower(): DeviceRequests\GetPower
319
    {
320
        return new DeviceRequests\GetPower;
321
    }
322
323
    private function decodeSetDevicePower(string $data, int $offset): DeviceCommands\SetPower
324
    {
325
        $level = \unpack('vlevel', $data, $offset)['level'];
326
327
        return new DeviceCommands\SetPower($level);
328
    }
329
330
    private function decodeStateDevicePower(string $data, int $offset): DeviceResponses\StatePower
331
    {
332
        $level = \unpack('vlevel', $data, $offset)['level'];
333
334
        return new DeviceResponses\StatePower($level);
335
    }
336
337
    private function decodeGetService(): DeviceRequests\GetService
338
    {
339
        return new DeviceRequests\GetService;
340
    }
341
342
    private function decodeStateService(string $data, int $offset): DeviceResponses\StateService
343
    {
344
        [
345
            'serviceType' => $serviceType,
346
            'port' => $port,
347
        ] = \unpack('CserviceType/Vport', $data, $offset);
348
349
        return new DeviceResponses\StateService(new DeviceDataTypes\Service($serviceType, $port));
350
    }
351
352
    private function decodeGetVersion(): DeviceRequests\GetVersion
353
    {
354
        return new DeviceRequests\GetVersion;
355
    }
356
357
    private function decodeStateVersion(string $data, int $offset): DeviceResponses\StateVersion
358
    {
359
        [
360
            'vendor'  => $vendor,
361
            'product' => $product,
362
            'version' => $version,
363
        ] = \unpack('Vvendor/Vproduct/Vversion', $data, $offset);
364
365
        return new DeviceResponses\StateVersion(new DeviceDataTypes\Version($vendor, $product, $version));
366
    }
367
368
    private function decodeGetWifiFirmware(): DeviceRequests\GetWifiFirmware
369
    {
370
        return new DeviceRequests\GetWifiFirmware;
371
    }
372
373
    /**
374
     * @param string $data
375
     * @param int $offset
376
     * @return DeviceResponses\StateWifiFirmware
377
     * @throws InvalidValueException
378
     */
379
    private function decodeStateWifiFirmware(string $data, int $offset): DeviceResponses\StateWifiFirmware
380
    {
381
        [
382
            'build'    => $build,
383
            'version'  => $version,
384
        ] = \unpack('Pbuild/Preserved/Vversion', $data, $offset);
385
386
        $build = nanotime_to_datetimeimmutable($build);
387
388
        return new DeviceResponses\StateWifiFirmware(new DeviceDataTypes\WifiFirmware($build, $version));
389
    }
390
391
    private function decodeGetWifiInfo(): DeviceRequests\GetWifiInfo
392
    {
393
        return new DeviceRequests\GetWifiInfo;
394
    }
395
396
    private function decodeStateWifiInfo(string $data, int $offset): DeviceResponses\StateWifiInfo
397
    {
398
399
        [
400
            'signal'   => $signal,
401
            'tx'       => $tx,
402
            'rx'       => $rx,
403
        ] = \unpack(\DaveRandom\LibLifxLan\FLOAT32_CODE . 'signal/Vtx/Vrx/vreserved', $data, $offset);
404
405
        return new DeviceResponses\StateWifiInfo(new DeviceDataTypes\WifiInfo($signal, $tx, $rx));
406
    }
407
408
    private function decodeGet(): LightRequests\Get
409
    {
410
        return new LightRequests\Get;
411
    }
412
413
    private function decodeSetColor(string $data, int $offset): LightCommmands\SetColor
414
    {
415
        [
416
            'hue'         => $hue,
417
            'saturation'  => $saturation,
418
            'brightness'  => $brightness,
419
            'temperature' => $temperature,
420
            'duration'    => $duration,
421
        ] = \unpack('Creserved/' . self::HSBK_FORMAT . '/Vduration', $data, $offset);
422
423
        $color = new LightDataTypes\HsbkColor($hue, $saturation, $brightness, $temperature);
424
425
        return new LightCommmands\SetColor(new LightDataTypes\ColorTransition($color, $duration));
426
    }
427
428
    private function decodeSetWaveform(string $data, int $offset): LightCommmands\SetWaveform
429
    {
430
        $format
431
            = 'Creserved/Ctransient/'
432
            . self::HSBK_FORMAT
433
            . '/Vperiod/' . \DaveRandom\LibLifxLan\FLOAT32_CODE . 'cycles/vskewRatio/Cwaveform'
434
        ;
435
436
        [
437
            'transient'   => $transient,
438
            'hue'         => $hue,
439
            'saturation'  => $saturation,
440
            'brightness'  => $brightness,
441
            'temperature' => $temperature,
442
            'period'      => $period,
443
            'cycles'      => $cycles,
444
            'skewRatio'   => $skewRatio,
445
            'waveform'    => $waveform,
446
        ] = \unpack($format, $data, $offset);
447
448
        $color = new LightDataTypes\HsbkColor($hue, $saturation, $brightness, $temperature);
449
450
        $skewRatio = uint16_to_int16($skewRatio);
451
        $effect = new LightDataTypes\Effect((bool)$transient, $color, $period, $cycles, $skewRatio, $waveform);
452
453
        return new LightCommmands\SetWaveform($effect);
454
    }
455
456
    private function decodeSetWaveformOptional(string $data, int $offset): LightCommmands\SetWaveformOptional
457
    {
458
        $format
459
            = 'Creserved/Ctransient/'
460
            . self::HSBK_FORMAT
461
            . '/Vperiod/' . \DaveRandom\LibLifxLan\FLOAT32_CODE . 'cycles/vskewRatio/Cwaveform'
462
            . '/CsetHue/CsetSaturation/CsetBrightness/CsetTemperature'
463
        ;
464
465
        [
466
            'transient'      => $transient,
467
            'hue'            => $hue,
468
            'saturation'     => $saturation,
469
            'brightness'     => $brightness,
470
            'temperature'    => $temperature,
471
            'period'         => $period,
472
            'cycles'         => $cycles,
473
            'skewRatio'      => $skewRatio,
474
            'waveform'       => $waveform,
475
            'setHue'         => $setHue,
476
            'setSaturation'  => $setSaturation,
477
            'setBrightness'  => $setBrightness,
478
            'setTemperature' => $setTemperature,
479
        ] = \unpack($format, $data, $offset);
480
481
        $color = new LightDataTypes\HsbkColor($hue, $saturation, $brightness, $temperature);
482
        $skewRatio = uint16_to_int16($skewRatio);
483
484
        $options = ($setHue ? LightDataTypes\Effect::SET_HUE : 0)
485
            | ($setSaturation ? LightDataTypes\Effect::SET_SATURATION : 0)
486
            | ($setBrightness ? LightDataTypes\Effect::SET_BRIGHTNESS : 0)
487
            | ($setTemperature ? LightDataTypes\Effect::SET_TEMPERATURE : 0);
488
489
        $effect = new LightDataTypes\Effect((bool)$transient, $color, $period, $cycles, $skewRatio, $waveform, $options);
490
491
        return new LightCommmands\SetWaveformOptional($effect);
492
    }
493
494
    private function decodeState(string $data, int $offset): LightResponses\State
495
    {
496
        [
497
            'hue'         => $hue,
498
            'saturation'  => $saturation,
499
            'brightness'  => $brightness,
500
            'temperature' => $temperature,
501
            'power'       => $power,
502
            'label'       => $label,
503
        ] = \unpack(self::HSBK_FORMAT . '/vreserved/vpower/a32label/Preserved', $data, $offset);
504
505
        $color = new LightDataTypes\HsbkColor($hue, $saturation, $brightness, $temperature);
506
        $label = new DeviceDataTypes\Label(\rtrim($label, "\x00"));
507
508
        return new LightResponses\State(new LightDataTypes\State($color, $power, $label));
509
    }
510
511
    private function decodeGetInfrared(): LightRequests\GetInfrared
512
    {
513
        return new LightRequests\GetInfrared;
514
    }
515
516
    private function decodeSetInfrared(string $data, int $offset): LightCommmands\SetInfrared
517
    {
518
        $level = \unpack('vlevel', $data, $offset)['level'];
519
520
        return new LightCommmands\SetInfrared($level);
521
    }
522
523
    private function decodeStateInfrared(string $data, int $offset): LightResponses\StateInfrared
524
    {
525
        $level = \unpack('vlevel', $data, $offset)['level'];
526
527
        return new LightResponses\StateInfrared($level);
528
    }
529
530
    private function decodeGetLightPower(): LightRequests\GetPower
531
    {
532
        return new LightRequests\GetPower;
533
    }
534
535
    private function decodeSetLightPower(string $data, int $offset): LightCommmands\SetPower
536
    {
537
        [
538
            'level'    => $level,
539
            'duration' => $duration,
540
        ] = \unpack('vlevel/Vduration', $data, $offset);
541
542
        return new LightCommmands\SetPower(new LightDataTypes\PowerTransition($level, $duration));
543
    }
544
545
    private function decodeStateLightPower(string $data, int $offset): LightResponses\StatePower
546
    {
547
        $level = \unpack('vlevel', $data, $offset)['level'];
548
549
        return new LightResponses\StatePower($level);
550
    }
551
552
    public function __construct(UuidFactoryInterface $uuidFactory = null)
553
    {
554
        $this->uuidFactory = $uuidFactory ?? new UuidFactory;
555
    }
556
557
    /**
558
     * @param int $type
559
     * @param string $data
560
     * @param int $offset
561
     * @param int|null $dataLength
562
     * @return Message
563
     * @throws DecodingException
564
     */
565
    public function decodeMessage(int $type, string $data, int $offset = 0, int $dataLength = null): Message
566
    {
567
        $dataLength = $dataLength ?? (\strlen($data) - $offset);
568
569
        if (!\array_key_exists($type, self::MESSAGE_INFO)) {
570
            return new UnknownMessage($type, \substr($data, $offset, $dataLength));
571
        }
572
573
        [$expectedLength, $messageName] = self::MESSAGE_INFO[$type];
574
575
        if ($dataLength !== $expectedLength) {
576
            throw new InvalidMessagePayloadLengthException(
577
                "Invalid payload length for {$messageName} message,"
578
                . " expecting {$expectedLength} bytes, got {$dataLength} bytes"
579
            );
580
        }
581
582
        try {
583
            return ([$this, 'decode' . $messageName])($data, $offset);
584
        } /** @noinspection PhpRedundantCatchClauseInspection */ catch (InvalidValueException $e) {
585
            throw new MalformedMessagePayloadException($e->getMessage(), $e->getCode(), $e);
586
        }
587
    }
588
}
589