Skip to content

Commit 8e05803

Browse files
committed
feature: Add command response feature for nodes
Implements a new feature to send commands to nodes and receive responses with status tracking. This is just for testing out command-response workflow and can be found under Node details, if the feature is enabled in firmware. Production apps should integrate this elsewhere as per their requirement. Note that this feature will be disabled by default and can be enabled by setting isCommandResponseSupported=true in local.properties.
1 parent cf0409e commit 8e05803

File tree

13 files changed

+667
-12
lines changed

13 files changed

+667
-12
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ This account linking flow enables users to link their Alexa user identity with t
203203
- Link their account without entering Alexa account credentials if already logged into Alexa app. They will have to login to Rainmaker once, when trying to link accounts.
204204
- Link their account from your RainMaker using [Login with Amazon (LWA)](https://developer.amazon.com/docs/login-with-amazon/documentation-overview.html), when the Alexa app isn't installed on their device.
205205

206+
### Command Response
207+
208+
Command Response allows users to send commands to nodes and receive responses back asynchronously. This provides a more robust way of communicating with nodes and also allows nodes to provide access control based on primary/secondary role.
209+
More information about this can be found [here](https://rainmaker.espressif.com/docs/cmd-resp)
210+
211+
This feature is optional and disabled by default.
212+
Add `isCommandResponseSupported=true` in `local.properties` file to enable this feature.
213+
206214
## Matter support
207215

208216
### What is Matter?

app/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ android {
122122
buildConfigField "boolean", "isAutomationSupported", localProperties.getProperty("isAutomationSupported", "true")
123123
buildConfigField "boolean", "isOtaSupported", localProperties.getProperty("isOtaSupported", "false")
124124
buildConfigField "boolean", "isMatterSupported", localProperties.getProperty("isMatterSupported", "false")
125+
buildConfigField "boolean", "isCommandResponseSupported", localProperties.getProperty("isCommandResponseSupported", "false")
125126

126127
//---Enable Continuous Updates---//
127128
buildConfigField "boolean", "isContinuousUpdateEnable", localProperties.getProperty("isContinuousUpdateEnable", "true")

app/src/main/AndroidManifest.xml

+5
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,11 @@
378378
</intent-filter>
379379
</receiver>
380380

381+
<activity
382+
android:name="com.espressif.ui.activities.CmdRespActivity"
383+
android:screenOrientation="portrait"
384+
android:theme="@style/AppTheme.NoActionBar" />
385+
381386
</application>
382387

383388
<queries>

app/src/main/java/com/espressif/AppConstants.kt

+11
Original file line numberDiff line numberDiff line change
@@ -543,5 +543,16 @@ class AppConstants {
543543
EVENT_CTRL_CONFIG_DONE,
544544
EVENT_MATTER_DEVICE_CONNECTIVITY
545545
}
546+
547+
// Command Response constants
548+
const val KEY_RESPONSE_DATA = "response_data"
549+
const val KEY_REQUEST_ID = "request_id"
550+
const val KEY_STATUS_DESCRIPTION = "status_description"
551+
const val KEY_REQUESTS = "requests"
552+
const val KEY_CMD = "cmd"
553+
const val KEY_IS_BASE64 = "is_base64"
554+
const val KEY_TIMEOUT = "timeout"
555+
const val KEY_DATA = "data"
556+
const val URL_USER_NODES_CMD = "/user/nodes/cmd"
546557
}
547558
}

app/src/main/java/com/espressif/cloudapi/ApiInterface.java

+9
Original file line numberDiff line numberDiff line change
@@ -346,4 +346,13 @@ Call<ResponseBody> pushFwUpdate(@Url String url, @Header(AppConstants.HEADER_AUT
346346
Call<ResponseBody> convertGroupToFabric(@Url String url, @Header(AppConstants.HEADER_AUTHORIZATION) String token,
347347
@Query(AppConstants.KEY_GROUP_ID) String groupId,
348348
@Body JsonObject body);
349+
350+
@POST
351+
Call<ResponseBody> sendCommandResponse(@Url String url, @Header(AppConstants.HEADER_AUTHORIZATION) String token,
352+
@Body JsonObject requestBody);
353+
354+
@GET
355+
Call<ResponseBody> getCommandResponseStatus(
356+
@Url String url, @Header(AppConstants.HEADER_AUTHORIZATION) String token,
357+
@Query(AppConstants.KEY_REQUEST_ID) String requestId);
349358
}

app/src/main/java/com/espressif/cloudapi/ApiManager.java

+92
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,17 @@
7676
import java.util.Map;
7777

7878
import io.reactivex.Observable;
79+
import io.reactivex.android.schedulers.AndroidSchedulers;
7980
import io.reactivex.functions.Consumer;
8081
import io.reactivex.functions.Function;
8182
import io.reactivex.schedulers.Schedulers;
83+
import io.reactivex.observers.DisposableSingleObserver;
84+
import io.reactivex.disposables.Disposable;
8285
import okhttp3.ResponseBody;
8386
import retrofit2.Call;
8487
import retrofit2.Callback;
8588
import retrofit2.Response;
89+
import retrofit2.HttpException;
8690

8791
public class ApiManager {
8892

@@ -4711,4 +4715,92 @@ public void run() {
47114715
public void cancelRequestStatusPollingTask() {
47124716
handler.removeCallbacks(stopRequestStatusPollingTask);
47134717
}
4718+
4719+
public void sendCommandResponse(JsonObject requestBody, ApiResponseListener listener) {
4720+
Log.d(TAG, "Send command response");
4721+
4722+
apiInterface.sendCommandResponse(getBaseUrl() + AppConstants.URL_USER_NODES_CMD,
4723+
accessToken, requestBody).enqueue(new Callback<ResponseBody>() {
4724+
4725+
@Override
4726+
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
4727+
try {
4728+
if (response.isSuccessful()) {
4729+
String jsonResponse = response.body().string();
4730+
Log.d(TAG, "Response : " + jsonResponse);
4731+
JSONObject jsonObject = new JSONObject(jsonResponse);
4732+
String requestId = jsonObject.optString(AppConstants.KEY_REQUEST_ID);
4733+
Bundle data = new Bundle();
4734+
data.putString(AppConstants.KEY_REQUEST_ID, requestId);
4735+
listener.onSuccess(data);
4736+
} else {
4737+
String jsonErrResponse = response.errorBody().string();
4738+
processError(jsonErrResponse, listener, "Failed to send command");
4739+
}
4740+
} catch (Exception e) {
4741+
e.printStackTrace();
4742+
listener.onResponseFailure(e);
4743+
}
4744+
}
4745+
4746+
@Override
4747+
public void onFailure(Call<ResponseBody> call, Throwable t) {
4748+
t.printStackTrace();
4749+
listener.onNetworkFailure(new Exception(t));
4750+
}
4751+
});
4752+
}
4753+
4754+
public void getCommandResponseStatus(String requestId, ApiResponseListener listener) {
4755+
Log.d(TAG, "Get command response status");
4756+
4757+
apiInterface.getCommandResponseStatus(
4758+
getBaseUrl() + AppConstants.URL_USER_NODES_CMD, accessToken,
4759+
requestId).enqueue(new Callback<ResponseBody>() {
4760+
@Override
4761+
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
4762+
try {
4763+
if (response.isSuccessful()) {
4764+
String jsonResponse = response.body().string();
4765+
Log.d(TAG, "Response : " + jsonResponse);
4766+
JSONObject jsonObject = new JSONObject(jsonResponse);
4767+
JSONArray requests = jsonObject.getJSONArray("requests");
4768+
4769+
if (requests.length() > 0) {
4770+
JSONObject request = requests.getJSONObject(0);
4771+
String status = request.optString(AppConstants.KEY_STATUS);
4772+
Bundle data = new Bundle();
4773+
data.putString(AppConstants.KEY_STATUS, status);
4774+
4775+
// Enable button if status is not "requested" or "in_progress"
4776+
boolean enableButton = !status.equals("requested") && !status.equals("in_progress");
4777+
data.putBoolean("enable_button", enableButton);
4778+
4779+
if (request.has(AppConstants.KEY_RESPONSE_DATA)) {
4780+
data.putString(AppConstants.KEY_RESPONSE_DATA, request.getJSONObject(AppConstants.KEY_RESPONSE_DATA).toString());
4781+
}
4782+
if (request.has(AppConstants.KEY_STATUS_DESCRIPTION)) {
4783+
data.putString(AppConstants.KEY_STATUS_DESCRIPTION, request.getString(AppConstants.KEY_STATUS_DESCRIPTION));
4784+
}
4785+
listener.onSuccess(data);
4786+
} else {
4787+
listener.onResponseFailure(new Exception("No request found"));
4788+
}
4789+
} else {
4790+
String jsonErrResponse = response.errorBody().string();
4791+
processError(jsonErrResponse, listener, "Failed to get command status");
4792+
}
4793+
} catch (Exception e) {
4794+
e.printStackTrace();
4795+
listener.onResponseFailure(e);
4796+
}
4797+
}
4798+
4799+
@Override
4800+
public void onFailure(Call<ResponseBody> call, Throwable t) {
4801+
t.printStackTrace();
4802+
listener.onNetworkFailure(new Exception(t));
4803+
}
4804+
});
4805+
}
47144806
}

0 commit comments

Comments
 (0)