(){}.getType());
+ hashMap.put(arg.getIdentifier(),arrayValueWrapper);
+ continue;
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ showToast("数据格式错误");
+ return;
+ }
+ try {
+ OutputParams params = new OutputParams(hashMap);
+ if (!isSubDev) {
+ LinkKit.getInstance().getDeviceThing().thingEventPost(event.getIdentifier(), params, resourceListener);
+ } else {
+// IThing thing = LinkKit.getInstance().getGateway().getSubDeviceThing(mBaseInfo).first;
+// if (thing == null){
+// showToast("子设备当前状态不支持");
+// return;
+// }
+// thing.thingEventPost(event.getIdentifier(), params, resourceListener);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ showToast("上报失败 " + e);
+ }
+ }
+ //endregion
+
+ //region 私有
+
+ /**
+ * 初始化建联
+ * 如果初始化建联失败,需要用户重试去完成初始化,并确保初始化成功。如应用启动的时候无网络,导致失败,可以在网络可以的时候再次执行初始化,成功之后不需要再次执行。
+ * 初始化成功之后,如果因为网络原因连接断开了,用户不需要执行初始化建联操作,SDK会处理建联。
+ *
+ * onError 初始化失败
+ * onInitDone 初始化成功
+ *
+ * SDK 支持以userName+password+clientId 的方式登录(不推荐,建议使用三元组建联)
+ * 设置如下参数,InitManager.init的时候 deviceSecret, productSecret 可以不填
+ * MqttConfigure.mqttUserName = username;
+ * MqttConfigure.mqttPassWord = password;
+ * MqttConfigure.mqttClientId = clientId;
+ */
+ private void connect(Context context) {
+ AppLog.d(TAG, "connect() called");
+ InitManager.init(context, productKey, deviceName, deviceSecret, productSecret, mqttHost, new IDemoCallback() {
+ @Override
+ public void onError(AError aError) {
+ AppLog.d(TAG, "onError() called with: aError = [" + InitManager.getAErrorString(aError) + "]");
+ // 初始化失败,初始化失败之后需要用户负责重新初始化
+ // 如一开始网络不通导致初始化失败,后续网络恢复之后需要重新初始化
+ if (aError != null) {
+ AppLog.d(TAG, "初始化IOT失败,错误信息:" + aError.getCode() + "-" + aError.getSubCode() + ", " + aError.getMsg());
+ // ToastUtils.error("初始化IOT失败,错误信息:" + aError.getCode() + "-" + aError.getSubCode() + ", " + aError.getMsg());
+ } else {
+ AppLog.d(TAG, "初始化IOT失败");
+ //ToastUtils.error("初始化IOT失败");
+ }
+ }
+
+ @Override
+ public void onInitDone(Object data) {
+ AppLog.d(TAG, "初始化IOT成功: data = [" + data + "]");
+ isInitDone = true;
+
+
+ }
+ });
+ }
+
+ /**
+ * 注意:该场景只适合于设备未激活的场景
+ * 验证一型一密 需要以下步骤:
+ * 1.云端创建产品,开启产品的动态注册功能;
+ * 2.创建一个设备,在文件中(raw/deviceinfo)填写改设备信息 productKey,deviceName, productSecret;
+ * 3.通过这三个信息可以去云端动态拿到deviceSecret,并建立长连接;
+ *
+ * @return
+ */
+ public String getFromRaw() {
+ InputStreamReader inputReader = null;
+ BufferedReader bufReader = null;
+ try {
+ inputReader = new InputStreamReader(MainApplication.getContext().getResources().openRawResource(R.raw.deviceinfo));
+ bufReader = new BufferedReader(inputReader);
+ String line = "";
+ String Result = "";
+ while ((line = bufReader.readLine()) != null)
+ Result += line;
+ return Result;
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (bufReader != null) {
+ bufReader.close();
+ }
+ if (inputReader != null) {
+ inputReader.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 解析数据
+ *
+ * @param testData
+ */
+ private void getDeviceInfoFrom(String testData) {
+ AppLog.d(TAG, "getDeviceInfoFrom() called with: testData = [" + testData + "]");
+ try {
+ if (testData == null) {
+ AppLog.e(TAG, "getDeviceInfoFrom: data empty.");
+ userDevInfoError = true;
+ return;
+ }
+ Gson mGson = new Gson();
+ DeviceInfoData deviceInfoData = mGson.fromJson(testData, DeviceInfoData.class);
+ if (deviceInfoData == null) {
+ AppLog.e(TAG, "getDeviceInfoFrom: file format error.");
+ userDevInfoError = true;
+ return;
+ }
+ AppLog.d(TAG, "getDeviceInfoFrom deviceInfoData=" + deviceInfoData);
+ if (checkValid(deviceInfoData)) {
+ mDeviceInfoData = new DeviceInfoData();
+ mDeviceInfoData.productKey = deviceInfoData.productKey;
+ mDeviceInfoData.productSecret = deviceInfoData.productSecret;
+ mDeviceInfoData.deviceName = deviceInfoData.deviceName;
+ mDeviceInfoData.deviceSecret = deviceInfoData.deviceSecret;
+ mDeviceInfoData.username = deviceInfoData.username;
+ mDeviceInfoData.password = deviceInfoData.password;
+ mDeviceInfoData.clientId = deviceInfoData.clientId;
+ mDeviceInfoData.deviceToken = deviceInfoData.deviceToken;
+ mDeviceInfoData.registerType = deviceInfoData.registerType;
+ mDeviceInfoData.mqttHost = deviceInfoData.mqttHost;
+ mDeviceInfoData.instanceId = deviceInfoData.instanceId;
+
+ userDevInfoError = false;
+
+ mDeviceInfoData.subDevice = new ArrayList<>();
+ if (deviceInfoData.subDevice == null) {
+ AppLog.d(TAG, "getDeviceInfoFrom: subDevice empty..");
+ return;
+ }
+ for (int i = 0; i < deviceInfoData.subDevice.size(); i++) {
+ if (checkValid(deviceInfoData.subDevice.get(i))) {
+ mDeviceInfoData.subDevice.add(deviceInfoData.subDevice.get(i));
+ } else {
+ AppLog.d(TAG, "getDeviceInfoFrom: subDevice info invalid. discard.");
+ }
+ }
+
+ productKey = mDeviceInfoData.productKey;
+ deviceName = mDeviceInfoData.deviceName;
+ deviceSecret = mDeviceInfoData.deviceSecret;
+ productSecret = mDeviceInfoData.productSecret;
+ password = mDeviceInfoData.password;
+ username = mDeviceInfoData.username;
+ clientId = mDeviceInfoData.clientId;
+ deviceToken = mDeviceInfoData.deviceToken;
+ registerType = mDeviceInfoData.registerType;
+ mqttHost = mDeviceInfoData.mqttHost;
+ instanceId = mDeviceInfoData.instanceId;
+
+ AppLog.d(TAG, "getDeviceInfoFrom: final data=" + mDeviceInfoData);
+ } else {
+ AppLog.e(TAG, "res/raw/deviceinfo error.");
+ userDevInfoError = true;
+ }
+
+ } catch (Exception e) {
+ AppLog.e(TAG, "getDeviceInfoFrom: e", e);
+ userDevInfoError = true;
+ }
+
+ }
+
+ private boolean checkValid(BaseInfo baseInfo) {
+ if (baseInfo == null) {
+ return false;
+ }
+ if (TextUtils.isEmpty(baseInfo.productKey) || TextUtils.isEmpty(baseInfo.deviceName)) {
+ return false;
+ }
+ if (baseInfo instanceof DeviceInfoData) {
+ if (TextUtils.isEmpty(((DeviceInfo) baseInfo).productSecret) && TextUtils.isEmpty(((DeviceInfo) baseInfo).deviceSecret) && TextUtils.isEmpty(((DeviceInfoData) baseInfo).password)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static void showToast(String message)
+ {
+ ToastUtils.info(message);
+ }
+
+ private boolean isValidDouble(String value) {
+ if (TextUtils.isEmpty(value)) {
+ return false;
+ }
+ try {
+ if (pattern != null && pattern.matcher(value) != null) {
+ if (pattern.matcher(value).matches()) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ private Double getDouble(String value) {
+ if (isValidDouble(value)) {
+ return Double.parseDouble(value);
+ }
+ return null;
+ }
+
+ private boolean isValidInt(String value) {
+ return !TextUtils.isEmpty(value);
+ }
+
+ private int getInt(String value) {
+ if (isValidInt(value)) {
+ try {
+ return Integer.parseInt(value);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return DEF_VALUE;
+ }
+
+ private void report(String identifier, ValueWrapper valueWrapper) {
+ reportData.clear();
+ Map reportData = new HashMap<>();
+ /**
+ * 物模型默认的模块中,属性的identifer是不用加前缀的. 比如名为lightSwitch的属性, identifer就是lightSwitch
+ * 如果是用户自定义的模块,属性的identifer前要加"模块名:"这样的前缀. 比如myBlock模块中的lightSwitch属性,
+ * identifer就要写成"myBlock:lightSwitch"
+ */
+ reportData.put(identifier, valueWrapper);
+ if (!isSubDev) {
+ try {
+ LinkKit.getInstance().getDeviceThing().thingPropertyPost(reportData, resourceListener);
+ } catch (Exception e) {
+ e.printStackTrace();
+ showToast("上报失败 " + e);
+ }
+ } else {
+ try {
+// IThing thing = LinkKit.getInstance().getGateway().getSubDeviceThing(mBaseInfo).first;
+// if (thing == null){
+// showToast("子设备当前状态不支持");
+// return;
+// }
+// thing.thingPropertyPost(reportData, resourceListener);
+ } catch (Exception e) {
+ e.printStackTrace();
+ showToast("上报失败 " + e);
+ }
+ }
+ }
+
+ /**
+ * 上报返回数据
+ */
+ private static IPublishResourceListener resourceListener = new IPublishResourceListener() {
+ @Override
+ public void onSuccess(String alinkId, Object o) {
+ AppLog.d(TAG, "onSuccess() called with: alinkId = [" + alinkId + "], o = [" + o + "]");
+ showToast("设备状态上报上行成功(code=200),用户可以根据云端返回的data判断是否有不符合的属性上报");
+ }
+
+ @Override
+ public void onError(String alinkId, AError aError) {
+ AppLog.d(TAG, "onError() called with: alinkId = [" + alinkId + "], aError = [" + aError + "]");
+ showToast("设备上报状态失败");
+ }
+ };
+
+ /**
+ * 接收到服务
+ */
+ private static ITResRequestHandler itResRequestHandler=new ITResRequestHandler() {
+ @Override
+ public void onProcess(String identify, Object result, ITResResponseCallback itResResponseCallback) {
+ Log.d(TAG, "onProcess() called with: s = [" + identify + "], o = [" + result + "], itResResponseCallback = [" + itResResponseCallback + "]");
+ showToast("收到异步服务调用 " + identify);
+ if (SERVICE_SET.equals(identify)) {
+ // TODO 用户按照真实设备的接口调用 设置设备的属性
+ // 设置完真实设备属性之后,上报设置完成的属性值
+ // 用户根据实际情况判断属性是否设置成功 这里测试直接返回成功
+ boolean isSetPropertySuccess = true;
+ if (isSetPropertySuccess){
+ if (result instanceof InputParams) {
+ Map data = (Map) ((InputParams) result).getData();
+// data.get()
+
+ // 响应云端 接收数据成功
+ itResResponseCallback.onComplete(identify, null, null);
+ } else {
+ itResResponseCallback.onComplete(identify, null, null);
+ }
+ //updatePropertyValue((Property) mPropertySpinner.getSelectedItem());
+ } else {
+ AError error = new AError();
+ error.setCode(100);
+ error.setMsg("setPropertyFailed.");
+ itResResponseCallback.onComplete(identify, new ErrorInfo(error), null);
+ }
+
+ } else if (SERVICE_GET.equals(identify)){
+ // 初始化的时候将默认值初始化传进来,物模型内部会直接返回云端缓存的值
+
+ }else
+ {
+ OutputParams outputParams = new OutputParams();
+// outputParams.put("op", new ValueWrapper.IntValueWrapper(20));
+ itResResponseCallback.onComplete(identify,null, outputParams);
+ }
+ }
+
+ @Override
+ public void onSuccess(Object o, OutputParams outputParams) {
+ showToast("注册服务成功");
+ }
+
+ @Override
+ public void onFail(Object o, ErrorInfo errorInfo) {
+ showToast("注册服务失败");
+ }
+ };
+ //endregion
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bonait/bnframework/common/iot/manager/IDemoCallback.java b/app/src/main/java/com/bonait/bnframework/common/iot/manager/IDemoCallback.java
new file mode 100644
index 00000000..36a62550
--- /dev/null
+++ b/app/src/main/java/com/bonait/bnframework/common/iot/manager/IDemoCallback.java
@@ -0,0 +1,24 @@
+package com.bonait.bnframework.common.iot.manager;
+
+import com.aliyun.alink.linkkit.api.ILinkKitConnectListener;
+
+/*
+ * Copyright (c) 2014-2016 Alibaba Group. All rights reserved.
+ * License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+public interface IDemoCallback extends ILinkKitConnectListener{
+}
diff --git a/app/src/main/java/com/bonait/bnframework/common/iot/manager/InitManager.java b/app/src/main/java/com/bonait/bnframework/common/iot/manager/InitManager.java
new file mode 100644
index 00000000..04487baf
--- /dev/null
+++ b/app/src/main/java/com/bonait/bnframework/common/iot/manager/InitManager.java
@@ -0,0 +1,330 @@
+package com.bonait.bnframework.common.iot.manager;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.alink.dm.api.DeviceInfo;
+import com.aliyun.alink.dm.api.IoTApiClientConfig;
+import com.aliyun.alink.linkkit.api.ILinkKitConnectListener;
+import com.aliyun.alink.linkkit.api.IoTDMConfig;
+import com.aliyun.alink.linkkit.api.IoTH2Config;
+import com.aliyun.alink.linkkit.api.IoTMqttClientConfig;
+import com.aliyun.alink.linkkit.api.LinkKit;
+import com.aliyun.alink.linkkit.api.LinkKitInitParams;
+import com.aliyun.alink.linksdk.channel.core.persistent.mqtt.MqttConfigure;
+import com.aliyun.alink.linksdk.cmp.api.ConnectSDK;
+import com.aliyun.alink.linksdk.cmp.connect.channel.MqttPublishRequest;
+import com.aliyun.alink.linksdk.cmp.connect.hubapi.HubApiRequest;
+import com.aliyun.alink.linksdk.cmp.core.base.AMessage;
+import com.aliyun.alink.linksdk.cmp.core.base.ARequest;
+import com.aliyun.alink.linksdk.cmp.core.base.AResponse;
+import com.aliyun.alink.linksdk.cmp.core.base.ConnectState;
+import com.aliyun.alink.linksdk.cmp.core.listener.IConnectNotifyListener;
+import com.aliyun.alink.linksdk.cmp.core.listener.IConnectSendListener;
+import com.aliyun.alink.linksdk.tmp.device.payload.ValueWrapper;
+import com.aliyun.alink.linksdk.tools.AError;
+import com.bonait.bnframework.common.iot.AliyunIOTManager;
+import com.bonait.bnframework.common.iot.mode.AppLog;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/*
+ * Copyright (c) 2014-2016 Alibaba Group. All rights reserved.
+ * License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+public class InitManager {
+ private static final String TAG = "InitManager";
+
+ /**
+ * 如果需要动态注册设备获取设备的deviceSecret, 可以参考本接口实现。
+ * 动态注册条件检测:
+ * 1.云端开启该设备动态注册功能;
+ * 2.首先在云端创建 pk,dn;
+ * @param context 上下文
+ * @param productKey 产品类型
+ * @param deviceName 设备名称 需要现在云端创建
+ * @param productSecret 产品密钥
+ * @param listener 密钥请求回调
+ */
+ public static void registerDevice(Context context, String productKey, String deviceName, String productSecret, IConnectSendListener listener) {
+ DeviceInfo myDeviceInfo = new DeviceInfo();
+ myDeviceInfo.productKey = productKey;
+ myDeviceInfo.deviceName = deviceName;
+ myDeviceInfo.productSecret = productSecret;
+ LinkKitInitParams params = new LinkKitInitParams();
+ params.connectConfig = new IoTApiClientConfig();
+ // 如果明确需要切换域名,可以设置 connectConfig 中 domain 的值;
+ params.deviceInfo = myDeviceInfo;
+ HubApiRequest hubApiRequest = new HubApiRequest();
+ hubApiRequest.path = "/auth/register/device";
+ // 调用动态注册接口
+ LinkKit.getInstance().deviceRegister(context, params, hubApiRequest, listener);
+ }
+
+ /**
+ * Android 设备端 SDK 初始化示例代码
+ * @param context 上下文
+ * @param productKey 产品类型
+ * @param deviceName 设备名称
+ * @param deviceSecret 设备密钥
+ * @param productSecret 产品密钥
+ * @param callback 初始化建联结果回调
+ */
+ public static void init(final Context context, String productKey, String deviceName, String deviceSecret, String productSecret, String mqttHost, final IDemoCallback callback) {
+ final LinkKitInitParams params = new LinkKitInitParams();
+
+ //Step1: 构造三元组信息对象
+ DeviceInfo deviceInfo = new DeviceInfo();
+ deviceInfo.productKey = productKey; // 产品类型
+ deviceInfo.deviceName = deviceName; // 设备名称
+ deviceInfo.deviceSecret = deviceSecret; // 设备密钥
+ deviceInfo.productSecret = productSecret; // 产品密钥
+ params.deviceInfo = deviceInfo;
+
+ //Step2: 全局默认域名
+ IoTApiClientConfig userData = new IoTApiClientConfig();
+ params.connectConfig = userData;
+
+ //Step3: 物模型缓存
+ Map propertyValues = new HashMap<>();
+ /**
+ * 物模型的数据会缓存到该字段中. 不可删除或者设置为空, 否则功能会异常
+ * 用户调用物模型上报接口之后,物模型会有相关数据缓存。
+ */
+ params.propertyValues = propertyValues;
+
+ //Step4: mqtt设置
+ /**
+ * 慎用
+ * Mqtt 相关参数设置,包括接入点等信息.具体见deviceinfo文件说明
+ * 域名、产品密钥、认证安全模式等;
+ */
+ IoTMqttClientConfig clientConfig = new IoTMqttClientConfig(productKey, deviceName, deviceSecret);
+ clientConfig.receiveOfflineMsg = false;//cleanSession=1 不接受离线消息
+ //mqtt接入点信息. 详情请参照https://help.aliyun.com/document_detail/147356.htm
+ clientConfig.channelHost = mqttHost;
+ params.mqttClientConfig = clientConfig;
+
+ //Step5: 高阶功能功能配置,默认均为关闭状态
+ IoTDMConfig ioTDMConfig = new IoTDMConfig();
+ // 默认开启物模型功能,开启之后init方法会等到物模型初始化(包含请求云端物模型)完成之后才返回onInitDone
+ ioTDMConfig.enableThingModel = true;
+ // 默认不开启网关功能,开启之后,初始化的时候会初始化网关模块,获取云端网关子设备列表
+ ioTDMConfig.enableGateway = false;
+ // 默认不开启,是否开启日志推送功能
+ ioTDMConfig.enableLogPush = false;
+ params.ioTDMConfig = ioTDMConfig;
+
+ //Step6: 下行消息处理回调设置
+ LinkKit.getInstance().registerOnPushListener(notifyListener);
+
+ //Step7: 一型一密免预注册设置(可选)
+ //对于一型一密免预注册的设备, 设备连云时要用上deviceToken和clientId
+ MqttConfigure.deviceToken = AliyunIOTManager.deviceToken;
+ MqttConfigure.clientId = AliyunIOTManager.clientId;
+
+ //Step8: H2文件上传设置(可选)
+ /**
+ * 如果要用到HTTP2文件上传, 需要用户设置域名
+ */
+ IoTH2Config ioTH2Config = new IoTH2Config();
+ ioTH2Config.clientId = "client-id";
+ ioTH2Config.endPoint = "https://" + productKey + ioTH2Config.endPoint;// 线上环境
+ params.iotH2InitParams = ioTH2Config;
+
+ //Step9: id2相关设置(可选)
+ /**
+ * 如果要用到id2的方式鉴权, 请确认在id2控制台开启相关服务后, 再打开下列代码
+ * 同时,在./app/src/main/res/raw/deviceinfo中,将deviceSecret设置为itls_secret,并填入productSecret
+ */
+ // Id2ItlsSdk.init(context);
+ // if ("itls_secret".equals(deviceSecret)){
+ // clientConfig.channelHost = productKey + ".itls.cn-shanghai.aliyuncs.com:1883";//线上
+ // clientConfig.productSecret = productSecret;
+ // clientConfig.secureMode = 8;
+
+ // 对于对于企业实例, 或者2021年07月30日之后(含当日)开通的物联网平台服务下公共实例的客户,需要通过如下方式指定实例id(格式如iot-xxxxxx)
+ // MqttConfigure.extraMqttClientIdItems=",instanceId=" + "${实例id}";
+ // }
+
+ /**
+ * 设备初始化建联
+ * onError 初始化建联失败,如果因网络问题导致初始化失败,需要用户重试初始化
+ * onInitDone 初始化成功
+ */
+ LinkKit.getInstance().init(context, params, new ILinkKitConnectListener() {
+ @Override
+ public void onError(AError error) {
+ AppLog.d(TAG, "onError() called with: error = [" + getAErrorString(error) + "]");
+ callback.onError(error);
+ }
+
+ @Override
+ public void onInitDone(Object data) {
+ AppLog.d(TAG, "onInitDone() called with: data = [" + data + "]");
+ callback.onInitDone(data);
+ }
+ });
+ }
+
+ /**
+ * 下行监听器,云端 MQTT 下行数据都会通过这里回调
+ */
+ private static IConnectNotifyListener notifyListener = new IConnectNotifyListener() {
+ /**
+ * onNotify 会触发的前提是 shouldHandle 没有指定不处理这个topic
+ * @param connectId 连接类型,这里判断是否长链 connectId == ConnectSDK.getInstance().getPersistentConnectId()
+ * @param topic 下行的topic
+ * @param aMessage 下行的数据内容
+ */
+ @Override
+ public void onNotify(String connectId, String topic, AMessage aMessage) {
+ String data = new String((byte[]) aMessage.data);
+ // 服务端返回数据示例 data = {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"}
+ AppLog.d(TAG, "onNotify() called with: connectId = [" + connectId + "], topic = [" + topic + "], aMessage = [" + data + "]");
+
+ if (ConnectSDK.getInstance().getPersistentConnectId().equals(connectId) && !TextUtils.isEmpty(topic) &&
+ topic.startsWith("/ext/rrpc/")) {
+ ToastUtils.showToast("收到云端自定义RRPC下行:topic=" + topic + ",data=" + data);
+ //示例 topic=/ext/rrpc/1138654706478941696//a1ExY4afKY1/testDevice/user/get
+ //AppLog.d(TAG, "receice Message=" + new String((byte[]) aMessage.data));
+ // 服务端返回数据示例 {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"}
+ MqttPublishRequest request = new MqttPublishRequest();
+ request.isRPC = false;
+ request.topic = topic;
+ String[] array = topic.split("/");
+ String resId = array[3];
+ request.msgId = resId;
+
+ String alinkdId = null;
+
+ try{
+ JSONObject jsonObject = JSONObject.parseObject(data);
+ alinkdId = jsonObject.getString("id");
+ }catch (Exception e){
+ AppLog.e(TAG,"parse alinkId failed, exit");
+ return;
+ }
+
+ // TODO 用户根据实际情况填写 仅做参考
+ request.payloadObj = "{\"id\":\"" + alinkdId + "\", \"code\":\"200\"" + ",\"data\":{\"aa\":1} }";
+ LinkKit.getInstance().publish(request, new IConnectSendListener() {
+ @Override
+ public void onResponse(ARequest aRequest, AResponse aResponse) {
+ // 响应成功
+ // ToastUtils.showToast("云端系统RRPC下行响应成功");
+ }
+
+ @Override
+ public void onFailure(ARequest aRequest, AError aError) {
+ // 响应失败
+ // ToastUtils.showToast("云端系统RRPC下行响应失败");
+ }
+ });
+ } else if (ConnectSDK.getInstance().getPersistentConnectId().equals(connectId) && !TextUtils.isEmpty(topic) &&
+ topic.startsWith("/sys/" + AliyunIOTManager.productKey + "/" + AliyunIOTManager.deviceName + "/rrpc/request/")) {
+ ToastUtils.showToast("收到云端系统RRPC下行:topic=" + topic + ",data=" + data);
+ // AppLog.d(TAG, "receice Message=" + new String((byte[]) aMessage.data));
+ // 服务端返回数据示例 {"method":"thing.service.test_service","id":"123374967","params":{"vv":60},"version":"1.0.0"}
+ MqttPublishRequest request = new MqttPublishRequest();
+ // 支持 0 和 1, 默认0
+ // request.qos = 0;
+ request.isRPC = false;
+ request.topic = topic.replace("request", "response");
+ String[] array = topic.split("/");
+ String resId = array[6];
+ request.msgId = resId;
+ // TODO 用户根据实际情况填写 仅做参考
+ request.payloadObj = "{\"id\":\"" + resId + "\", \"code\":\"200\"" + ",\"data\":{} }";
+
+ LinkKit.getInstance().publish(request, new IConnectSendListener() {
+ @Override
+ public void onResponse(ARequest aRequest, AResponse aResponse) {
+ // ToastUtils.showToast("云端系统RRPC下行响应成功");
+ }
+
+ @Override
+ public void onFailure(ARequest aRequest, AError aError) {
+ // ToastUtils.showToast("云端系统RRPC下行响应失败");
+ }
+ });
+ } else if (ConnectSDK.getInstance().getPersistentConnectId().equals(connectId) && !TextUtils.isEmpty(topic) &&
+ topic.startsWith("/sys/" + AliyunIOTManager.productKey + "/" + AliyunIOTManager.deviceName + "/broadcast/request/")) {
+ /**
+ * topic 格式:/sys/${pk}/${dn}/broadcast/request/+
+ * 无需订阅,云端免订阅,默认无需业务进行ack,但是也支持用户云端和设备端约定业务ack
+ * 示例:/sys/a14NQ5RLiZA/android_lp_test1/broadcast/request/1229336863924294656
+ * 注意:触发端数据需要进行Base64编码,否则会出现端上乱码,
+ * 如云端: org.apache.commons.codec.binary.Base64.encodeBase64String("broadcastContent".getBytes())
+ */
+ //
+ ToastUtils.showToast("收到云端批量广播下行:topic=" + topic + ",data=" + data);
+ //TODO 根据批量广播做业务逻辑处理
+
+ } else if (ConnectSDK.getInstance().getPersistentConnectId().equals(connectId) && !TextUtils.isEmpty(topic) &&
+ topic.startsWith("/broadcast/" + AliyunIOTManager.productKey )) {
+ //
+ /**
+ * topic 需要用户自己订阅才能收到,topic 格式:/broadcast/${pk}/${自定义action},需要和云端发送topic一致
+ * 示例:/broadcast/a14NQ5RLiZA/oldBroadcast
+ * 注意:触发端数据需要进行Base64编码,否则会出现端上乱码,
+ * 如云端: org.apache.commons.codec.binary.Base64.encodeBase64String("broadcastContent".getBytes())
+ */
+ ToastUtils.showToast("收到云端广播下行:topic=" + topic + ",data=" + data);
+ //TODO 根据广播做业务逻辑处理
+ } else {
+ ToastUtils.showToast("收到云端下行:topic=" + topic + ",data=" + data);
+ /**
+ * TODO
+ * 根据订阅的具体 topic 做业务处理
+ */
+ }
+ }
+
+ /**
+ * @param connectId 连接类型,这里判断是否长链 connectId == ConnectSDK.getInstance().getPersistentConnectId()
+ * @param topic 下行topic
+ * @return 是否要处理这个topic,如果为true,则会回调到onNotify;如果为false,onNotify不会回调这个topic相关的数据。建议默认为true。
+ */
+ @Override
+ public boolean shouldHandle(String connectId, String topic) {
+ return true;
+ }
+
+ /**
+ * @param connectId 连接类型,这里判断是否长链 connectId == ConnectSDK.getInstance().getPersistentConnectId()
+ * @param connectState {@link ConnectState}
+ * CONNECTED, 连接成功
+ * DISCONNECTED, 已断链
+ * CONNECTING, 连接中
+ * CONNECTFAIL; 连接失败
+ */
+ @Override
+ public void onConnectStateChange(String connectId, ConnectState connectState) {
+ AppLog.d(TAG, "onConnectStateChange() called with: connectId = [" + connectId + "], connectState = [" + connectState + "]");
+ }
+ };
+
+ public static String getAErrorString(AError error) {
+ if (error == null) {
+ return null;
+ }
+ return JSONObject.toJSONString(error);
+ }
+}
diff --git a/app/src/main/java/com/bonait/bnframework/common/iot/manager/MD5Util.java b/app/src/main/java/com/bonait/bnframework/common/iot/manager/MD5Util.java
new file mode 100644
index 00000000..e6268d3f
--- /dev/null
+++ b/app/src/main/java/com/bonait/bnframework/common/iot/manager/MD5Util.java
@@ -0,0 +1,71 @@
+package com.bonait.bnframework.common.iot.manager;
+
+import android.text.TextUtils;
+
+import com.bonait.bnframework.common.iot.mode.AppLog;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+
+public class MD5Util {
+ private static final String TAG = "MD5Util";
+
+ public static String getFileMd5(String filepath) {
+ if (TextUtils.isEmpty(filepath)) {
+ AppLog.e(TAG, "getMd5ByFile filepath=null.");
+ return null;
+ }
+
+ File file = null;
+ try {
+ file = new File(filepath);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ if (file == null) {
+ AppLog.e(TAG, "getMd5ByFile file not exist.");
+ return null;
+ }
+ InputStream inputStream = null;
+ MessageDigest md5 = null;
+
+ byte[] buffer = new byte[1024 * 8];
+ int readCount = 0;
+
+ try {
+ inputStream = new FileInputStream(file);
+ md5 = MessageDigest.getInstance("MD5");
+ while ((readCount = inputStream.read(buffer)) > 0) {
+ md5.update(buffer, 0, readCount);
+ }
+ return hexString(md5.digest());
+ } catch (Exception e) {
+ System.out.println("error");
+ return null;
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static String hexString(byte[] md5Bytes) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < md5Bytes.length; i++) {
+ int val = ((int) md5Bytes[i]) & 0xff;
+ if (val < 16) {
+ sb.append("0");
+ }
+ sb.append(Integer.toHexString(val));
+ }
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/bonait/bnframework/common/iot/manager/ToastUtils.java b/app/src/main/java/com/bonait/bnframework/common/iot/manager/ToastUtils.java
new file mode 100644
index 00000000..1c1cf351
--- /dev/null
+++ b/app/src/main/java/com/bonait/bnframework/common/iot/manager/ToastUtils.java
@@ -0,0 +1,25 @@
+package com.bonait.bnframework.common.iot.manager;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import com.aliyun.alink.linksdk.tools.ThreadTools;
+import com.bonait.bnframework.MainApplication;
+import com.bonait.bnframework.common.iot.mode.AppLog;
+
+public class ToastUtils {
+
+ public static void showToast(String message) {
+ AppLog.e("客户端接收阿里云数据",message);
+ showToast(message, MainApplication.getContext());
+ }
+
+ private static void showToast(final String message, final Context context) {
+ ThreadTools.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/bonait/bnframework/common/iot/mode/AppLog.java b/app/src/main/java/com/bonait/bnframework/common/iot/mode/AppLog.java
new file mode 100644
index 00000000..bfa8f599
--- /dev/null
+++ b/app/src/main/java/com/bonait/bnframework/common/iot/mode/AppLog.java
@@ -0,0 +1,56 @@
+package com.bonait.bnframework.common.iot.mode;
+
+import android.util.Log;
+
+import com.aliyun.alink.linksdk.tools.log.HLoggerFactory;
+import com.aliyun.alink.linksdk.tools.log.ILogger;
+
+
+public class AppLog {
+ private static final String PRE_TAG = "LK--";
+ private static ILogger logger = new HLoggerFactory().getInstance(PRE_TAG);
+
+ public static final int ASSERT = Log.ASSERT;
+ public static final int DEBUG = Log.DEBUG;
+ public static final int ERROR = Log.ERROR;
+ public static final int INFO = Log.INFO;
+ public static final int VERBOSE = Log.VERBOSE;
+ public static final int WARN = Log.WARN;
+
+ static public void d(String tag, String msg) {
+ logger.d(tag, getFilterString(msg));
+ }
+
+ static public void i(String tag, String msg) {
+ logger.i(tag, getFilterString(msg));
+ }
+
+ static public void w(String tag, String msg) {
+ logger.w(tag, getFilterString(msg));
+ }
+
+ static public void e(String tag, String msg) {
+ logger.e(tag, getFilterString(msg));
+ }
+
+ static public void e(String tag, String where, Exception ex) {
+ if (null != ex) {
+ logger.e(tag, getFilterString(where) + " EXCEPTION: " + ex.getMessage());
+ ex.printStackTrace();
+ } else {
+ logger.e(tag, getFilterString(where) + " EXCEPTION: unknown");
+ }
+ }
+
+ public static void llog(byte priority, String tag, String msg) {
+ com.aliyun.alink.linksdk.tools.ALog.llog(priority, PRE_TAG + tag, getFilterString(msg));
+ }
+
+ public static void setLevel(byte level) {
+ com.aliyun.alink.linksdk.tools.ALog.setLevel(level);
+ }
+
+ private static String getFilterString(String msg) {
+ return msg;
+ }
+}
diff --git a/app/src/main/java/com/bonait/bnframework/common/iot/mode/DeviceInfoData.java b/app/src/main/java/com/bonait/bnframework/common/iot/mode/DeviceInfoData.java
new file mode 100644
index 00000000..f82e7d45
--- /dev/null
+++ b/app/src/main/java/com/bonait/bnframework/common/iot/mode/DeviceInfoData.java
@@ -0,0 +1,58 @@
+package com.bonait.bnframework.common.iot.mode;
+
+import com.aliyun.alink.dm.api.DeviceInfo;
+
+import java.util.List;
+
+/*
+ * Copyright (c) 2014-2016 Alibaba Group. All rights reserved.
+ * License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+public class DeviceInfoData extends DeviceInfo {
+
+ /**
+ * 与网关关联的子设备信息
+ * 后续网关测试demo 会 添加子设备 删除子设备 建立 topo关系 子设备上下线等
+ */
+ public List subDevice = null;
+ public String password = null;
+ public String username = null;
+ public String clientId = null;
+
+ //旧公共分区, pk.region; 无instanceId
+ //新公共实例, 企业实例,下述都有
+ //todo 重点说明一下
+ public String mqttHost = null;
+ public String instanceId = null;
+
+ /**
+ * 动态注册类型
+ */
+ public String registerType = null;
+ public String deviceToken = null;
+
+ @Override
+ public String toString() {
+ return "DeviceInfoData{" +
+ "subDevice=" + subDevice +
+ ", password='" + password + '\'' +
+ ", username='" + username + '\'' +
+ ", clientId='" + clientId + '\'' +
+ ", deviceToken='" + deviceToken + '\'' +
+ '}';
+ }
+}
diff --git a/app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigation2Activity.java b/app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigation2Activity.java
index 38813b3c..008bbc6d 100644
--- a/app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigation2Activity.java
+++ b/app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigation2Activity.java
@@ -4,6 +4,7 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import com.bonait.bnframework.business.ConfigData;
+import com.bonait.bnframework.common.iot.AliyunIOTManager;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import androidx.viewpager.widget.ViewPager;
import android.view.KeyEvent;
@@ -54,6 +55,7 @@ public class BottomNavigation2Activity extends BaseActivity {
@Override
protected void onDestroy() {
ConfigData.getInstance().ColsePLC();
+ //AliyunIOTManager.getInstance().CloseDev();
super.onDestroy();
}
@@ -159,5 +161,7 @@ public class BottomNavigation2Activity extends BaseActivity {
ConfigData.getInstance().ToggleEnvironment();
//2.初始化PLC
ConfigData.getInstance().ConnectPLC();
+ //初始化阿里云连接
+ //AliyunIOTManager.getInstance().OpenDev(this);
}
}
diff --git a/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home1Fragment.java b/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home1Fragment.java
index 917c8649..5b8ef7b4 100644
--- a/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home1Fragment.java
+++ b/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home1Fragment.java
@@ -23,6 +23,8 @@ import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
+import com.aliyun.alink.linksdk.tmp.devicemodel.Event;
+import com.aliyun.alink.linksdk.tmp.devicemodel.Property;
import com.bonait.bnframework.R;
import com.bonait.bnframework.business.ExecuteTheRecipe;
import com.bonait.bnframework.common.base.BaseFragment;
@@ -36,6 +38,7 @@ import com.bonait.bnframework.common.helper.ByteHelper;
import com.bonait.bnframework.common.helper.I.IThread;
import com.bonait.bnframework.common.helper.I.IWriteCallBack;
import com.bonait.bnframework.common.helper.I.MyClickListener;
+import com.bonait.bnframework.common.helper.Json;
import com.bonait.bnframework.common.helper.MessageLog;
import com.bonait.bnframework.common.helper.ThreadManager;
import com.bonait.bnframework.common.modbus.ModbusTcpServer;
@@ -52,6 +55,7 @@ import com.capton.colorfulprogressbar.ColorfulProgressbar;
import com.litao.slider.NiftySlider;
import com.litao.slider.SliderEffect;
import com.litao.slider.Utils;
+import com.lzy.okgo.OkGo;
import com.orhanobut.logger.Logger;
import com.qmuiteam.qmui.widget.QMUITopBar;
import com.qmuiteam.qmui.widget.dialog.QMUIDialog;
@@ -642,6 +646,8 @@ public class Home1Fragment extends BaseFragment {
if (!IsMake(true)) { return; }
ToastUtils.info("点击按钮:急停");
ExecuteTheRecipe.WritePLC("停止", true, null);
+
+
break;
}
}
diff --git a/app/src/main/res/raw/deviceinfo b/app/src/main/res/raw/deviceinfo
new file mode 100644
index 00000000..40445589
--- /dev/null
+++ b/app/src/main/res/raw/deviceinfo
@@ -0,0 +1,70 @@
+{
+ /**
+ * 物联网平台为产品颁发的全局唯一标识符
+ */
+ "productKey": "grgpECHSL7q",
+
+ /**
+ * 设备在产品内的唯一标识符
+ */
+ "deviceName": "fyfcs",
+
+ /**
+ * 产品秘钥,一型一密(预注册/免预注册)的情况下需要填入,否则为空
+ */
+ "productSecret": "k5etVQigsmaSxz9r",
+
+ /**
+ * 物联网平台为设备颁发的设备密钥,用于认证加密. 一机一密情况下需要填入, 否则为空
+ */
+ "deviceSecret": "1974cc749f4b10aa2f48a01e2f316ea6",
+
+ /**
+ * 如果productSecret不为空,但deviceSecret为空,表明设备采用一型一密方式进行认证
+ * registerType表示一型一密的类型: 输入空字符串"", 表示预注册; 输入regnwl, 表示免预注册
+ * 默认(不填)为预注册.
+ */
+ "registerType": "",
+
+ /**
+ * 设置实例的实例id.
+ * 使用公共/企业实例接入的客户, 如果实例详情有实例的id, 需要将该实例id填入.默认为空.
+ * 格式如 iot-0****l
+ */
+ "instanceId": "iot-06z00g9pf3kwtxp",
+
+ /**
+ * 对于企业实例, 或者2021年07月30日之后(含当日)开通的物联网平台服务下公共实例, 请使用带实例id的url接入,
+ * 使用公共/企业实例接入的客户, 如果实例详情中带有接入点信息, 也可以用该接入点接入. 实例接入点的参考格式如:
+ * "{instanceid}.mqtt.iothub.aliyuncs.com:443"
+ *
+ * 对于2021年07月30日之前(不含当日)开通的物联网平台服务下公共实例,
+ * 实例接入点的格式如: "{YourProductKey}.iot-as-mqtt.{region}.aliyuncs.com:443" ,
+ * region默认cn-shanghai, 如果用户的region属于上海, 请不要再设置. 否则, 请在如下域名中选择
+ * | ${YourProductKey}.iot-as-mqtt.cn-shanghai.aliyuncs.com | Shanghai | 443
+ * | ${YourProductKey}.iot-as-mqtt.ap-southeast-1.aliyuncs.com | Singapore | 443
+ * | ${YourProductKey}.iot-as-mqtt.ap-northeast-1.aliyuncs.com | Japan | 443
+ * | ${YourProductKey}.iot-as-mqtt.us-west-1.aliyuncs.com | West-US | 443
+ * | ${YourProductKey}.iot-as-mqtt.eu-central-1.aliyuncs.com | EU | 443
+ * 注:北京/深圳region的用户,请参照上文的企业实例填入接入点,不要用本段提到的带region信息的接入点
+ *
+ * 详情请参照https://help.aliyun.com/document_detail/147356.htm
+ */
+ "mqttHost": "",
+
+ /**
+ * 网关/子设备连云情况下需要填写.默认为空.
+ */
+ "subDevice": [
+ {
+ "productKey": "",
+ "productSecret": "",
+ "deviceName": ""
+ },
+ {
+ "productKey": "",
+ "productSecret": "",
+ "deviceName": ""
+ }
+ ]
+}
diff --git a/build.gradle b/build.gradle
index 183319a8..212da901 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,9 @@ buildscript {
repositories {
google()
jcenter()
-
+ maven {
+ url "https://maven.aliyun.com/nexus/content/repositories/releases/"
+ }
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
@@ -21,7 +23,9 @@ allprojects {
google()
jcenter()
maven { url "https://jitpack.io" }
-
+ maven {
+ url "https://maven.aliyun.com/nexus/content/repositories/releases/"
+ }
}
}