Completed
Push — master ( 8c1d4f...b44e39 )
by Chris
02:46 queued 12s
created

MessageDecoder   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 512
Duplicated Lines 0 %

Test Coverage

Coverage 8.06%

Importance

Changes 0
Metric Value
dl 0
loc 512
ccs 17
cts 211
cp 0.0806
rs 8.4864
c 0
b 0
f 0
wmc 48

42 Methods

Rating   Name   Duplication   Size   Complexity  
A decodeGetLabel() 0 3 1
A decodeEchoResponse() 0 3 1
A decodeGetGroup() 0 3 1
A decodeGet() 0 3 1
A decodeSetLightPower() 0 8 1
A decodeGetWifiInfo() 0 3 1
A decodeSetLabel() 0 3 1
A decodeStateHostInfo() 0 9 1
A decodeStateInfrared() 0 5 1
A decodeAcknowledgement() 0 3 1
A decodeStateGroup() 0 13 1
A decodeGetLocation() 0 3 1
A decodeGetLightPower() 0 3 1
A decodeStateVersion() 0 9 1
A decodeStateHostFirmware() 0 10 1
A decodeStateDevicePower() 0 5 1
A decodeGetDevicePower() 0 3 1
A decodeSetDevicePower() 0 5 1
B decodeSetWaveformOptional() 0 36 5
A decodeGetService() 0 3 1
A decodeStateLightPower() 0 5 1
A decodeStateService() 0 8 1
A decodeGetInfrared() 0 3 1
A decodeStateLabel() 0 3 1
A decodeState() 0 15 1
A decodeGetInfo() 0 3 1
A decodeStateInfo() 0 11 1
A decodeSetGroup() 0 13 1
B decodeSetWaveform() 0 26 1
A decodeGetHostFirmware() 0 3 1
A __construct() 0 3 1
A decodeSetColor() 0 13 1
A decodeMessage() 0 18 3
A decodeGetHostInfo() 0 3 1
A decodeSetLocation() 0 13 1
A decodeSetInfrared() 0 5 1
A decodeGetWifiFirmware() 0 3 1
A decodeStateWifiFirmware() 0 10 1
A decodeEchoRequest() 0 3 1
A decodeStateWifiInfo() 0 10 1
A decodeGetVersion() 0 3 1
A decodeStateLocation() 0 13 1

How to fix   Complexity   

Complex Class

Complex classes like MessageDecoder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MessageDecoder, and based on these observations, apply Extract Interface, too.

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