Browse Source

透传设备接入完善

xiwa 2 years ago
parent
commit
e67129dec7
18 changed files with 276 additions and 36 deletions
  1. BIN
      data/components/6c095554-35e7-4e9d-a8d2-bb919e9479f4/iot-emqx-component-0.4.2-SNAPSHOT.jar
  2. BIN
      data/components/eabb131d-8fd1-43a8-88d9-a198abfd3d42/iot-mqtt-component-0.4.2-SNAPSHOT.jar
  3. 1 1
      data/converters/6260396d67aced2696184053/converter.js
  4. 14 8
      data/init/thingModel.json
  5. 49 2
      iot-components/iot-emqx-component/src/main/java/cc/iotkit/comp/emqx/JsScripter.java
  6. 14 2
      iot-components/iot-emqx-component/src/main/java/cc/iotkit/comp/emqx/LuaScripter.java
  7. 23 8
      iot-components/iot-emqx-component/src/main/java/cc/iotkit/comp/emqx/TransparentConverter.java
  8. 1 1
      iot-components/iot-emqx-component/src/main/java/cc/iotkit/comp/emqx/TransparentMsg.java
  9. 3 0
      iot-components/iot-mqtt-component/src/main/java/cc/iotkit/comp/mqtt/IScripter.java
  10. 49 2
      iot-components/iot-mqtt-component/src/main/java/cc/iotkit/comp/mqtt/JsScripter.java
  11. 14 2
      iot-components/iot-mqtt-component/src/main/java/cc/iotkit/comp/mqtt/LuaScripter.java
  12. 23 8
      iot-components/iot-mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentConverter.java
  13. 1 1
      iot-components/iot-mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentMsg.java
  14. 1 0
      iot-data/iot-data-cache/src/main/java/cc/iotkit/data/config/CacheConfig.java
  15. 4 0
      iot-data/iot-model/src/main/java/cc/iotkit/model/product/ProductModel.java
  16. 1 0
      iot-standalone/src/main/java/cc/iotkit/manager/controller/ProductController.java
  17. 76 0
      iot-test-tool/iot-test-mqtt/src/main/java/cc/iotkit/test/mqtt/example/TransparentTest.java
  18. 2 1
      iot-test-tool/iot-test-mqtt/src/main/java/cc/iotkit/test/mqtt/service/MessageHandler.java

BIN
data/components/6c095554-35e7-4e9d-a8d2-bb919e9479f4/iot-emqx-component-0.4.2-SNAPSHOT.jar


BIN
data/components/eabb131d-8fd1-43a8-88d9-a198abfd3d42/iot-mqtt-component-0.4.2-SNAPSHOT.jar


+ 1 - 1
data/converters/6260396d67aced2696184053/converter.js

@@ -115,7 +115,7 @@ this.encode = function (service,device) {
 	var rst=component.transparentEncode(service,device);
 	topic="/sys/"+rst.productKey+"/"+rst.deviceName+"/c/service/rawSend";
 	params.model=rst.content.model;
-	params.mac=rst.content.mac;
+	params.deviceName=rst.content.deviceName;
 	params.data=rst.content.data;
 	
 	return {

+ 14 - 8
data/init/thingModel.json

@@ -104,17 +104,19 @@
                   "length": "10240"
                 }
               },
-              "name": "数据"
+              "name": "数据",
+              "required": false
             },
             {
-              "identifier": "mac",
+              "identifier": "deviceName",
               "dataType": {
                 "type": "text",
                 "specs": {
                   "length": "128"
                 }
               },
-              "name": "设备mac"
+              "name": "设备唯一码",
+              "required": false
             },
             {
               "identifier": "model",
@@ -124,7 +126,8 @@
                   "length": "128"
                 }
               },
-              "name": "设备型号"
+              "name": "设备型号",
+              "required": false
             }
           ],
           "outputData": [],
@@ -143,17 +146,19 @@
                   "length": "10240"
                 }
               },
-              "name": "数据"
+              "name": "数据",
+              "required": false
             },
             {
-              "identifier": "mac",
+              "identifier": "deviceName",
               "dataType": {
                 "type": "text",
                 "specs": {
                   "length": "128"
                 }
               },
-              "name": "设备mac"
+              "name": "设备唯一码",
+              "required": false
             },
             {
               "identifier": "model",
@@ -163,7 +168,8 @@
                   "length": "128"
                 }
               },
-              "name": "设备型号"
+              "name": "设备型号",
+              "required": false
             }
           ],
           "name": "透传上报"

+ 49 - 2
iot-components/iot-emqx-component/src/main/java/cc/iotkit/comp/emqx/JsScripter.java

@@ -10,19 +10,66 @@
 package cc.iotkit.comp.emqx;
 
 import cc.iotkit.common.thing.ThingService;
+import cc.iotkit.common.utils.JsonUtil;
 import cc.iotkit.model.device.message.ThingModelMessage;
+import cc.iotkit.model.product.ProductModel;
+import jdk.nashorn.api.scripting.NashornScriptEngine;
+import jdk.nashorn.api.scripting.ScriptObjectMirror;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.beanutils.BeanUtils;
 
+import javax.script.ScriptEngineManager;
+import java.util.Map;
+
+@Slf4j
+@Data
 public class JsScripter implements IScripter {
 
+    private ProductModel model;
+
+    private final NashornScriptEngine engine = (NashornScriptEngine) (new ScriptEngineManager()).getEngineByName("nashorn");
+
+    private Object scriptObj;
+
+    public JsScripter(ProductModel model) {
+        this.model = model;
+    }
+
     @Override
     public void setScript(String script) {
+        try {
+            scriptObj = engine.eval(String.format("new (function () {\n%s})()", script));
+        } catch (Throwable e) {
+            throw new RuntimeException("init script error", e);
+        }
     }
 
     public ThingModelMessage decode(TransparentMsg msg) {
-        return null;
+        try {
+            ScriptObjectMirror result = (ScriptObjectMirror) engine.invokeMethod(scriptObj, "decode", msg);
+            ThingModelMessage message = new ThingModelMessage();
+            BeanUtils.populate(message, result);
+            return message;
+        } catch (Throwable e) {
+            log.error("invoke decode script error", e);
+            return null;
+        }
     }
 
     public TransparentMsg encode(ThingService<?> service) {
-        return null;
+        try {
+            ScriptObjectMirror result = (ScriptObjectMirror) engine.invokeMethod(scriptObj, "encode", service);
+            Map map = (Map) JsonUtil.toObject(result);
+            TransparentMsg message = new TransparentMsg();
+            BeanUtils.populate(message, map);
+            message.setProductKey(model.getProductKey());
+            message.setModel(model.getModel());
+            message.setDeviceName(service.getDeviceName());
+            return message;
+        } catch (Throwable e) {
+            log.error("invoke encode script error", e);
+            return null;
+        }
     }
 }

+ 14 - 2
iot-components/iot-emqx-component/src/main/java/cc/iotkit/comp/emqx/LuaScripter.java

@@ -11,6 +11,8 @@ package cc.iotkit.comp.emqx;
 
 import cc.iotkit.common.thing.ThingService;
 import cc.iotkit.model.device.message.ThingModelMessage;
+import cc.iotkit.model.product.ProductModel;
+import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.beanutils.BeanUtils;
 import org.luaj.vm2.LuaTable;
@@ -27,14 +29,21 @@ import java.util.List;
 import java.util.Map;
 
 @Slf4j
+@Data
 public class LuaScripter implements IScripter {
 
+    private ProductModel model;
+
     private final LuaScriptEngine engine = (LuaScriptEngine) (
             new ScriptEngineManager().getEngineByName("luaj"));
 
     private LuaValue decoder;
     private LuaValue encoder;
 
+    public LuaScripter(ProductModel model) {
+        this.model = model;
+    }
+
     @Override
     public void setScript(String script) {
         try {
@@ -52,14 +61,14 @@ public class LuaScripter implements IScripter {
         try {
             LuaTable table = new LuaTable();
             table.set("model", msg.getModel());
-            table.set("mac", msg.getMac());
+            table.set("deviceName", msg.getDeviceName());
             table.set("data", msg.getData());
             Map result = (Map) parse(decoder.call(table));
             ThingModelMessage modelMessage = new ThingModelMessage();
             BeanUtils.populate(modelMessage, result);
 
             modelMessage.setProductKey(msg.getProductKey());
-            modelMessage.setDeviceName(msg.getMac());
+            modelMessage.setDeviceName(msg.getDeviceName());
             return modelMessage;
         } catch (Throwable e) {
             log.error("execute decode script error", e);
@@ -85,6 +94,9 @@ public class LuaScripter implements IScripter {
             Map map = (Map) parse(result);
             TransparentMsg message = new TransparentMsg();
             BeanUtils.populate(message, map);
+            message.setProductKey(model.getProductKey());
+            message.setModel(model.getModel());
+            message.setDeviceName(service.getDeviceName());
             return message;
         } catch (Throwable e) {
             log.error("execute encode script error", e);

+ 23 - 8
iot-components/iot-emqx-component/src/main/java/cc/iotkit/comp/emqx/TransparentConverter.java

@@ -20,6 +20,7 @@ import cc.iotkit.model.device.DeviceInfo;
 import cc.iotkit.model.device.message.ThingModelMessage;
 import cc.iotkit.model.product.ProductModel;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -37,10 +38,13 @@ public class TransparentConverter {
      * 透传解码
      */
     public ThingModelMessage decode(TransparentMsg msg) {
-        //通过上报消息中的model取得对应的产品
-        String productKey = checkScriptUpdate(msg.getModel());
+        String productKey = msg.getProductKey();
+        productKey = checkScriptUpdate(productKey, msg.getModel());
         msg.setProductKey(productKey);
-        return scripters.get(productKey).decode(msg);
+        ThingModelMessage decodeMsg = scripters.get(productKey).decode(msg);
+        decodeMsg.setProductKey(msg.getProductKey());
+        decodeMsg.setDeviceName(msg.getDeviceName());
+        return decodeMsg;
     }
 
     /**
@@ -48,7 +52,7 @@ public class TransparentConverter {
      */
     public DeviceMessage encode(ThingService<?> service, Device device) {
         String productKey = service.getProductKey();
-        checkScriptUpdate(device.getModel());
+        checkScriptUpdate(productKey, device.getModel());
         TransparentMsg transparentMsg = scripters.get(productKey).encode(service);
         //转换成网关消息
         String deviceName = service.getDeviceName();
@@ -82,8 +86,19 @@ public class TransparentConverter {
     /**
      * 检查产品脚本是否更新
      */
-    private String checkScriptUpdate(String model) {
-        ProductModel productModel = getScript(model);
+    private String checkScriptUpdate(String pk, String model) {
+        ProductModel productModel = null;
+        if (StringUtils.isNotBlank(model)) {
+            productModel = getScript(model);
+        }
+        //指定型号获取不到获取默认型号
+        if (productModel == null && StringUtils.isNotBlank(pk)) {
+            productModel = getScript(ProductModel.getDefaultModel(pk));
+        }
+        if (productModel == null) {
+            throw new RuntimeException("product model script does not exist");
+        }
+
         String productKey = productModel.getProductKey();
         String script = productModel.getScript();
 
@@ -94,9 +109,9 @@ public class TransparentConverter {
 
         String type = productModel.getType();
         if (ProductModel.TYPE_LUA.equals(type)) {
-            scripters.putIfAbsent(productKey, new LuaScripter());
+            scripters.putIfAbsent(productKey, new LuaScripter(productModel));
         } else if (ProductModel.TYPE_JS.equals(type)) {
-            scripters.putIfAbsent(productKey, new JsScripter());
+            scripters.putIfAbsent(productKey, new JsScripter(productModel));
         }
 
         //更新脚本

+ 1 - 1
iot-components/iot-emqx-component/src/main/java/cc/iotkit/comp/emqx/TransparentMsg.java

@@ -23,7 +23,7 @@ public class TransparentMsg {
 
     private String model;
 
-    private String mac;
+    private String deviceName;
 
     private String data;
 

+ 3 - 0
iot-components/iot-mqtt-component/src/main/java/cc/iotkit/comp/mqtt/IScripter.java

@@ -11,9 +11,12 @@ package cc.iotkit.comp.mqtt;
 
 import cc.iotkit.common.thing.ThingService;
 import cc.iotkit.model.device.message.ThingModelMessage;
+import cc.iotkit.model.product.ProductModel;
 
 public interface IScripter {
 
+    void setModel(ProductModel model);
+
     void setScript(String script);
 
     /**

+ 49 - 2
iot-components/iot-mqtt-component/src/main/java/cc/iotkit/comp/mqtt/JsScripter.java

@@ -10,19 +10,66 @@
 package cc.iotkit.comp.mqtt;
 
 import cc.iotkit.common.thing.ThingService;
+import cc.iotkit.common.utils.JsonUtil;
 import cc.iotkit.model.device.message.ThingModelMessage;
+import cc.iotkit.model.product.ProductModel;
+import jdk.nashorn.api.scripting.NashornScriptEngine;
+import jdk.nashorn.api.scripting.ScriptObjectMirror;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.beanutils.BeanUtils;
 
+import javax.script.ScriptEngineManager;
+import java.util.Map;
+
+@Slf4j
+@Data
 public class JsScripter implements IScripter {
 
+    private ProductModel model;
+
+    private final NashornScriptEngine engine = (NashornScriptEngine) (new ScriptEngineManager()).getEngineByName("nashorn");
+
+    private Object scriptObj;
+
+    public JsScripter(ProductModel model) {
+        this.model = model;
+    }
+
     @Override
     public void setScript(String script) {
+        try {
+            scriptObj = engine.eval(String.format("new (function () {\n%s})()", script));
+        } catch (Throwable e) {
+            throw new RuntimeException("init script error", e);
+        }
     }
 
     public ThingModelMessage decode(TransparentMsg msg) {
-        return null;
+        try {
+            ScriptObjectMirror result = (ScriptObjectMirror) engine.invokeMethod(scriptObj, "decode", msg);
+            ThingModelMessage message = new ThingModelMessage();
+            BeanUtils.populate(message, result);
+            return message;
+        } catch (Throwable e) {
+            log.error("invoke decode script error", e);
+            return null;
+        }
     }
 
     public TransparentMsg encode(ThingService<?> service) {
-        return null;
+        try {
+            ScriptObjectMirror result = (ScriptObjectMirror) engine.invokeMethod(scriptObj, "encode", service);
+            Map map = (Map) JsonUtil.toObject(result);
+            TransparentMsg message = new TransparentMsg();
+            BeanUtils.populate(message, map);
+            message.setProductKey(model.getProductKey());
+            message.setModel(model.getModel());
+            message.setDeviceName(service.getDeviceName());
+            return message;
+        } catch (Throwable e) {
+            log.error("invoke encode script error", e);
+            return null;
+        }
     }
 }

+ 14 - 2
iot-components/iot-mqtt-component/src/main/java/cc/iotkit/comp/mqtt/LuaScripter.java

@@ -11,6 +11,8 @@ package cc.iotkit.comp.mqtt;
 
 import cc.iotkit.common.thing.ThingService;
 import cc.iotkit.model.device.message.ThingModelMessage;
+import cc.iotkit.model.product.ProductModel;
+import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.beanutils.BeanUtils;
 import org.luaj.vm2.LuaTable;
@@ -27,14 +29,21 @@ import java.util.List;
 import java.util.Map;
 
 @Slf4j
+@Data
 public class LuaScripter implements IScripter {
 
+    private ProductModel model;
+
     private final LuaScriptEngine engine = (LuaScriptEngine) (
             new ScriptEngineManager().getEngineByName("luaj"));
 
     private LuaValue decoder;
     private LuaValue encoder;
 
+    public LuaScripter(ProductModel model) {
+        this.model = model;
+    }
+
     @Override
     public void setScript(String script) {
         try {
@@ -52,14 +61,14 @@ public class LuaScripter implements IScripter {
         try {
             LuaTable table = new LuaTable();
             table.set("model", msg.getModel());
-            table.set("mac", msg.getMac());
+            table.set("deviceName", msg.getDeviceName());
             table.set("data", msg.getData());
             Map result = (Map) parse(decoder.call(table));
             ThingModelMessage modelMessage = new ThingModelMessage();
             BeanUtils.populate(modelMessage, result);
 
             modelMessage.setProductKey(msg.getProductKey());
-            modelMessage.setDeviceName(msg.getMac());
+            modelMessage.setDeviceName(msg.getDeviceName());
             return modelMessage;
         } catch (Throwable e) {
             log.error("execute decode script error", e);
@@ -85,6 +94,9 @@ public class LuaScripter implements IScripter {
             Map map = (Map) parse(result);
             TransparentMsg message = new TransparentMsg();
             BeanUtils.populate(message, map);
+            message.setProductKey(model.getProductKey());
+            message.setModel(model.getModel());
+            message.setDeviceName(service.getDeviceName());
             return message;
         } catch (Throwable e) {
             log.error("execute encode script error", e);

+ 23 - 8
iot-components/iot-mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentConverter.java

@@ -20,6 +20,7 @@ import cc.iotkit.model.device.DeviceInfo;
 import cc.iotkit.model.device.message.ThingModelMessage;
 import cc.iotkit.model.product.ProductModel;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -37,10 +38,13 @@ public class TransparentConverter {
      * 透传解码
      */
     public ThingModelMessage decode(TransparentMsg msg) {
-        //通过上报消息中的model取得对应的产品
-        String productKey = checkScriptUpdate(msg.getModel());
+        String productKey = msg.getProductKey();
+        productKey = checkScriptUpdate(productKey, msg.getModel());
         msg.setProductKey(productKey);
-        return scripters.get(productKey).decode(msg);
+        ThingModelMessage decodeMsg = scripters.get(productKey).decode(msg);
+        decodeMsg.setProductKey(msg.getProductKey());
+        decodeMsg.setDeviceName(msg.getDeviceName());
+        return decodeMsg;
     }
 
     /**
@@ -48,7 +52,7 @@ public class TransparentConverter {
      */
     public DeviceMessage encode(ThingService<?> service, Device device) {
         String productKey = service.getProductKey();
-        checkScriptUpdate(device.getModel());
+        checkScriptUpdate(productKey, device.getModel());
         TransparentMsg transparentMsg = scripters.get(productKey).encode(service);
         //转换成网关消息
         String deviceName = service.getDeviceName();
@@ -82,8 +86,19 @@ public class TransparentConverter {
     /**
      * 检查产品脚本是否更新
      */
-    private String checkScriptUpdate(String model) {
-        ProductModel productModel = getScript(model);
+    private String checkScriptUpdate(String pk, String model) {
+        ProductModel productModel = null;
+        if (StringUtils.isNotBlank(model)) {
+            productModel = getScript(model);
+        }
+        //指定型号获取不到获取默认型号
+        if (productModel == null && StringUtils.isNotBlank(pk)) {
+            productModel = getScript(ProductModel.getDefaultModel(pk));
+        }
+        if (productModel == null) {
+            throw new RuntimeException("product model script does not exist");
+        }
+
         String productKey = productModel.getProductKey();
         String script = productModel.getScript();
 
@@ -94,9 +109,9 @@ public class TransparentConverter {
 
         String type = productModel.getType();
         if (ProductModel.TYPE_LUA.equals(type)) {
-            scripters.putIfAbsent(productKey, new LuaScripter());
+            scripters.putIfAbsent(productKey, new LuaScripter(productModel));
         } else if (ProductModel.TYPE_JS.equals(type)) {
-            scripters.putIfAbsent(productKey, new JsScripter());
+            scripters.putIfAbsent(productKey, new JsScripter(productModel));
         }
 
         //更新脚本

+ 1 - 1
iot-components/iot-mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentMsg.java

@@ -23,7 +23,7 @@ public class TransparentMsg {
 
     private String model;
 
-    private String mac;
+    private String deviceName;
 
     private String data;
 

+ 1 - 0
iot-data/iot-data-cache/src/main/java/cc/iotkit/data/config/CacheConfig.java

@@ -46,6 +46,7 @@ public class CacheConfig {
                 Constants.CACHE_CATEGORY, config,
                 Constants.CACHE_THING_MODEL, config,
                 Constants.CACHE_SPACE, config,
+                Constants.CACHE_PRODUCT_SCRIPT, config,
                 //统计缓存5分钟
                 Constants.CACHE_DEVICE_STATS, config.entryTtl(Duration.ofMinutes(5))
         );

+ 4 - 0
iot-data/iot-model/src/main/java/cc/iotkit/model/product/ProductModel.java

@@ -49,4 +49,8 @@ public class ProductModel implements Id<String> {
 
     private Long modifyAt;
 
+    public static String getDefaultModel(String pk) {
+        return pk + "_default";
+    }
+
 }

+ 1 - 0
iot-standalone/src/main/java/cc/iotkit/manager/controller/ProductController.java

@@ -57,6 +57,7 @@ public class ProductController {
     @Autowired
     private AliyunConfig aliyunConfig;
     @Autowired
+    @Qualifier("productModelDataCache")
     private IProductModelData productModelData;
     @Autowired
     private IDbStructureData dbStructureData;

+ 76 - 0
iot-test-tool/iot-test-mqtt/src/main/java/cc/iotkit/test/mqtt/example/TransparentTest.java

@@ -0,0 +1,76 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.test.mqtt.example;
+
+import cc.iotkit.test.mqtt.config.Mqtt;
+import cc.iotkit.test.mqtt.model.Request;
+import cc.iotkit.test.mqtt.service.Gateway;
+import cc.iotkit.test.mqtt.service.ReportTask;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * 透传测试
+ */
+@Slf4j
+public class TransparentTest {
+
+    public static void main(String[] args) throws IOException {
+
+
+        if (args.length == 0) {
+            Mqtt.brokerHost = "127.0.0.1";
+//            Mqtt.brokerHost = "120.76.96.206";
+//            Mqtt.brokerHost = "172.16.1.109";
+        } else {
+            Mqtt.brokerHost = args[0];
+        }
+
+        log.info("start gateway ");
+        Gateway gateway = new Gateway("hbtgIA0SuVw9lxjB",
+                "TEST:GW:T0001");
+
+        gateway.addSubDevice("",
+                "TEST_LIGHT_0001",
+                "M1");
+
+        gateway.onDeviceOnline((device) -> {
+            String pk = device.getProductKey();
+
+            //设备上线后添加上报定时任务
+            ReportTask reportTask = new ReportTask(gateway.getClient());
+            reportTask.addTask(String.format("/sys/%s/%s/s/event/rawReport",
+                    pk, device.getDeviceName()),
+                    () -> {
+                        Request request = new Request();
+                        request.setId(UUID.randomUUID().toString());
+                        request.setMethod("thing.event.rawReport");
+                        Map<String, Object> param = new HashMap<>();
+                        param.put("model", "M1");
+                        param.put("deviceName", "TEST_LIGHT_0001");
+                        param.put("data", "111110011");
+                        request.setParams(param);
+                        return request;
+                    });
+            reportTask.start(10);
+        });
+
+        gateway.start();
+
+        System.in.read();
+    }
+}

+ 2 - 1
iot-test-tool/iot-test-mqtt/src/main/java/cc/iotkit/test/mqtt/service/MessageHandler.java

@@ -58,10 +58,11 @@ public class MessageHandler implements Handler<MqttPublishMessage> {
                 if (response.getCode() == 0) {
                     Map<String, Object> data = response.getData();
                     String productKey = data.get("productKey").toString();
+                    String deviceName = data.get("deviceName").toString();
                     if (StringUtils.isBlank(productKey)) {
+                        deviceOnlineListener.accept(new Device(productKey, deviceName, ""));
                         return;
                     }
-                    String deviceName = data.get("deviceName").toString();
 
                     //订阅子设备消息
                     String subTopic = String.format("/sys/%s/%s/c/#", productKey, deviceName);