0
# Device Shadow Operations
1
2
Complete AWS IoT Device Shadow functionality including get, update, delete operations for both classic and named shadows, with support for shadow delta events and real-time shadow document updates through both V1 (callback-based) and V2 (Future-based) client interfaces.
3
4
## Capabilities
5
6
### V1 Shadow Client (Callback-Based)
7
8
The V1 client provides callback-based operations following the traditional publish/subscribe pattern.
9
10
#### Client Creation
11
12
```python { .api }
13
class IotShadowClient(MqttServiceClient):
14
"""
15
V1 client for AWS IoT Device Shadow service with callback-based operations.
16
17
Parameters:
18
- mqtt_connection: MQTT connection (mqtt.Connection or mqtt5.Client)
19
"""
20
def __init__(self, mqtt_connection): ...
21
```
22
23
Usage example:
24
25
```python
26
from awsiot import mqtt_connection_builder, iotshadow
27
28
# Create MQTT connection
29
connection = mqtt_connection_builder.mtls_from_path(
30
endpoint="your-endpoint.iot.us-east-1.amazonaws.com",
31
cert_filepath="/path/to/certificate.pem.crt",
32
pri_key_filepath="/path/to/private.pem.key",
33
client_id="shadow-client-123"
34
)
35
36
# Create shadow client
37
shadow_client = iotshadow.IotShadowClient(connection)
38
```
39
40
#### Classic Shadow Operations
41
42
##### Get Shadow
43
44
```python { .api }
45
def publish_get_shadow(self, request, qos):
46
"""
47
Publish request to get device shadow.
48
49
Parameters:
50
- request (GetShadowRequest): Shadow request
51
- qos (awscrt.mqtt.QoS): Quality of service
52
53
Returns:
54
Future: Future that completes when request is published
55
"""
56
57
def subscribe_to_get_shadow_accepted(self, request, qos, callback):
58
"""
59
Subscribe to successful get shadow responses.
60
61
Parameters:
62
- request (GetShadowSubscriptionRequest): Subscription request
63
- qos (awscrt.mqtt.QoS): Quality of service
64
- callback: Function called when shadow is retrieved
65
66
Returns:
67
Tuple[Future, str]: Future for subscription, topic string
68
"""
69
70
def subscribe_to_get_shadow_rejected(self, request, qos, callback):
71
"""
72
Subscribe to rejected get shadow responses.
73
74
Parameters:
75
- request (GetShadowSubscriptionRequest): Subscription request
76
- qos (awscrt.mqtt.QoS): Quality of service
77
- callback: Function called when request is rejected
78
79
Returns:
80
Tuple[Future, str]: Future for subscription, topic string
81
"""
82
```
83
84
##### Update Shadow
85
86
```python { .api }
87
def publish_update_shadow(self, request, qos):
88
"""
89
Publish request to update device shadow.
90
91
Parameters:
92
- request (UpdateShadowRequest): Shadow update request
93
- qos (awscrt.mqtt.QoS): Quality of service
94
95
Returns:
96
Future: Future that completes when request is published
97
"""
98
99
def subscribe_to_update_shadow_accepted(self, request, qos, callback):
100
"""
101
Subscribe to successful update shadow responses.
102
103
Parameters:
104
- request (UpdateShadowSubscriptionRequest): Subscription request
105
- qos (awscrt.mqtt.QoS): Quality of service
106
- callback: Function called when update succeeds
107
108
Returns:
109
Tuple[Future, str]: Future for subscription, topic string
110
"""
111
112
def subscribe_to_update_shadow_rejected(self, request, qos, callback):
113
"""
114
Subscribe to rejected update shadow responses.
115
116
Parameters:
117
- request (UpdateShadowSubscriptionRequest): Subscription request
118
- qos (awscrt.mqtt.QoS): Quality of service
119
- callback: Function called when update is rejected
120
121
Returns:
122
Tuple[Future, str]: Future for subscription, topic string
123
"""
124
```
125
126
##### Delete Shadow
127
128
```python { .api }
129
def publish_delete_shadow(self, request, qos):
130
"""
131
Publish request to delete device shadow.
132
133
Parameters:
134
- request (DeleteShadowRequest): Shadow delete request
135
- qos (awscrt.mqtt.QoS): Quality of service
136
137
Returns:
138
Future: Future that completes when request is published
139
"""
140
141
def subscribe_to_delete_shadow_accepted(self, request, qos, callback):
142
"""
143
Subscribe to successful delete shadow responses.
144
"""
145
146
def subscribe_to_delete_shadow_rejected(self, request, qos, callback):
147
"""
148
Subscribe to rejected delete shadow responses.
149
"""
150
```
151
152
#### Named Shadow Operations
153
154
Named shadows allow multiple shadows per device. All classic shadow operations have named shadow equivalents:
155
156
```python { .api }
157
def publish_get_named_shadow(self, request, qos): ...
158
def publish_update_named_shadow(self, request, qos): ...
159
def publish_delete_named_shadow(self, request, qos): ...
160
161
def subscribe_to_get_named_shadow_accepted(self, request, qos, callback): ...
162
def subscribe_to_get_named_shadow_rejected(self, request, qos, callback): ...
163
def subscribe_to_update_named_shadow_accepted(self, request, qos, callback): ...
164
def subscribe_to_update_named_shadow_rejected(self, request, qos, callback): ...
165
def subscribe_to_delete_named_shadow_accepted(self, request, qos, callback): ...
166
def subscribe_to_delete_named_shadow_rejected(self, request, qos, callback): ...
167
```
168
169
#### Shadow Event Subscriptions
170
171
##### Delta Events
172
173
Subscribe to shadow delta events when desired and reported states differ:
174
175
```python { .api }
176
def subscribe_to_shadow_delta_events(self, request, qos, callback):
177
"""
178
Subscribe to shadow delta events for classic shadow.
179
180
Parameters:
181
- request (ShadowDeltaUpdatedSubscriptionRequest): Subscription request
182
- qos (awscrt.mqtt.QoS): Quality of service
183
- callback: Function called when delta event occurs
184
185
Returns:
186
Tuple[Future, str]: Future for subscription, topic string
187
"""
188
189
def subscribe_to_named_shadow_delta_events(self, request, qos, callback):
190
"""
191
Subscribe to shadow delta events for named shadow.
192
"""
193
```
194
195
##### Document Events
196
197
Subscribe to shadow document update events:
198
199
```python { .api }
200
def subscribe_to_shadow_documents_events(self, request, qos, callback):
201
"""
202
Subscribe to shadow document update events for classic shadow.
203
204
Parameters:
205
- request (ShadowUpdatedSubscriptionRequest): Subscription request
206
- qos (awscrt.mqtt.QoS): Quality of service
207
- callback: Function called when shadow document updates
208
209
Returns:
210
Tuple[Future, str]: Future for subscription, topic string
211
"""
212
213
def subscribe_to_named_shadow_documents_events(self, request, qos, callback):
214
"""
215
Subscribe to shadow document update events for named shadow.
216
"""
217
```
218
219
### V2 Shadow Client (Future-Based)
220
221
The V2 client provides Future-based operations with request-response semantics.
222
223
#### Client Creation
224
225
```python { .api }
226
class IotShadowClientV2:
227
"""
228
V2 client for AWS IoT Device Shadow service with Future-based operations.
229
"""
230
def __init__(self, connection): ...
231
```
232
233
#### Classic Shadow Operations
234
235
```python { .api }
236
def get_shadow(self, request):
237
"""
238
Get device shadow using request-response pattern.
239
240
Parameters:
241
- request (GetShadowRequest): Shadow request
242
243
Returns:
244
Future[GetShadowResponse]: Future containing shadow response
245
"""
246
247
def update_shadow(self, request):
248
"""
249
Update device shadow using request-response pattern.
250
251
Parameters:
252
- request (UpdateShadowRequest): Shadow update request
253
254
Returns:
255
Future[UpdateShadowResponse]: Future containing update response
256
"""
257
258
def delete_shadow(self, request):
259
"""
260
Delete device shadow using request-response pattern.
261
262
Parameters:
263
- request (DeleteShadowRequest): Shadow delete request
264
265
Returns:
266
Future[DeleteShadowResponse]: Future containing delete response
267
"""
268
```
269
270
#### Named Shadow Operations
271
272
```python { .api }
273
def get_named_shadow(self, request):
274
"""Get named shadow using request-response pattern."""
275
276
def update_named_shadow(self, request):
277
"""Update named shadow using request-response pattern."""
278
279
def delete_named_shadow(self, request):
280
"""Delete named shadow using request-response pattern."""
281
```
282
283
#### Streaming Operations
284
285
```python { .api }
286
def create_shadow_delta_stream(self, request, options):
287
"""
288
Create streaming operation for shadow delta events.
289
290
Parameters:
291
- request (ShadowDeltaUpdatedSubscriptionRequest): Stream request
292
- options (ServiceStreamOptions): Stream configuration
293
294
Returns:
295
StreamingOperation: Streaming operation handle
296
"""
297
298
def create_named_shadow_delta_stream(self, request, options):
299
"""Create streaming operation for named shadow delta events."""
300
301
def create_shadow_documents_stream(self, request, options):
302
"""Create streaming operation for shadow document events."""
303
304
def create_named_shadow_documents_stream(self, request, options):
305
"""Create streaming operation for named shadow document events."""
306
```
307
308
### Data Model Classes
309
310
#### Request Classes
311
312
```python { .api }
313
@dataclass
314
class GetShadowRequest:
315
"""Request to get device shadow."""
316
thing_name: str
317
318
@dataclass
319
class GetNamedShadowRequest:
320
"""Request to get named shadow."""
321
thing_name: str
322
shadow_name: str
323
324
@dataclass
325
class UpdateShadowRequest:
326
"""Request to update device shadow."""
327
thing_name: str
328
state: Optional[ShadowState] = None
329
client_token: Optional[str] = None
330
331
@dataclass
332
class UpdateNamedShadowRequest:
333
"""Request to update named shadow."""
334
thing_name: str
335
shadow_name: str
336
state: Optional[ShadowState] = None
337
client_token: Optional[str] = None
338
339
@dataclass
340
class DeleteShadowRequest:
341
"""Request to delete device shadow."""
342
thing_name: str
343
344
@dataclass
345
class DeleteNamedShadowRequest:
346
"""Request to delete named shadow."""
347
thing_name: str
348
shadow_name: str
349
```
350
351
#### Response Classes
352
353
```python { .api }
354
@dataclass
355
class GetShadowResponse:
356
"""Response from get shadow operation."""
357
state: Optional[ShadowStateWithDelta] = None
358
metadata: Optional[ShadowMetadata] = None
359
version: Optional[int] = None
360
timestamp: Optional[datetime.datetime] = None
361
client_token: Optional[str] = None
362
363
@dataclass
364
class UpdateShadowResponse:
365
"""Response from update shadow operation."""
366
state: Optional[ShadowState] = None
367
metadata: Optional[ShadowMetadata] = None
368
version: Optional[int] = None
369
timestamp: Optional[datetime.datetime] = None
370
client_token: Optional[str] = None
371
372
@dataclass
373
class DeleteShadowResponse:
374
"""Response from delete shadow operation."""
375
version: Optional[int] = None
376
timestamp: Optional[datetime.datetime] = None
377
client_token: Optional[str] = None
378
```
379
380
#### Shadow State Classes
381
382
```python { .api }
383
@dataclass
384
class ShadowState:
385
"""Device shadow state containing desired and reported properties."""
386
desired: Optional[Dict[str, Any]] = None
387
reported: Optional[Dict[str, Any]] = None
388
389
@dataclass
390
class ShadowStateWithDelta:
391
"""Shadow state including delta between desired and reported."""
392
desired: Optional[Dict[str, Any]] = None
393
reported: Optional[Dict[str, Any]] = None
394
delta: Optional[Dict[str, Any]] = None
395
396
@dataclass
397
class ShadowMetadata:
398
"""Metadata about shadow properties including timestamps."""
399
desired: Optional[Dict[str, Any]] = None
400
reported: Optional[Dict[str, Any]] = None
401
```
402
403
#### Event Classes
404
405
```python { .api }
406
@dataclass
407
class ShadowDeltaUpdatedEvent:
408
"""Event published when shadow delta changes."""
409
version: Optional[int] = None
410
timestamp: Optional[datetime.datetime] = None
411
state: Optional[Dict[str, Any]] = None
412
metadata: Optional[Dict[str, Any]] = None
413
client_token: Optional[str] = None
414
415
@dataclass
416
class ShadowUpdatedEvent:
417
"""Event published when shadow document updates."""
418
previous: Optional[ShadowUpdatedSnapshot] = None
419
current: Optional[ShadowUpdatedSnapshot] = None
420
timestamp: Optional[datetime.datetime] = None
421
client_token: Optional[str] = None
422
```
423
424
#### Subscription Request Classes
425
426
```python { .api }
427
@dataclass
428
class GetShadowSubscriptionRequest:
429
"""Subscription request for get shadow responses."""
430
thing_name: str
431
432
@dataclass
433
class ShadowDeltaUpdatedSubscriptionRequest:
434
"""Subscription request for shadow delta events."""
435
thing_name: str
436
437
@dataclass
438
class ShadowUpdatedSubscriptionRequest:
439
"""Subscription request for shadow document events."""
440
thing_name: str
441
```
442
443
#### Error Response Classes
444
445
```python { .api }
446
@dataclass
447
class ErrorResponse:
448
"""Error response from shadow operations."""
449
code: Optional[int] = None
450
message: Optional[str] = None
451
timestamp: Optional[datetime.datetime] = None
452
client_token: Optional[str] = None
453
```
454
455
## Usage Examples
456
457
### V1 Client - Basic Shadow Update
458
459
```python
460
from awsiot import mqtt_connection_builder, iotshadow
461
from awscrt import mqtt
462
import json
463
464
# Create connection and client
465
connection = mqtt_connection_builder.mtls_from_path(
466
endpoint="your-endpoint.iot.us-east-1.amazonaws.com",
467
cert_filepath="/path/to/certificate.pem.crt",
468
pri_key_filepath="/path/to/private.pem.key",
469
client_id="shadow-device-123"
470
)
471
472
shadow_client = iotshadow.IotShadowClient(connection)
473
connection.connect().result()
474
475
# Define callbacks
476
def on_update_accepted(response):
477
print(f"Shadow update accepted: {response}")
478
479
def on_update_rejected(error):
480
print(f"Shadow update rejected: {error}")
481
482
# Subscribe to responses
483
shadow_client.subscribe_to_update_shadow_accepted(
484
iotshadow.UpdateShadowSubscriptionRequest(thing_name="MyDevice"),
485
mqtt.QoS.AT_LEAST_ONCE,
486
on_update_accepted
487
).result()
488
489
shadow_client.subscribe_to_update_shadow_rejected(
490
iotshadow.UpdateShadowSubscriptionRequest(thing_name="MyDevice"),
491
mqtt.QoS.AT_LEAST_ONCE,
492
on_update_rejected
493
).result()
494
495
# Update shadow
496
update_request = iotshadow.UpdateShadowRequest(
497
thing_name="MyDevice",
498
state=iotshadow.ShadowState(
499
reported={"temperature": 23.5, "humidity": 60.2, "status": "online"}
500
)
501
)
502
503
shadow_client.publish_update_shadow(update_request, mqtt.QoS.AT_LEAST_ONCE).result()
504
```
505
506
### V1 Client - Shadow Delta Monitoring
507
508
```python
509
def on_shadow_delta(delta_event):
510
"""Handle shadow delta events when desired != reported."""
511
print(f"Shadow delta received: {delta_event}")
512
513
# Extract delta changes
514
if delta_event.state:
515
for property_name, desired_value in delta_event.state.items():
516
print(f"Property '{property_name}' should be updated to: {desired_value}")
517
518
# Update device state and report back
519
# ... device-specific logic here ...
520
521
# Report updated state
522
update_request = iotshadow.UpdateShadowRequest(
523
thing_name="MyDevice",
524
state=iotshadow.ShadowState(
525
reported=delta_event.state # Report desired state as achieved
526
),
527
client_token=delta_event.client_token
528
)
529
shadow_client.publish_update_shadow(update_request, mqtt.QoS.AT_LEAST_ONCE)
530
531
# Subscribe to delta events
532
shadow_client.subscribe_to_shadow_delta_events(
533
iotshadow.ShadowDeltaUpdatedSubscriptionRequest(thing_name="MyDevice"),
534
mqtt.QoS.AT_LEAST_ONCE,
535
on_shadow_delta
536
).result()
537
```
538
539
### V2 Client - Request-Response Pattern
540
541
```python
542
from awsiot import mqtt_connection_builder, iotshadow
543
import asyncio
544
545
async def shadow_operations():
546
# Create connection
547
connection = mqtt_connection_builder.mtls_from_path(
548
endpoint="your-endpoint.iot.us-east-1.amazonaws.com",
549
cert_filepath="/path/to/certificate.pem.crt",
550
pri_key_filepath="/path/to/private.pem.key",
551
client_id="shadow-v2-client"
552
)
553
554
# Create V2 client
555
shadow_client = iotshadow.IotShadowClientV2(connection)
556
await connection.connect()
557
558
try:
559
# Get current shadow
560
get_request = iotshadow.GetShadowRequest(thing_name="MyDevice")
561
get_response = await shadow_client.get_shadow(get_request)
562
print(f"Current shadow: {get_response}")
563
564
# Update shadow
565
update_request = iotshadow.UpdateShadowRequest(
566
thing_name="MyDevice",
567
state=iotshadow.ShadowState(
568
desired={"target_temperature": 22.0},
569
reported={"current_temperature": 20.1}
570
)
571
)
572
update_response = await shadow_client.update_shadow(update_request)
573
print(f"Update response: {update_response}")
574
575
except iotshadow.V2ServiceException as e:
576
print(f"Shadow operation failed: {e.message}")
577
if e.modeled_error:
578
print(f"Error details: {e.modeled_error}")
579
580
finally:
581
await connection.disconnect()
582
583
# Run async operations
584
asyncio.run(shadow_operations())
585
```
586
587
### Named Shadow Operations
588
589
```python
590
# Using named shadows for multi-component devices
591
update_request = iotshadow.UpdateNamedShadowRequest(
592
thing_name="SmartHome",
593
shadow_name="thermostat",
594
state=iotshadow.ShadowState(
595
reported={
596
"temperature": 21.5,
597
"humidity": 45.0,
598
"mode": "heat",
599
"target_temperature": 22.0
600
}
601
)
602
)
603
604
# V1 client
605
shadow_client.publish_update_named_shadow(update_request, mqtt.QoS.AT_LEAST_ONCE)
606
607
# V2 client
608
response = await shadow_client.update_named_shadow(update_request)
609
```