Skip to content

Commit 90f2e0b

Browse files
authored
Merge pull request #56 from jencao-aws/april-test-iot-data-plane
Added examples for IOT data plane
2 parents 718c6ff + a23751f commit 90f2e0b

4 files changed

Lines changed: 403 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
" Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
" SPDX-License-Identifier: Apache-2.0
3+
CLASS /awsex/cl_iop_actions DEFINITION
4+
PUBLIC
5+
FINAL
6+
CREATE PUBLIC .
7+
8+
PUBLIC SECTION.
9+
" Updates the shadow for an AWS IoT thing
10+
" @parameter iv_thing_name | The name of the thing (e.g., 'MyIoTDevice')
11+
" @parameter iv_shadow_state | The shadow state as JSON string
12+
" @raising /aws1/cx_rt_generic | Thrown when operation fails
13+
METHODS update_thing_shadow
14+
IMPORTING
15+
!iv_thing_name TYPE /aws1/iopthingname
16+
!iv_shadow_state TYPE string
17+
RAISING
18+
/aws1/cx_rt_generic.
19+
20+
" Gets the shadow for an AWS IoT thing
21+
" @parameter iv_thing_name | The name of the thing (e.g., 'MyIoTDevice')
22+
" @parameter ov_shadow | The shadow state as JSON string
23+
" @raising /aws1/cx_rt_generic | Thrown when operation fails
24+
METHODS get_thing_shadow
25+
IMPORTING
26+
!iv_thing_name TYPE /aws1/iopthingname
27+
RETURNING
28+
VALUE(ov_shadow) TYPE string
29+
RAISING
30+
/aws1/cx_rt_generic.
31+
32+
PROTECTED SECTION.
33+
PRIVATE SECTION.
34+
ENDCLASS.
35+
36+
37+
38+
CLASS /awsex/cl_iop_actions IMPLEMENTATION.
39+
40+
METHOD update_thing_shadow.
41+
CONSTANTS cv_pfl TYPE /aws1/rt_profile_id VALUE 'ZCODE_DEMO'.
42+
43+
DATA(lo_session) = /aws1/cl_rt_session_aws=>create( cv_pfl ).
44+
DATA(lo_iop) = /aws1/cl_iop_factory=>create( lo_session ).
45+
46+
" snippet-start:[iot.abapv1.update_thing_shadow]
47+
TRY.
48+
" Convert JSON string to xstring for payload
49+
DATA(lv_payload) = /aws1/cl_rt_util=>string_to_xstring( iv_shadow_state ).
50+
51+
lo_iop->updatethingshadow(
52+
iv_thingname = iv_thing_name
53+
iv_payload = lv_payload ).
54+
MESSAGE 'Thing shadow updated successfully.' TYPE 'I'.
55+
CATCH /aws1/cx_iopresourcenotfoundex.
56+
MESSAGE 'Thing not found.' TYPE 'E'.
57+
CATCH /aws1/cx_rt_generic INTO DATA(lo_exception).
58+
DATA(lv_error) = |{ lo_exception->get_text( ) }|.
59+
MESSAGE lv_error TYPE 'E'.
60+
ENDTRY.
61+
" snippet-end:[iot.abapv1.update_thing_shadow]
62+
ENDMETHOD.
63+
64+
METHOD get_thing_shadow.
65+
CONSTANTS cv_pfl TYPE /aws1/rt_profile_id VALUE 'ZCODE_DEMO'.
66+
67+
DATA(lo_session) = /aws1/cl_rt_session_aws=>create( cv_pfl ).
68+
DATA(lo_iop) = /aws1/cl_iop_factory=>create( lo_session ).
69+
70+
" snippet-start:[iot.abapv1.get_thing_shadow]
71+
TRY.
72+
DATA(lo_result) = lo_iop->getthingshadow( iv_thingname = iv_thing_name ).
73+
74+
" Convert xstring payload to JSON string
75+
DATA(lv_payload) = lo_result->get_payload( ).
76+
ov_shadow = /aws1/cl_rt_util=>xstring_to_string( lv_payload ).
77+
MESSAGE 'Thing shadow retrieved successfully.' TYPE 'I'.
78+
CATCH /aws1/cx_iopresourcenotfoundex.
79+
MESSAGE 'Thing shadow not found.' TYPE 'E'.
80+
CATCH /aws1/cx_rt_generic INTO DATA(lo_exception).
81+
DATA(lv_error) = |{ lo_exception->get_text( ) }|.
82+
MESSAGE lv_error TYPE 'E'.
83+
ENDTRY.
84+
" snippet-end:[iot.abapv1.get_thing_shadow]
85+
ENDMETHOD.
86+
87+
ENDCLASS.
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
" Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
" SPDX-License-Identifier: Apache-2.0
3+
CLASS ltc_awsex_cl_iop_actions DEFINITION DEFERRED.
4+
CLASS /awsex/cl_iop_actions DEFINITION LOCAL FRIENDS ltc_awsex_cl_iop_actions.
5+
6+
CLASS ltc_awsex_cl_iop_actions DEFINITION FOR TESTING DURATION LONG RISK LEVEL DANGEROUS.
7+
8+
PRIVATE SECTION.
9+
CONSTANTS cv_pfl TYPE /aws1/rt_profile_id VALUE 'ZCODE_DEMO'.
10+
11+
CLASS-DATA ao_iot TYPE REF TO /aws1/if_iot.
12+
CLASS-DATA ao_iop TYPE REF TO /aws1/if_iop.
13+
CLASS-DATA ao_session TYPE REF TO /aws1/cl_rt_session_base.
14+
CLASS-DATA ao_iop_actions TYPE REF TO /awsex/cl_iop_actions.
15+
16+
CLASS-DATA av_thing_name TYPE /aws1/iopthingname.
17+
18+
METHODS: update_thing_shadow FOR TESTING RAISING /aws1/cx_rt_generic,
19+
get_thing_shadow FOR TESTING RAISING /aws1/cx_rt_generic.
20+
21+
CLASS-METHODS class_setup RAISING /aws1/cx_rt_generic.
22+
CLASS-METHODS class_teardown RAISING /aws1/cx_rt_generic.
23+
24+
CLASS-METHODS wait_for_thing_ready
25+
IMPORTING
26+
iv_thing_name TYPE /aws1/iotthingname
27+
iv_max_wait TYPE i DEFAULT 30
28+
RAISING
29+
/aws1/cx_rt_generic.
30+
31+
ENDCLASS.
32+
33+
CLASS ltc_awsex_cl_iop_actions IMPLEMENTATION.
34+
35+
METHOD class_setup.
36+
ao_session = /aws1/cl_rt_session_aws=>create( cv_pfl ).
37+
ao_iot = /aws1/cl_iot_factory=>create( ao_session ).
38+
ao_iop = /aws1/cl_iop_factory=>create( ao_session ).
39+
ao_iop_actions = NEW /awsex/cl_iop_actions( ).
40+
41+
" Create a test thing for shadow operations - MUST succeed
42+
DATA(lv_uuid) = /awsex/cl_utils=>get_random_string( ).
43+
av_thing_name = |sap-abap-iot-shadow-{ lv_uuid }|.
44+
45+
TRY.
46+
DATA(lo_thing_result) = ao_iot->creatething( iv_thingname = av_thing_name ).
47+
48+
IF lo_thing_result IS NOT BOUND.
49+
MESSAGE 'Failed to create IoT thing for shadow tests' TYPE 'E'.
50+
ENDIF.
51+
52+
" Verify thing was created
53+
cl_abap_unit_assert=>assert_not_initial(
54+
act = lo_thing_result->get_thingname( )
55+
msg = |Thing name is initial after creation| ).
56+
57+
CATCH /aws1/cx_iotresrcalrdyexistsex.
58+
" Thing already exists - verify it's accessible
59+
TRY.
60+
ao_iot->describething( iv_thingname = av_thing_name ).
61+
CATCH /aws1/cx_rt_generic INTO DATA(lo_desc_ex).
62+
MESSAGE |Thing exists but cannot be accessed: { lo_desc_ex->get_text( ) }| TYPE 'E'.
63+
ENDTRY.
64+
CATCH /aws1/cx_rt_generic INTO DATA(lo_create_ex).
65+
MESSAGE |Failed to create thing: { lo_create_ex->get_text( ) }| TYPE 'E'.
66+
ENDTRY.
67+
68+
" Wait for thing to be fully ready
69+
wait_for_thing_ready( iv_thing_name = av_thing_name ).
70+
71+
ENDMETHOD.
72+
73+
METHOD class_teardown.
74+
" Clean up test thing
75+
IF av_thing_name IS NOT INITIAL.
76+
TRY.
77+
" Delete the thing shadow first if it exists
78+
TRY.
79+
ao_iop->deletethingshadow( iv_thingname = av_thing_name ).
80+
" Wait for shadow deletion to propagate
81+
WAIT UP TO 2 SECONDS.
82+
CATCH /aws1/cx_rt_generic.
83+
" Shadow may not exist
84+
ENDTRY.
85+
86+
" Delete the thing
87+
ao_iot->deletething( iv_thingname = av_thing_name ).
88+
CATCH /aws1/cx_rt_generic.
89+
" Thing may not exist or may have dependencies
90+
ENDTRY.
91+
ENDIF.
92+
93+
ENDMETHOD.
94+
95+
METHOD wait_for_thing_ready.
96+
" Wait for thing to be ready with status-based polling
97+
DATA lv_elapsed TYPE i VALUE 0.
98+
DATA lv_ready TYPE abap_bool VALUE abap_false.
99+
DATA lv_start_time TYPE timestampl.
100+
DATA lv_current_time TYPE timestampl.
101+
102+
GET TIME STAMP FIELD lv_start_time.
103+
104+
WHILE lv_elapsed < iv_max_wait AND lv_ready = abap_false.
105+
TRY.
106+
" Try to describe the thing
107+
DATA(lo_thing_desc) = ao_iot->describething( iv_thingname = iv_thing_name ).
108+
IF lo_thing_desc IS BOUND.
109+
lv_ready = abap_true.
110+
ELSE.
111+
WAIT UP TO 2 SECONDS.
112+
GET TIME STAMP FIELD lv_current_time.
113+
lv_elapsed = lv_current_time - lv_start_time.
114+
ENDIF.
115+
116+
CATCH /aws1/cx_rt_generic.
117+
" Thing not ready yet
118+
WAIT UP TO 2 SECONDS.
119+
GET TIME STAMP FIELD lv_current_time.
120+
lv_elapsed = lv_current_time - lv_start_time.
121+
ENDTRY.
122+
ENDWHILE.
123+
124+
IF lv_ready = abap_false.
125+
MESSAGE 'Thing did not become ready in time' TYPE 'E'.
126+
ENDIF.
127+
128+
ENDMETHOD.
129+
130+
METHOD update_thing_shadow.
131+
" Verify thing exists
132+
TRY.
133+
ao_iot->describething( iv_thingname = av_thing_name ).
134+
CATCH /aws1/cx_rt_generic INTO DATA(lo_verify_ex).
135+
MESSAGE |Thing does not exist: { lo_verify_ex->get_text( ) }| TYPE 'E'.
136+
ENDTRY.
137+
138+
" Create a shadow state JSON
139+
DATA lv_shadow_state TYPE string.
140+
lv_shadow_state =
141+
'{"state":{"desired":{"temperature":25,"humidity":60},"reported":{"temperature":24,"humidity":58}}}'.
142+
143+
" Update thing shadow - this MUST succeed
144+
ao_iop_actions->update_thing_shadow(
145+
iv_thing_name = av_thing_name
146+
iv_shadow_state = lv_shadow_state ).
147+
148+
" Wait for shadow to be updated
149+
WAIT UP TO 3 SECONDS.
150+
151+
" Verify shadow was updated by getting it - MUST be retrievable
152+
DATA(lo_shadow_result) = ao_iop->getthingshadow( iv_thingname = av_thing_name ).
153+
154+
cl_abap_unit_assert=>assert_bound(
155+
act = lo_shadow_result
156+
msg = |Shadow result is not bound| ).
157+
158+
DATA(lv_payload) = lo_shadow_result->get_payload( ).
159+
160+
cl_abap_unit_assert=>assert_not_initial(
161+
act = lv_payload
162+
msg = |Shadow payload is empty| ).
163+
164+
" Convert payload to string and verify it contains expected data
165+
DATA(lv_shadow_json) = /aws1/cl_rt_util=>xstring_to_string( lv_payload ).
166+
167+
cl_abap_unit_assert=>assert_not_initial(
168+
act = lv_shadow_json
169+
msg = |Shadow JSON is empty| ).
170+
171+
" Verify shadow structure - MUST contain expected fields
172+
cl_abap_unit_assert=>assert_differs(
173+
act = find( val = lv_shadow_json sub = 'state' )
174+
exp = -1
175+
msg = |Shadow does not contain state field| ).
176+
177+
cl_abap_unit_assert=>assert_differs(
178+
act = find( val = lv_shadow_json sub = 'desired' )
179+
exp = -1
180+
msg = |Shadow does not contain desired state| ).
181+
182+
cl_abap_unit_assert=>assert_differs(
183+
act = find( val = lv_shadow_json sub = 'reported' )
184+
exp = -1
185+
msg = |Shadow does not contain reported state| ).
186+
187+
" Verify our test values are present
188+
cl_abap_unit_assert=>assert_differs(
189+
act = find( val = lv_shadow_json sub = 'temperature' )
190+
exp = -1
191+
msg = |Shadow does not contain temperature field| ).
192+
193+
cl_abap_unit_assert=>assert_differs(
194+
act = find( val = lv_shadow_json sub = 'humidity' )
195+
exp = -1
196+
msg = |Shadow does not contain humidity field| ).
197+
198+
ENDMETHOD.
199+
200+
METHOD get_thing_shadow.
201+
" Verify thing exists
202+
TRY.
203+
ao_iot->describething( iv_thingname = av_thing_name ).
204+
CATCH /aws1/cx_rt_generic INTO DATA(lo_verify_ex).
205+
MESSAGE |Thing does not exist: { lo_verify_ex->get_text( ) }| TYPE 'E'.
206+
ENDTRY.
207+
208+
" First ensure shadow exists by updating it
209+
DATA lv_shadow_state TYPE string.
210+
lv_shadow_state =
211+
'{"state":{"desired":{"power":"on","level":75},"reported":{"power":"on","level":70}}}'.
212+
213+
TRY.
214+
ao_iop->updatethingshadow(
215+
iv_thingname = av_thing_name
216+
iv_payload = /aws1/cl_rt_util=>string_to_xstring( lv_shadow_state ) ).
217+
CATCH /aws1/cx_rt_generic INTO DATA(lo_update_ex).
218+
MESSAGE |Failed to create shadow for test: { lo_update_ex->get_text( ) }| TYPE 'E'.
219+
ENDTRY.
220+
221+
" Wait for shadow to be updated with status-based polling
222+
DATA lv_shadow_ready TYPE abap_bool VALUE abap_false.
223+
DATA lv_retry_count TYPE i VALUE 0.
224+
225+
WHILE lv_retry_count < 10 AND lv_shadow_ready = abap_false.
226+
TRY.
227+
DATA(lo_check_shadow) = ao_iop->getthingshadow( iv_thingname = av_thing_name ).
228+
IF lo_check_shadow IS BOUND AND lo_check_shadow->get_payload( ) IS NOT INITIAL.
229+
lv_shadow_ready = abap_true.
230+
ELSE.
231+
lv_retry_count = lv_retry_count + 1.
232+
WAIT UP TO 2 SECONDS.
233+
ENDIF.
234+
CATCH /aws1/cx_rt_generic.
235+
lv_retry_count = lv_retry_count + 1.
236+
IF lv_retry_count < 10.
237+
WAIT UP TO 2 SECONDS.
238+
ELSE.
239+
MESSAGE 'Shadow did not become ready in time' TYPE 'E'.
240+
ENDIF.
241+
ENDTRY.
242+
ENDWHILE.
243+
244+
IF lv_shadow_ready = abap_false.
245+
MESSAGE 'Shadow was not created successfully' TYPE 'E'.
246+
ENDIF.
247+
248+
" Now get the shadow using our action method - this MUST succeed
249+
DATA(lv_shadow) = ao_iop_actions->get_thing_shadow( iv_thing_name = av_thing_name ).
250+
251+
cl_abap_unit_assert=>assert_not_initial(
252+
act = lv_shadow
253+
msg = |Shadow is empty| ).
254+
255+
" Verify shadow contains expected structure - MUST contain these fields
256+
cl_abap_unit_assert=>assert_differs(
257+
act = find( val = lv_shadow sub = 'state' )
258+
exp = -1
259+
msg = |Shadow does not contain state| ).
260+
261+
cl_abap_unit_assert=>assert_differs(
262+
act = find( val = lv_shadow sub = 'desired' )
263+
exp = -1
264+
msg = |Shadow does not contain desired state| ).
265+
266+
cl_abap_unit_assert=>assert_differs(
267+
act = find( val = lv_shadow sub = 'reported' )
268+
exp = -1
269+
msg = |Shadow does not contain reported state| ).
270+
271+
" Verify it contains our test values - MUST contain these fields
272+
cl_abap_unit_assert=>assert_differs(
273+
act = find( val = lv_shadow sub = 'power' )
274+
exp = -1
275+
msg = |Shadow does not contain power field| ).
276+
277+
cl_abap_unit_assert=>assert_differs(
278+
act = find( val = lv_shadow sub = 'level' )
279+
exp = -1
280+
msg = |Shadow does not contain level field| ).
281+
282+
" Verify the actual values are correct
283+
cl_abap_unit_assert=>assert_true(
284+
act = xsdbool( find( val = lv_shadow sub = '75' ) >= 0 OR find( val = lv_shadow sub = '"level":75' ) >= 0 )
285+
msg = |Shadow does not contain expected level value 75| ).
286+
287+
ENDMETHOD.
288+
289+
ENDCLASS.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
3+
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
4+
<asx:values>
5+
<VSEOCLASS>
6+
<CLSNAME>/AWSEX/CL_IOP_ACTIONS</CLSNAME>
7+
<LANGU>E</LANGU>
8+
<DESCRIPT>Iot-data-plane Code Example</DESCRIPT>
9+
<STATE>1</STATE>
10+
<CLSCCINCL>X</CLSCCINCL>
11+
<FIXPT>X</FIXPT>
12+
<UNICODE>X</UNICODE>
13+
<WITH_UNIT_TESTS>X</WITH_UNIT_TESTS>
14+
</VSEOCLASS>
15+
</asx:values>
16+
</asx:abap>
17+
</abapGit>

0 commit comments

Comments
 (0)