1
+ package com .espressif .ui .activities ;
2
+
3
+ import android .os .Bundle ;
4
+ import android .os .Handler ;
5
+ import android .text .TextUtils ;
6
+ import android .text .TextWatcher ;
7
+ import android .text .Editable ;
8
+ import android .util .Log ;
9
+ import android .view .View ;
10
+ import android .widget .Button ;
11
+ import android .widget .EditText ;
12
+ import android .widget .TextView ;
13
+
14
+ import androidx .appcompat .app .AppCompatActivity ;
15
+ import androidx .appcompat .widget .Toolbar ;
16
+
17
+ import com .espressif .AppConstants ;
18
+ import com .espressif .cloudapi .ApiManager ;
19
+ import com .espressif .cloudapi .ApiResponseListener ;
20
+ import com .espressif .rainmaker .R ;
21
+ import com .google .android .material .appbar .MaterialToolbar ;
22
+ import com .google .android .material .textfield .TextInputEditText ;
23
+ import com .google .gson .JsonArray ;
24
+ import com .google .gson .JsonElement ;
25
+ import com .google .gson .JsonObject ;
26
+ import com .google .gson .JsonParser ;
27
+ import com .google .gson .JsonSyntaxException ;
28
+
29
+ import org .json .JSONObject ;
30
+ import org .json .JSONArray ;
31
+
32
+ public class CmdRespActivity extends AppCompatActivity {
33
+
34
+ private static final String TAG = CmdRespActivity .class .getSimpleName ();
35
+ private static final int STATUS_POLLING_INTERVAL = 2000 ; // 2 seconds
36
+
37
+ private String nodeId ;
38
+ private TextInputEditText etId , etPayload , etTimeout ;
39
+ private TextView tvResponse ;
40
+ private Button btnSend ;
41
+ private ApiManager apiManager ;
42
+ private Handler handler ;
43
+ private String requestId ;
44
+ private boolean isPolling = false ;
45
+
46
+ @ Override
47
+ protected void onCreate (Bundle savedInstanceState ) {
48
+ super .onCreate (savedInstanceState );
49
+ setContentView (R .layout .activity_cmd_resp );
50
+
51
+ nodeId = getIntent ().getStringExtra (AppConstants .KEY_NODE_ID );
52
+ initViews ();
53
+ apiManager = ApiManager .getInstance (this );
54
+ handler = new Handler ();
55
+ }
56
+
57
+ private void initViews () {
58
+ MaterialToolbar toolbar = findViewById (R .id .toolbar );
59
+ setSupportActionBar (toolbar );
60
+ getSupportActionBar ().setDisplayHomeAsUpEnabled (true );
61
+ getSupportActionBar ().setDisplayShowHomeEnabled (true );
62
+ getSupportActionBar ().setTitle (R .string .title_activity_cmd_resp );
63
+ toolbar .setNavigationIcon (R .drawable .ic_arrow_left );
64
+ toolbar .setNavigationOnClickListener (v -> finish ());
65
+
66
+ etId = findViewById (R .id .et_cmd_id );
67
+ etPayload = findViewById (R .id .et_cmd_payload );
68
+ etTimeout = findViewById (R .id .et_cmd_timeout );
69
+ tvResponse = findViewById (R .id .tv_cmd_response );
70
+ btnSend = findViewById (R .id .btn_send_cmd );
71
+
72
+ btnSend .setOnClickListener (v -> sendCommand ());
73
+
74
+ // Add text change listeners to re-enable button
75
+ TextWatcher textWatcher = new TextWatcher () {
76
+ @ Override
77
+ public void beforeTextChanged (CharSequence s , int start , int count , int after ) {
78
+ }
79
+
80
+ @ Override
81
+ public void onTextChanged (CharSequence s , int start , int before , int count ) {
82
+ if (isPolling ) {
83
+ btnSend .setEnabled (true );
84
+ btnSend .setOnClickListener (v -> {
85
+ stopPolling ();
86
+ sendCommand ();
87
+ });
88
+ }
89
+ }
90
+
91
+ @ Override
92
+ public void afterTextChanged (Editable s ) {
93
+ }
94
+ };
95
+
96
+ etId .addTextChangedListener (textWatcher );
97
+ etPayload .addTextChangedListener (textWatcher );
98
+ etTimeout .addTextChangedListener (textWatcher );
99
+ }
100
+
101
+ private void sendCommand () {
102
+ String idStr = etId .getText ().toString ();
103
+ String payload = etPayload .getText ().toString ();
104
+ String timeoutStr = etTimeout .getText ().toString ();
105
+
106
+ if (TextUtils .isEmpty (idStr )) {
107
+ etId .setError (getString (R .string .error_cmd_id_empty ));
108
+ return ;
109
+ }
110
+
111
+ try {
112
+ int commandId = Integer .parseInt (idStr );
113
+ if (commandId <= 0 ) {
114
+ etId .setError (getString (R .string .error_cmd_id_positive ));
115
+ return ;
116
+ }
117
+ } catch (NumberFormatException e ) {
118
+ etId .setError (getString (R .string .error_cmd_id_invalid ));
119
+ return ;
120
+ }
121
+
122
+ if (TextUtils .isEmpty (payload )) {
123
+ etPayload .setError (getString (R .string .error_empty_payload ));
124
+ return ;
125
+ }
126
+
127
+ if (TextUtils .isEmpty (timeoutStr )) {
128
+ etTimeout .setError (getString (R .string .error_cmd_timeout_empty ));
129
+ return ;
130
+ }
131
+
132
+ int timeout ;
133
+ try {
134
+ timeout = Integer .parseInt (timeoutStr );
135
+ if (timeout <= 0 ) {
136
+ etTimeout .setError (getString (R .string .error_cmd_timeout_positive ));
137
+ return ;
138
+ }
139
+ } catch (NumberFormatException e ) {
140
+ etTimeout .setError (getString (R .string .error_cmd_timeout_invalid ));
141
+ return ;
142
+ }
143
+
144
+ JsonObject requestBody = new JsonObject ();
145
+ requestBody .addProperty (AppConstants .KEY_CMD , Integer .parseInt (idStr ));
146
+ requestBody .addProperty (AppConstants .KEY_IS_BASE64 , false );
147
+ requestBody .addProperty (AppConstants .KEY_TIMEOUT , timeout );
148
+
149
+ JsonArray nodeIds = new JsonArray ();
150
+ nodeIds .add (nodeId );
151
+ requestBody .add (AppConstants .KEY_NODE_IDS , nodeIds );
152
+
153
+ // Try parsing as JSON first
154
+ try {
155
+ JsonParser parser = new JsonParser ();
156
+ JsonElement jsonElement ;
157
+ try {
158
+ jsonElement = parser .parse (payload );
159
+ } catch (JsonSyntaxException e ) {
160
+ // Invalid JSON syntax
161
+ etPayload .setError (getString (R .string .error_invalid_payload ));
162
+ tvResponse .setText (getString (R .string .error_invalid_payload ));
163
+ return ;
164
+ }
165
+
166
+ // Valid JSON syntax, but check if it's an object
167
+ if (!jsonElement .isJsonObject ()) {
168
+ etPayload .setError (getString (R .string .error_invalid_json ));
169
+ tvResponse .setText (getString (R .string .error_invalid_json ));
170
+ return ;
171
+ }
172
+ requestBody .add (AppConstants .KEY_DATA , jsonElement );
173
+ } catch (Exception e ) {
174
+ // Check if it's base64 encoded
175
+ if (isBase64 (payload )) {
176
+ requestBody .addProperty (AppConstants .KEY_DATA , payload );
177
+ requestBody .addProperty (AppConstants .KEY_IS_BASE64 , true );
178
+ } else {
179
+ // Neither valid JSON nor base64
180
+ etPayload .setError (getString (R .string .error_invalid_payload ));
181
+ tvResponse .setText (getString (R .string .error_invalid_payload ));
182
+ return ;
183
+ }
184
+ }
185
+
186
+ Log .d (TAG , "Command request body: " + requestBody .toString ());
187
+ tvResponse .setText ("" );
188
+ btnSend .setEnabled (false );
189
+
190
+ apiManager .sendCommandResponse (requestBody , new ApiResponseListener () {
191
+ @ Override
192
+ public void onSuccess (Bundle data ) {
193
+ String reqId = data .getString (AppConstants .KEY_REQUEST_ID );
194
+ if (!TextUtils .isEmpty (reqId )) {
195
+ requestId = reqId ;
196
+ String msg = getString (R .string .request_id_received , requestId );
197
+ tvResponse .setText (msg );
198
+ startPolling ();
199
+ }
200
+ }
201
+
202
+ @ Override
203
+ public void onResponseFailure (Exception exception ) {
204
+ stopPolling ();
205
+ String error = exception .getMessage ();
206
+ if (!TextUtils .isEmpty (error )) {
207
+ tvResponse .setText (error );
208
+ }
209
+ }
210
+
211
+ @ Override
212
+ public void onNetworkFailure (Exception exception ) {
213
+ stopPolling ();
214
+ tvResponse .setText (R .string .error_network );
215
+ }
216
+ });
217
+ }
218
+
219
+ private boolean isBase64 (String str ) {
220
+ if (TextUtils .isEmpty (str )) {
221
+ return false ;
222
+ }
223
+ try {
224
+ android .util .Base64 .decode (str , android .util .Base64 .DEFAULT );
225
+ return true ;
226
+ } catch (IllegalArgumentException e ) {
227
+ return false ;
228
+ }
229
+ }
230
+
231
+ private void startPolling () {
232
+ if (!isPolling ) {
233
+ isPolling = true ;
234
+ btnSend .setEnabled (false );
235
+ btnSend .setOnClickListener (v -> stopPolling ());
236
+ pollStatus ();
237
+ }
238
+ }
239
+
240
+ private void stopPolling () {
241
+ isPolling = false ;
242
+ handler .removeCallbacksAndMessages (null );
243
+ btnSend .setEnabled (true );
244
+ btnSend .setOnClickListener (v -> sendCommand ());
245
+ }
246
+
247
+ private void pollStatus () {
248
+ if (!isPolling ) {
249
+ return ;
250
+ }
251
+
252
+ Log .d (TAG , "Polling status for request: " + requestId );
253
+
254
+ apiManager .getCommandResponseStatus (requestId , new ApiResponseListener () {
255
+ @ Override
256
+ public void onSuccess (Bundle data ) {
257
+ Log .d (TAG , "Poll status success: " + data .toString ());
258
+ runOnUiThread (() -> {
259
+ try {
260
+ String status = data .getString (AppConstants .KEY_STATUS );
261
+
262
+ // Display latest status
263
+ tvResponse .setText ("Status: " + status );
264
+
265
+ // Enable button if status is not "requested" or "in_progress"
266
+ if (!status .equals ("requested" ) && !status .equals ("in_progress" )) {
267
+ btnSend .setEnabled (true );
268
+ }
269
+
270
+ if ("success" .equals (status )) {
271
+ String responseData = data .getString (AppConstants .KEY_RESPONSE_DATA );
272
+ if (!TextUtils .isEmpty (responseData )) {
273
+ tvResponse .append ("\n Response: " + responseData );
274
+ }
275
+ stopPolling ();
276
+ } else if ("timed_out" .equals (status )) {
277
+ tvResponse .append ("\n Reason: Timed Out" );
278
+ stopPolling ();
279
+ } else if ("failure" .equals (status )) {
280
+ String statusDesc = data .getString (AppConstants .KEY_STATUS_DESCRIPTION );
281
+ if (!TextUtils .isEmpty (statusDesc )) {
282
+ tvResponse .append ("\n Reason: " + statusDesc );
283
+ }
284
+ stopPolling ();
285
+ } else {
286
+ handler .postDelayed (() -> pollStatus (), STATUS_POLLING_INTERVAL );
287
+ }
288
+ } catch (Exception e ) {
289
+ Log .e (TAG , "Error parsing response" , e );
290
+ tvResponse .setText ("Error parsing response" );
291
+ stopPolling ();
292
+ }
293
+ });
294
+ }
295
+
296
+ @ Override
297
+ public void onResponseFailure (Exception exception ) {
298
+ Log .e (TAG , "Poll status response failure" , exception );
299
+ runOnUiThread (() -> {
300
+ tvResponse .setText (R .string .error_network );
301
+ stopPolling ();
302
+ });
303
+ }
304
+
305
+ @ Override
306
+ public void onNetworkFailure (Exception exception ) {
307
+ Log .e (TAG , "Poll status network failure" , exception );
308
+ runOnUiThread (() -> {
309
+ tvResponse .setText (R .string .error_network );
310
+ stopPolling ();
311
+ });
312
+ }
313
+ });
314
+ }
315
+
316
+ @ Override
317
+ protected void onDestroy () {
318
+ stopPolling ();
319
+ super .onDestroy ();
320
+ }
321
+ }
0 commit comments