@@ -7,6 +7,7 @@ | |||||
<option name="testRunner" value="GRADLE" /> | <option name="testRunner" value="GRADLE" /> | ||||
<option name="distributionType" value="DEFAULT_WRAPPED" /> | <option name="distributionType" value="DEFAULT_WRAPPED" /> | ||||
<option name="externalProjectPath" value="$PROJECT_DIR$" /> | <option name="externalProjectPath" value="$PROJECT_DIR$" /> | ||||
<option name="gradleJvm" value="JDK" /> | |||||
<option name="modules"> | <option name="modules"> | ||||
<set> | <set> | ||||
<option value="$PROJECT_DIR$" /> | <option value="$PROJECT_DIR$" /> | ||||
@@ -1,6 +1,7 @@ | |||||
apply plugin: 'com.android.application' | apply plugin: 'com.android.application' | ||||
//apply plugin: 'com.jakewharton.butterknife' | //apply plugin: 'com.jakewharton.butterknife' | ||||
android { | android { | ||||
compileSdk rootProject.ext.compileSdkVersion | compileSdk rootProject.ext.compileSdkVersion | ||||
@@ -117,9 +118,7 @@ dependencies { | |||||
// debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3' | // debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3' | ||||
// implementation files('libs/commons-codec-1.6.jar') | // implementation files('libs/commons-codec-1.6.jar') | ||||
//MQTT | |||||
implementation files('libs\\org.eclipse.paho.android.service-1.1.1.jar') | |||||
implementation files('libs\\org.eclipse.paho.client.mqttv3-1.2.5.jar') | |||||
//Modbus | //Modbus | ||||
implementation 'com.github.licheedev:Modbus4Android:2.0.2' | implementation 'com.github.licheedev:Modbus4Android:2.0.2' | ||||
@@ -147,4 +146,10 @@ dependencies { | |||||
//轻量级sw | //轻量级sw | ||||
implementation 'com.github.zcweng:switch-button:0.0.3@aar' | implementation 'com.github.zcweng:switch-button:0.0.3@aar' | ||||
//阿里云IOT | |||||
implementation ('com.aliyun.alink.linksdk:lp-iot-linkkit:1.7.3.2') | |||||
//MQTT | |||||
// implementation files('libs\\org.eclipse.paho.android.service-1.1.1.jar') | |||||
// implementation files('libs\\org.eclipse.paho.client.mqttv3-1.2.5.jar') | |||||
} | } |
@@ -15,6 +15,9 @@ | |||||
<uses-permission android:name="android.permission.CAMERA" /> | <uses-permission android:name="android.permission.CAMERA" /> | ||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | ||||
<uses-sdk tools:overrideLibrary="com.aliyun.iot.breeze.biz,com.aliyun.iot.breeze.ota, | |||||
com.aliyun.iot.breeze.sdk,com.aliyun.iot.ble,com.aliyun.alink.linksdk.tmp"/> | |||||
<application | <application | ||||
android:name=".MainApplication" | android:name=".MainApplication" | ||||
android:allowBackup="true" | android:allowBackup="true" | ||||
@@ -27,7 +30,8 @@ | |||||
android:supportsRtl="true" | android:supportsRtl="true" | ||||
android:theme="@style/AppTheme" | android:theme="@style/AppTheme" | ||||
tools:ignore="GoogleAppIndexingWarning" | tools:ignore="GoogleAppIndexingWarning" | ||||
tools:node="merge"> | |||||
tools:node="merge" | |||||
tools:replace="android:icon"> | |||||
<activity | <activity | ||||
android:name=".modules.home.fragment.from.fragment.SystemCsPLCFragment" | android:name=".modules.home.fragment.from.fragment.SystemCsPLCFragment" | ||||
android:exported="false" tools:ignore="Instantiatable"/> | android:exported="false" tools:ignore="Instantiatable"/> | ||||
@@ -128,7 +132,7 @@ | |||||
<activity android:name=".modules.home.activity.BottomNavigation2Activity" /> | <activity android:name=".modules.home.activity.BottomNavigation2Activity" /> | ||||
<activity android:name=".modules.home.activity.BottomNavigationActivity" /> | <activity android:name=".modules.home.activity.BottomNavigationActivity" /> | ||||
<service android:name="org.eclipse.paho.android.service.MqttService" /> | |||||
<!-- <service android:name="org.eclipse.paho.android.service.MqttService" />--> | |||||
<receiver | <receiver | ||||
android:name=".common.base.BootReceiver" | android:name=".common.base.BootReceiver" | ||||
@@ -139,6 +143,8 @@ | |||||
<category android:name="android.intent.category.LAUNCHER" /> | <category android:name="android.intent.category.LAUNCHER" /> | ||||
</intent-filter> | </intent-filter> | ||||
</receiver> | </receiver> | ||||
</application> | </application> | ||||
</manifest> | </manifest> |
@@ -93,6 +93,8 @@ public class MainInit { | |||||
InitDBdata(); | InitDBdata(); | ||||
DataBus.getInstance().GetLc();//获取料仓数据 | DataBus.getInstance().GetLc();//获取料仓数据 | ||||
ConfigData.getInstance().LoadingCloud();//加载云端数据 | ConfigData.getInstance().LoadingCloud();//加载云端数据 | ||||
} | } | ||||
//========================================================================// | //========================================================================// | ||||
@@ -0,0 +1,726 @@ | |||||
package com.bonait.bnframework.common.iot; | |||||
import android.content.Context; | |||||
import android.text.TextUtils; | |||||
import android.util.Log; | |||||
import com.alibaba.fastjson.JSONObject; | |||||
import com.aliyun.alink.dm.api.BaseInfo; | |||||
import com.aliyun.alink.dm.api.DeviceInfo; | |||||
import com.aliyun.alink.dm.api.IThing; | |||||
import com.aliyun.alink.linkkit.api.LinkKit; | |||||
import com.aliyun.alink.linksdk.tmp.api.InputParams; | |||||
import com.aliyun.alink.linksdk.tmp.api.OutputParams; | |||||
import com.aliyun.alink.linksdk.tmp.device.payload.ValueWrapper; | |||||
import com.aliyun.alink.linksdk.tmp.devicemodel.Arg; | |||||
import com.aliyun.alink.linksdk.tmp.devicemodel.Event; | |||||
import com.aliyun.alink.linksdk.tmp.devicemodel.Property; | |||||
import com.aliyun.alink.linksdk.tmp.devicemodel.Service; | |||||
import com.aliyun.alink.linksdk.tmp.listener.IPublishResourceListener; | |||||
import com.aliyun.alink.linksdk.tmp.listener.ITResRequestHandler; | |||||
import com.aliyun.alink.linksdk.tmp.listener.ITResResponseCallback; | |||||
import com.aliyun.alink.linksdk.tmp.utils.ErrorInfo; | |||||
import com.aliyun.alink.linksdk.tmp.utils.GsonUtils; | |||||
import com.aliyun.alink.linksdk.tmp.utils.TmpConstant; | |||||
import com.aliyun.alink.linksdk.tools.AError; | |||||
import com.bonait.bnframework.MainApplication; | |||||
import com.bonait.bnframework.R; | |||||
import com.bonait.bnframework.common.iot.manager.IDemoCallback; | |||||
import com.bonait.bnframework.common.iot.manager.InitManager; | |||||
import com.bonait.bnframework.common.iot.mode.AppLog; | |||||
import com.bonait.bnframework.common.iot.mode.DeviceInfoData; | |||||
import com.bonait.bnframework.common.utils.ToastUtils; | |||||
import com.google.gson.Gson; | |||||
import com.google.gson.reflect.TypeToken; | |||||
import java.io.BufferedReader; | |||||
import java.io.IOException; | |||||
import java.io.InputStreamReader; | |||||
import java.util.ArrayList; | |||||
import java.util.HashMap; | |||||
import java.util.IdentityHashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.regex.Pattern; | |||||
public class AliyunIOTManager { | |||||
//region 变量 | |||||
private static final String TAG = "阿里云"; | |||||
/** | |||||
* 判断是否初始化完成 | |||||
* 未初始化完成,所有和云端的长链通信都不通 | |||||
*/ | |||||
public static boolean isInitDone = false; | |||||
public static boolean userDevInfoError = false; | |||||
public static DeviceInfoData mDeviceInfoData = null; | |||||
public static String productKey = null, deviceName = null, deviceSecret = null, productSecret = null, | |||||
password = null, username = null, clientId = null, deviceToken = null, mqttHost = null, instanceId = null; | |||||
private String registerType = null; | |||||
private final static int DEF_VALUE = Integer.MIN_VALUE; | |||||
final static Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$"); | |||||
private Map<String, ValueWrapper> reportData = null; | |||||
private boolean isSubDev = false; | |||||
private final static String SERVICE_SET = "set"; | |||||
private final static String SERVICE_GET = "get"; | |||||
//endregion | |||||
//region 单例 | |||||
private static AliyunIOTManager instance; | |||||
private AliyunIOTManager() { | |||||
reportData =new HashMap<String, ValueWrapper>(); | |||||
// 从 raw 读取指定配置文件 | |||||
String testData = getFromRaw(); | |||||
// 解析数据 | |||||
getDeviceInfoFrom(testData); | |||||
} | |||||
public static synchronized AliyunIOTManager getInstance() { | |||||
if (instance == null) { | |||||
instance = new AliyunIOTManager(); | |||||
} | |||||
return instance; | |||||
} | |||||
//endregion | |||||
//region 公有 | |||||
/** | |||||
* 打开设备 | |||||
*/ | |||||
public void OpenDev(Context context) { | |||||
try { | |||||
if (userDevInfoError) { | |||||
ToastUtils.warning("三元组文件格式不正确,请重新检查格式"); | |||||
} else { | |||||
if (!TextUtils.isEmpty(deviceSecret) || !TextUtils.isEmpty(password) || !TextUtils.isEmpty(deviceToken)) { | |||||
connect(context); | |||||
} else { | |||||
if (!userDevInfoError) { | |||||
ToastUtils.warning("三元组信息无效,请重新填写"); | |||||
} | |||||
userDevInfoError = true; | |||||
} | |||||
} | |||||
} catch (Exception ex) { | |||||
} | |||||
} | |||||
/** | |||||
* 耗时操作,建议放到异步线程 | |||||
* 反初始化同步接口 | |||||
*/ | |||||
public void CloseDev() { | |||||
AppLog.d(TAG, "deinit"); | |||||
isInitDone = false; | |||||
new Thread(new Runnable() { | |||||
@Override | |||||
public void run() { | |||||
// 同步接口 | |||||
LinkKit.getInstance().deinit(); | |||||
ToastUtils.info("关闭IOT成功"); | |||||
} | |||||
}).start(); | |||||
} | |||||
/** | |||||
* 获取设备属性 | |||||
* | |||||
* @return | |||||
*/ | |||||
public List<Property> GetProperty() { | |||||
return LinkKit.getInstance().getDeviceThing().getProperties(); | |||||
} | |||||
/** | |||||
* 上报属性 | |||||
* @param property | |||||
*/ | |||||
public void UploadProperty(Property property, String val) { | |||||
try { | |||||
if (property == null) { | |||||
showToast("选择的property为空"); | |||||
return; | |||||
} | |||||
String value = val; | |||||
if (property == null || value == null || property.getIdentifier() == null) { | |||||
showToast("属性或值为空"); | |||||
return; | |||||
} | |||||
if (property.getDataType() == null) { | |||||
showToast("属性类型为空"); | |||||
return; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_INTEGER.equals(property.getDataType().getType())) { | |||||
int parseData = getInt(value); | |||||
if (parseData != DEF_VALUE) { | |||||
report(property.getIdentifier(), new ValueWrapper.IntValueWrapper(parseData)); | |||||
} else { | |||||
showToast("数据格式不对"); | |||||
} | |||||
return; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_FLOAT.equals(property.getDataType().getType())) { | |||||
Double parseData = getDouble(value); | |||||
if (parseData != null) { | |||||
report(property.getIdentifier(), new ValueWrapper.DoubleValueWrapper(parseData)); | |||||
} else { | |||||
showToast("数据格式不对"); | |||||
} | |||||
return; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_DOUBLE.equals(property.getDataType().getType())) { | |||||
Double parseData = getDouble(value); | |||||
if (parseData != null) { | |||||
report(property.getIdentifier(), new ValueWrapper.DoubleValueWrapper(parseData)); | |||||
} else { | |||||
showToast("数据格式不对"); | |||||
} | |||||
return; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_BOOLEAN.equals(property.getDataType().getType())) { | |||||
int parseData = getInt(value); | |||||
if (parseData == 0 || parseData == 1) { | |||||
report(property.getIdentifier(), new ValueWrapper.BooleanValueWrapper(parseData)); | |||||
} else { | |||||
showToast("数据格式不对"); | |||||
} | |||||
return; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_TEXT.equals(property.getDataType().getType())) { | |||||
report(property.getIdentifier(), new ValueWrapper.StringValueWrapper(value)); | |||||
return; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_DATE.equals(property.getDataType().getType())) { | |||||
report(property.getIdentifier(), new ValueWrapper.DateValueWrapper(value)); | |||||
return; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_ENUM.equalsIgnoreCase(property.getDataType().getType())) { | |||||
report(property.getIdentifier(), new ValueWrapper.EnumValueWrapper(getInt(value))); | |||||
return; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_ARRAY.equalsIgnoreCase(property.getDataType().getType())) { | |||||
ValueWrapper.ArrayValueWrapper arrayValueWrapper = GsonUtils.fromJson(value, new TypeToken<ValueWrapper>() { | |||||
}.getType()); | |||||
report(property.getIdentifier(), arrayValueWrapper); | |||||
return; | |||||
} | |||||
// 结构体数据解析 结构体不支持嵌套结构体和数组 | |||||
if (TmpConstant.TYPE_VALUE_STRUCT.equals(property.getDataType().getType())) { | |||||
try { | |||||
List<Map<String, Object>> specsList = (List<Map<String, Object>>) property.getDataType().getSpecs(); | |||||
if (specsList == null || specsList.size() == 0) { | |||||
showToast("云端创建的struct结构为空,不上传任何值。"); | |||||
return; | |||||
} | |||||
JSONObject dataJson = JSONObject.parseObject(value); | |||||
Map<String, ValueWrapper> dataMap = new HashMap<>(); | |||||
Map<String, Object> specsItem = null; | |||||
for (int i = 0; i < specsList.size(); i++) { | |||||
specsItem = specsList.get(i); | |||||
if (specsItem == null) { | |||||
continue; | |||||
} | |||||
String idKey = (String) specsItem.get("identifier"); | |||||
String dataType = (String) ((Map) specsItem.get("dataType")).get("type"); | |||||
if (idKey != null && dataJson.containsKey(idKey) && dataType != null) { | |||||
ValueWrapper valueItem = null; | |||||
if ("int".equals(dataType)) { | |||||
valueItem = new ValueWrapper.IntValueWrapper(getInt(String.valueOf(dataJson.get(idKey)))); | |||||
} else if ("text".equals(dataType)) { | |||||
valueItem = new ValueWrapper.StringValueWrapper((String) dataJson.get(idKey)); | |||||
} else if ("float".equals(dataType) || "double".equals(dataType)) { | |||||
valueItem = new ValueWrapper.DoubleValueWrapper(getDouble(String.valueOf(dataJson.get(idKey)))); | |||||
} else if ("bool".equals(dataType)) { | |||||
valueItem = new ValueWrapper.BooleanValueWrapper(getInt(String.valueOf(dataJson.get(idKey)))); | |||||
} else if ("date".equals(dataType)) { | |||||
if (isValidInt(String.valueOf(dataJson.get(idKey)))) { | |||||
valueItem = new ValueWrapper.DateValueWrapper(String.valueOf(dataJson.get(idKey))); | |||||
} else { | |||||
showToast("数据格式不对"); | |||||
} | |||||
} else if ("enum".equals(dataType)) { | |||||
valueItem = new ValueWrapper.EnumValueWrapper(getInt(String.valueOf(dataJson.get(idKey)))); | |||||
} else { | |||||
showToast("数据格式不支持"); | |||||
} | |||||
if (valueItem != null) { | |||||
dataMap.put(idKey, valueItem); | |||||
} | |||||
} | |||||
} | |||||
report(property.getIdentifier(), new ValueWrapper.StructValueWrapper(dataMap)); | |||||
} catch (Exception e) { | |||||
showToast("数据格式不正确"); | |||||
} | |||||
return; | |||||
} | |||||
showToast("该类型Demo暂不支持,用户可参照其他类型代码示例开发支持。"); | |||||
} catch (Exception e) { | |||||
showToast("数据格式不对"); | |||||
e.printStackTrace(); | |||||
} | |||||
} | |||||
/** | |||||
* 获取设备服务, | |||||
* | |||||
* @return | |||||
*/ | |||||
public List<Service> GetService() { | |||||
return LinkKit.getInstance().getDeviceThing().getServices(); | |||||
} | |||||
/** | |||||
* 监听服务 | |||||
*/ | |||||
public void MonitorService() | |||||
{ | |||||
List<Service> services= LinkKit.getInstance().getDeviceThing().getServices(); | |||||
for (Service item:services) | |||||
{ | |||||
LinkKit.getInstance().getDeviceThing().thingServiceRegister(item.getName(),itResRequestHandler); | |||||
} | |||||
} | |||||
/** | |||||
* 获取设备事件 | |||||
* | |||||
* @return | |||||
*/ | |||||
public List<Event> GetEvents() { | |||||
return LinkKit.getInstance().getDeviceThing().getEvents(); | |||||
} | |||||
/** | |||||
* 上报事件通知 | |||||
* @param event | |||||
* @param val | |||||
*/ | |||||
public void UploadEvents(Event event, String val) { | |||||
if (event == null) { | |||||
showToast("事件不能为空"); | |||||
return; | |||||
} | |||||
HashMap<String, ValueWrapper> hashMap = new HashMap<>(); | |||||
try { | |||||
String mapEventData = val; | |||||
JSONObject object = JSONObject.parseObject(mapEventData); | |||||
if (object == null){ | |||||
showToast("参数不能为空"); | |||||
return; | |||||
} | |||||
if (event.getOutputData() != null) { | |||||
for (int i = 0; i < event.getOutputData().size(); i++) { | |||||
Arg arg = event.getOutputData().get(i); | |||||
if (arg == null || arg.getDataType() == null || arg.getIdentifier() == null){ | |||||
continue; | |||||
} | |||||
String idnValue = String.valueOf(object.get(arg.getIdentifier())); | |||||
if (idnValue == null || object.get(arg.getIdentifier()) == null){ | |||||
continue; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_INTEGER.equals(arg.getDataType().getType())) { | |||||
int parseData = getInt(idnValue); | |||||
if (parseData != DEF_VALUE) { | |||||
hashMap.put(arg.getIdentifier(), new ValueWrapper.IntValueWrapper(parseData)); | |||||
} else { | |||||
showToast("数据格式不对"); | |||||
break; | |||||
} | |||||
continue; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_FLOAT.equals(arg.getDataType().getType())) { | |||||
Double parseData = getDouble(idnValue); | |||||
if (parseData != null) { | |||||
hashMap.put(arg.getIdentifier(), new ValueWrapper.DoubleValueWrapper(parseData)); | |||||
} else { | |||||
showToast("数据格式不对"); | |||||
break; | |||||
} | |||||
continue; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_DOUBLE.equals(arg.getDataType().getType())) { | |||||
Double parseData = getDouble(idnValue); | |||||
if (parseData != null) { | |||||
hashMap.put(arg.getIdentifier(), new ValueWrapper.DoubleValueWrapper(parseData)); | |||||
} else { | |||||
showToast("数据格式不对"); | |||||
break; | |||||
} | |||||
continue; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_BOOLEAN.equals(arg.getDataType().getType())) { | |||||
int parseData = getInt(idnValue); | |||||
if (parseData == 0 || parseData == 1) { | |||||
hashMap.put(arg.getIdentifier(), new ValueWrapper.BooleanValueWrapper(parseData)); | |||||
} else { | |||||
showToast("数据格式不对"); | |||||
break; | |||||
} | |||||
continue; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_TEXT.equals(arg.getDataType().getType())) { | |||||
hashMap.put(arg.getIdentifier(), new ValueWrapper.StringValueWrapper(idnValue)); | |||||
continue; | |||||
} | |||||
if (TmpConstant.TYPE_VALUE_DATE.equals(arg.getDataType().getType())) { | |||||
hashMap.put(arg.getIdentifier(), new ValueWrapper.DateValueWrapper(idnValue)); | |||||
continue; | |||||
} | |||||
if(TmpConstant.TYPE_VALUE_ENUM.equalsIgnoreCase(arg.getDataType().getType())){ | |||||
hashMap.put(arg.getIdentifier(),new ValueWrapper.EnumValueWrapper(getInt(idnValue))); | |||||
continue; | |||||
} | |||||
if(TmpConstant.TYPE_VALUE_ARRAY.equalsIgnoreCase(arg.getDataType().getType())){ | |||||
ValueWrapper.ArrayValueWrapper arrayValueWrapper = GsonUtils.fromJson(idnValue,new TypeToken<ValueWrapper>(){}.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会处理建联。 | |||||
* <p> | |||||
* onError 初始化失败 | |||||
* onInitDone 初始化成功 | |||||
* <p> | |||||
* 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<String, ValueWrapper> 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<String, ValueWrapper> data = (Map<String, ValueWrapper>) ((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 | |||||
} |
@@ -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{ | |||||
} |
@@ -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<String, ValueWrapper> 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); | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
}); | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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<DeviceInfo> 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 + '\'' + | |||||
'}'; | |||||
} | |||||
} |
@@ -4,6 +4,7 @@ import android.os.Bundle; | |||||
import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||
import com.bonait.bnframework.business.ConfigData; | import com.bonait.bnframework.business.ConfigData; | ||||
import com.bonait.bnframework.common.iot.AliyunIOTManager; | |||||
import com.google.android.material.bottomnavigation.BottomNavigationView; | import com.google.android.material.bottomnavigation.BottomNavigationView; | ||||
import androidx.viewpager.widget.ViewPager; | import androidx.viewpager.widget.ViewPager; | ||||
import android.view.KeyEvent; | import android.view.KeyEvent; | ||||
@@ -54,6 +55,7 @@ public class BottomNavigation2Activity extends BaseActivity { | |||||
@Override | @Override | ||||
protected void onDestroy() { | protected void onDestroy() { | ||||
ConfigData.getInstance().ColsePLC(); | ConfigData.getInstance().ColsePLC(); | ||||
//AliyunIOTManager.getInstance().CloseDev(); | |||||
super.onDestroy(); | super.onDestroy(); | ||||
} | } | ||||
@@ -159,5 +161,7 @@ public class BottomNavigation2Activity extends BaseActivity { | |||||
ConfigData.getInstance().ToggleEnvironment(); | ConfigData.getInstance().ToggleEnvironment(); | ||||
//2.初始化PLC | //2.初始化PLC | ||||
ConfigData.getInstance().ConnectPLC(); | ConfigData.getInstance().ConnectPLC(); | ||||
//初始化阿里云连接 | |||||
//AliyunIOTManager.getInstance().OpenDev(this); | |||||
} | } | ||||
} | } |
@@ -23,6 +23,8 @@ import android.widget.ImageView; | |||||
import android.widget.RelativeLayout; | import android.widget.RelativeLayout; | ||||
import android.widget.SeekBar; | 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.R; | ||||
import com.bonait.bnframework.business.ExecuteTheRecipe; | import com.bonait.bnframework.business.ExecuteTheRecipe; | ||||
import com.bonait.bnframework.common.base.BaseFragment; | 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.IThread; | ||||
import com.bonait.bnframework.common.helper.I.IWriteCallBack; | import com.bonait.bnframework.common.helper.I.IWriteCallBack; | ||||
import com.bonait.bnframework.common.helper.I.MyClickListener; | 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.MessageLog; | ||||
import com.bonait.bnframework.common.helper.ThreadManager; | import com.bonait.bnframework.common.helper.ThreadManager; | ||||
import com.bonait.bnframework.common.modbus.ModbusTcpServer; | 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.NiftySlider; | ||||
import com.litao.slider.SliderEffect; | import com.litao.slider.SliderEffect; | ||||
import com.litao.slider.Utils; | import com.litao.slider.Utils; | ||||
import com.lzy.okgo.OkGo; | |||||
import com.orhanobut.logger.Logger; | import com.orhanobut.logger.Logger; | ||||
import com.qmuiteam.qmui.widget.QMUITopBar; | import com.qmuiteam.qmui.widget.QMUITopBar; | ||||
import com.qmuiteam.qmui.widget.dialog.QMUIDialog; | import com.qmuiteam.qmui.widget.dialog.QMUIDialog; | ||||
@@ -642,6 +646,8 @@ public class Home1Fragment extends BaseFragment { | |||||
if (!IsMake(true)) { return; } | if (!IsMake(true)) { return; } | ||||
ToastUtils.info("点击按钮:急停"); | ToastUtils.info("点击按钮:急停"); | ||||
ExecuteTheRecipe.WritePLC("停止", true, null); | ExecuteTheRecipe.WritePLC("停止", true, null); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
@@ -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": "" | |||||
} | |||||
] | |||||
} |
@@ -5,7 +5,9 @@ buildscript { | |||||
repositories { | repositories { | ||||
google() | google() | ||||
jcenter() | jcenter() | ||||
maven { | |||||
url "https://maven.aliyun.com/nexus/content/repositories/releases/" | |||||
} | |||||
} | } | ||||
dependencies { | dependencies { | ||||
classpath 'com.android.tools.build:gradle:7.4.1' | classpath 'com.android.tools.build:gradle:7.4.1' | ||||
@@ -21,7 +23,9 @@ allprojects { | |||||
google() | google() | ||||
jcenter() | jcenter() | ||||
maven { url "https://jitpack.io" } | maven { url "https://jitpack.io" } | ||||
maven { | |||||
url "https://maven.aliyun.com/nexus/content/repositories/releases/" | |||||
} | |||||
} | } | ||||
} | } | ||||