@@ -7,6 +7,7 @@ | |||
<option name="testRunner" value="GRADLE" /> | |||
<option name="distributionType" value="DEFAULT_WRAPPED" /> | |||
<option name="externalProjectPath" value="$PROJECT_DIR$" /> | |||
<option name="gradleJvm" value="JDK" /> | |||
<option name="modules"> | |||
<set> | |||
<option value="$PROJECT_DIR$" /> | |||
@@ -1,6 +1,7 @@ | |||
apply plugin: 'com.android.application' | |||
//apply plugin: 'com.jakewharton.butterknife' | |||
android { | |||
compileSdk rootProject.ext.compileSdkVersion | |||
@@ -117,9 +118,7 @@ dependencies { | |||
// debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3' | |||
// 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 | |||
implementation 'com.github.licheedev:Modbus4Android:2.0.2' | |||
@@ -147,4 +146,10 @@ dependencies { | |||
//轻量级sw | |||
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.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 | |||
android:name=".MainApplication" | |||
android:allowBackup="true" | |||
@@ -27,7 +30,8 @@ | |||
android:supportsRtl="true" | |||
android:theme="@style/AppTheme" | |||
tools:ignore="GoogleAppIndexingWarning" | |||
tools:node="merge"> | |||
tools:node="merge" | |||
tools:replace="android:icon"> | |||
<activity | |||
android:name=".modules.home.fragment.from.fragment.SystemCsPLCFragment" | |||
android:exported="false" tools:ignore="Instantiatable"/> | |||
@@ -128,7 +132,7 @@ | |||
<activity android:name=".modules.home.activity.BottomNavigation2Activity" /> | |||
<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 | |||
android:name=".common.base.BootReceiver" | |||
@@ -139,6 +143,8 @@ | |||
<category android:name="android.intent.category.LAUNCHER" /> | |||
</intent-filter> | |||
</receiver> | |||
</application> | |||
</manifest> |
@@ -93,6 +93,8 @@ public class MainInit { | |||
InitDBdata(); | |||
DataBus.getInstance().GetLc();//获取料仓数据 | |||
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 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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
@@ -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 { | |||
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/" | |||
} | |||
} | |||
} | |||