瀏覽代碼

实现基于云端lua低代码设备开发

xiwa 3 年之前
父節點
當前提交
5af991f401
共有 31 個文件被更改,包括 654 次插入83 次删除
  1. 1 14
      common/src/main/java/cc/iotkit/common/Constants.java
  2. 15 1
      dao/src/main/java/cc/iotkit/dao/DeviceCache.java
  3. 28 0
      dao/src/main/java/cc/iotkit/dao/ProductCache.java
  4. 16 0
      dao/src/main/java/cc/iotkit/dao/ProductModelRepository.java
  5. 3 0
      dao/src/main/java/cc/iotkit/dao/ProductRepository.java
  6. 22 6
      manager/src/main/java/cc/iotkit/manager/config/CacheConfig.java
  7. 44 4
      manager/src/main/java/cc/iotkit/manager/controller/ProductController.java
  8. 1 1
      manager/src/main/resources/application.yml
  9. 9 0
      model/src/main/java/cc/iotkit/model/product/Product.java
  10. 45 0
      model/src/main/java/cc/iotkit/model/product/ProductModel.java
  11. 6 0
      pom.xml
  12. 5 0
      protocol-gateway/component-server/pom.xml
  13. 5 3
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/ApiTool.java
  14. 26 3
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/DeviceComponentManager.java
  15. 1 1
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/DeviceMessageHandler.java
  16. 76 35
      protocol-gateway/component-server/src/main/java/cc/iotkit/comps/service/DeviceBehaviourService.java
  17. 12 0
      protocol-gateway/converter/src/main/java/cc/iotkit/converter/Device.java
  18. 1 0
      protocol-gateway/converter/src/main/java/cc/iotkit/converter/IConverter.java
  19. 10 1
      protocol-gateway/converter/src/main/java/cc/iotkit/converter/ScriptConverter.java
  20. 0 12
      protocol-gateway/http-biz-component/src/main/java/cc/iotkit/comp/biz/HttpBizComponent.java
  21. 1 0
      protocol-gateway/mqtt-client-simulator/src/main/java/cc/iotkit/simulator/Application.java
  22. 7 1
      protocol-gateway/mqtt-client-simulator/src/main/java/cc/iotkit/simulator/service/Gateway.java
  23. 13 0
      protocol-gateway/mqtt-component/dependency-reduced-pom.xml
  24. 11 0
      protocol-gateway/mqtt-component/pom.xml
  25. 20 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/IScripter.java
  26. 19 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/JsScripter.java
  27. 126 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/LuaScripter.java
  28. 21 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/MqttDeviceComponent.java
  29. 88 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentConverter.java
  30. 21 0
      protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentMsg.java
  31. 1 1
      rule-engine/src/main/java/cc/iotkit/ruleengine/filter/DeviceCondition.java

+ 1 - 14
common/src/main/java/cc/iotkit/common/Constants.java

@@ -18,20 +18,7 @@ public interface Constants {
 
     String APP_DESIGN_CACHE = "app_design_cache";
 
-    /**
-     * 天猫精灵平台
-     */
-    String PLATFORM_ALIGENIE = "aligenie";
-
-    /**
-     * topic前缀第三方接入网关
-     */
-    String TOPIC_PREFIX_GATEWAY = "gateway";
-
-    /**
-     * topic前缀APP
-     */
-    String TOPIC_PREFIX_APP = "app";
+    String PRODUCT_SCRIPT_CACHE = "product_script_cache";
 
     /**
      * 管理员角色

+ 15 - 1
dao/src/main/java/cc/iotkit/dao/DeviceCache.java

@@ -6,14 +6,27 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Repository;
 
+import javax.annotation.PostConstruct;
+
 @Repository
 public class DeviceCache {
 
     @Autowired
     private DeviceRepository deviceRepository;
 
+    private static DeviceCache INSTANCE;
+
+    @PostConstruct
+    public void init() {
+        INSTANCE = this;
+    }
+
+    public static DeviceCache getInstance() {
+        return INSTANCE;
+    }
+
     @Cacheable(value = Constants.DEVICE_CACHE, key = "#pk+'_'+#dn")
-    public DeviceInfo findByProductKeyAndDeviceName(String pk, String dn) {
+    public DeviceInfo getDeviceInfo(String pk, String dn) {
         return deviceRepository.findByProductKeyAndDeviceName(pk, dn);
     }
 
@@ -26,4 +39,5 @@ public class DeviceCache {
     public DeviceInfo get(String deviceId) {
         return deviceRepository.findById(deviceId).orElse(new DeviceInfo());
     }
+
 }

+ 28 - 0
dao/src/main/java/cc/iotkit/dao/ProductCache.java

@@ -2,11 +2,14 @@ package cc.iotkit.dao;
 
 import cc.iotkit.common.Constants;
 import cc.iotkit.model.product.Product;
+import cc.iotkit.model.product.ProductModel;
 import cc.iotkit.model.product.ThingModel;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Repository;
 
+import javax.annotation.PostConstruct;
+
 @Repository
 public class ProductCache {
 
@@ -14,6 +17,19 @@ public class ProductCache {
     private ProductRepository productRepository;
     @Autowired
     private ThingModelRepository thingModelRepository;
+    @Autowired
+    private ProductModelRepository productModelRepository;
+
+    private static ProductCache INSTANCE;
+
+    @PostConstruct
+    public void init() {
+        INSTANCE = this;
+    }
+
+    public static ProductCache getInstance() {
+        return INSTANCE;
+    }
 
     @Cacheable(value = Constants.PRODUCT_CACHE, key = "'pk'+#pk", unless = "#result == null")
     public Product findById(String pk) {
@@ -24,4 +40,16 @@ public class ProductCache {
     public ThingModel getThingModel(String pk) {
         return thingModelRepository.findByProductKey(pk);
     }
+
+    @Cacheable(value = Constants.PRODUCT_SCRIPT_CACHE, key = "'pk'+#pk", unless = "#result == null")
+    public ProductModel getProductScript(String pk) {
+        return productModelRepository.findById(pk).orElse(null);
+    }
+
+    @Cacheable(value = Constants.PRODUCT_SCRIPT_CACHE, key = "'model'+#model", unless = "#result == null")
+    public ProductModel getProductScriptByModel(String model) {
+        return productModelRepository.findByModel(model);
+    }
+
+
 }

+ 16 - 0
dao/src/main/java/cc/iotkit/dao/ProductModelRepository.java

@@ -0,0 +1,16 @@
+package cc.iotkit.dao;
+
+import cc.iotkit.model.product.ProductModel;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface ProductModelRepository extends MongoRepository<ProductModel, String> {
+
+    ProductModel findByModel(String model);
+
+    List<ProductModel> findByProductKey(String productKey);
+
+}

+ 3 - 0
dao/src/main/java/cc/iotkit/dao/ProductRepository.java

@@ -6,4 +6,7 @@ import org.springframework.stereotype.Repository;
 
 @Repository
 public interface ProductRepository extends MongoRepository<Product, String> {
+
+
+
 }

+ 22 - 6
manager/src/main/java/cc/iotkit/manager/config/CacheConfig.java

@@ -1,16 +1,19 @@
 package cc.iotkit.manager.config;
 
+import cc.iotkit.common.Constants;
 import com.github.benmanes.caffeine.cache.Caffeine;
 import com.google.common.collect.Lists;
 import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.cache.caffeine.CaffeineCache;
 import org.springframework.cache.support.SimpleCacheManager;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
 
 import java.util.concurrent.TimeUnit;
 
-//@Configuration
-//@EnableCaching
+@Configuration
+@EnableCaching
 public class CacheConfig {
 
     /**
@@ -20,23 +23,36 @@ public class CacheConfig {
     public CacheManager cacheManager() {
         SimpleCacheManager manager = new SimpleCacheManager();
         manager.setCaches(Lists.newArrayList(new CaffeineCache(
-                        "device_cache",
+                        Constants.DEVICE_CACHE,
                         Caffeine.newBuilder()
                                 .expireAfterWrite(5, TimeUnit.MINUTES)
                                 .build()
                 ),
                 new CaffeineCache(
-                        "product_cache",
+                        Constants.PRODUCT_CACHE,
                         Caffeine.newBuilder()
                                 .expireAfterWrite(5, TimeUnit.MINUTES)
                                 .build()
                 ),
                 new CaffeineCache(
-                        "app_design_cache",
+                        Constants.APP_DESIGN_CACHE,
                         Caffeine.newBuilder()
                                 .expireAfterWrite(5, TimeUnit.MINUTES)
                                 .build()
-                )));
+                ),
+                new CaffeineCache(
+                        Constants.THING_MODEL_CACHE,
+                        Caffeine.newBuilder()
+                                .expireAfterWrite(5, TimeUnit.MINUTES)
+                                .build()
+                ),
+                new CaffeineCache(
+                        Constants.PRODUCT_SCRIPT_CACHE,
+                        Caffeine.newBuilder()
+                                .expireAfterWrite(5, TimeUnit.MINUTES)
+                                .build()
+                )
+        ));
         return manager;
     }
 

+ 44 - 4
manager/src/main/java/cc/iotkit/manager/controller/ProductController.java

@@ -1,14 +1,17 @@
 package cc.iotkit.manager.controller;
 
+import cc.iotkit.common.exception.BizException;
 import cc.iotkit.common.utils.JsonUtil;
 import cc.iotkit.dao.CategoryRepository;
 import cc.iotkit.dao.ProductRepository;
+import cc.iotkit.dao.ProductModelRepository;
 import cc.iotkit.dao.ThingModelRepository;
 import cc.iotkit.manager.config.AliyunConfig;
 import cc.iotkit.manager.service.DataOwnerService;
 import cc.iotkit.model.Paging;
 import cc.iotkit.model.product.Category;
 import cc.iotkit.model.product.Product;
+import cc.iotkit.model.product.ProductModel;
 import cc.iotkit.model.product.ThingModel;
 import com.aliyun.oss.OSS;
 import com.aliyun.oss.OSSClientBuilder;
@@ -17,12 +20,16 @@ import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 
 @Slf4j
 @RestController
@@ -39,13 +46,21 @@ public class ProductController {
     private DataOwnerService dataOwnerService;
     @Autowired
     private AliyunConfig aliyunConfig;
+    @Autowired
+    private ProductModelRepository productModelRepository;
+
     private OSS ossClient;
 
-    @PostMapping("/list")
-    public Paging<Product> getProducts(Product form) {
+    @PostMapping("/list/{size}/{page}")
+    public Paging<Product> getProducts(
+            @PathVariable("size") int size,
+            @PathVariable("page") int page,
+            Product form) {
         form = dataOwnerService.wrapExample(form);
-        return new Paging<>(productRepository.count(Example.of(form)),
-                productRepository.findAll(Example.of(form)));
+        Page<Product> products = productRepository.findAll(Example.of(form),
+                PageRequest.of(page - 1, size, Sort.by(Sort.Order.desc("createAt")))
+        );
+        return new Paging<>(products.getTotalElements(), products.getContent());
     }
 
     @PostMapping("/save")
@@ -121,4 +136,29 @@ public class ProductController {
         return ossClient.generatePresignedUrl(bucket, fileName,
                 new Date(new Date().getTime() + 3600L * 1000 * 24 * 365 * 10)).toString();
     }
+
+    @GetMapping("/{productKey}/models")
+    public List<ProductModel> getModels(@PathVariable("productKey") String productKey) {
+        dataOwnerService.checkOwner(productRepository, productKey);
+        return productModelRepository.findByProductKey(productKey);
+    }
+
+    @PostMapping("/saveProductModel")
+    public void saveProductModel(ProductModel productModel) {
+        String model = productModel.getModel();
+        String productKey = productModel.getProductKey();
+        Optional<Product> optProduct = productRepository.findById(productKey);
+        if (!optProduct.isPresent()) {
+            throw new BizException("product does not exist");
+        }
+        dataOwnerService.checkOwner(optProduct.get());
+
+        ProductModel oldScript = productModelRepository.findByModel(model);
+        if (oldScript != null && !oldScript.getProductKey().equals(productKey)) {
+            throw new BizException("model already exists");
+        }
+
+        productModel.setModifyAt(System.currentTimeMillis());
+        productModelRepository.save(productModel);
+    }
 }

+ 1 - 1
manager/src/main/resources/application.yml

@@ -24,7 +24,7 @@ spring:
   cache:
     cache-names: foo,bar
     caffeine:
-      spec: maximumSize=5000,expireAfterAccess=120s
+      spec: maximumSize=5000,expireAfterAccess=300s
   mvc:
     pathmatch:
       matching-strategy: ant_path_matcher

+ 9 - 0
model/src/main/java/cc/iotkit/model/product/Product.java

@@ -31,5 +31,14 @@ public class Product implements Owned {
 
     private String img;
 
+    /**
+     * 是否透传
+     */
+    private Boolean transparent;
+
     private Long createAt;
+
+    public boolean isTransparent() {
+        return transparent != null && transparent;
+    }
 }

+ 45 - 0
model/src/main/java/cc/iotkit/model/product/ProductModel.java

@@ -0,0 +1,45 @@
+package cc.iotkit.model.product;
+
+import cc.iotkit.model.Owned;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Document
+public class ProductModel {
+
+    public static final String TYPE_JS = "JavaScript";
+    public static final String TYPE_LUA = "LuaScript";
+
+    public static final String STATE_DEV = "dev";
+    public static final String STATE_PUBLISH = "publish";
+
+    /**
+     * 型号在所有产品中唯一
+     */
+    @Id
+    private String model;
+
+    private String name;
+
+    private String productKey;
+
+    private String type;
+
+    private String script;
+
+    /**
+     * 脚本状态,只有发布状态才生效
+     */
+    private String state;
+
+    private Long modifyAt;
+
+}

+ 6 - 0
pom.xml

@@ -173,6 +173,12 @@
                 <version>1.2.5</version>
             </dependency>
 
+            <dependency>
+                <groupId>org.luaj</groupId>
+                <artifactId>luaj-jse</artifactId>
+                <version>3.0.1</version>
+            </dependency>
+
             <dependency>
                 <groupId>io.vertx</groupId>
                 <artifactId>vertx-core</artifactId>

+ 5 - 0
protocol-gateway/component-server/pom.xml

@@ -62,6 +62,11 @@
             <artifactId>pulsar-client-original</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.luaj</groupId>
+            <artifactId>luaj-jse</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>

+ 5 - 3
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/ApiTool.java

@@ -112,22 +112,23 @@ public class ApiTool {
             if (method == HttpMethod.POST) {
                 request.sendJson(params)
                         .onSuccess((response) -> {
-                            System.out.println(response.bodyAsString());
+                            log.info("send succeed,response:{}", response.bodyAsString());
                             apiResponse.set(response.bodyAsJson(ApiResponse.class));
                             wait.countDown();
                         })
                         .onFailure((err) -> {
-                            err.printStackTrace();
+                            log.error("send failed", err);
                             wait.countDown();
                         });
             } else if (method == HttpMethod.GET) {
                 request.send()
                         .onSuccess((response) -> {
+                            log.info("send succeed,response:{}", response.bodyAsString());
                             apiResponse.set(response.bodyAsJson(ApiResponse.class));
                             wait.countDown();
                         })
                         .onFailure((err) -> {
-                            err.printStackTrace();
+                            log.error("send failed", err);
                             wait.countDown();
                         });
             }
@@ -140,6 +141,7 @@ public class ApiTool {
         } catch (Throwable e) {
             apiResponse.get().setStatus(500);
             apiResponse.get().setMessage(e.getMessage());
+            log.error("send error", e);
         }
         return apiResponse.get();
     }

+ 26 - 3
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/DeviceComponentManager.java

@@ -9,11 +9,16 @@ import cc.iotkit.comp.IDeviceComponent;
 import cc.iotkit.comps.config.CacheKey;
 import cc.iotkit.comps.config.ComponentConfig;
 import cc.iotkit.comps.service.DeviceBehaviourService;
+import cc.iotkit.converter.Device;
 import cc.iotkit.converter.DeviceMessage;
 import cc.iotkit.converter.ScriptConverter;
 import cc.iotkit.converter.ThingService;
+import cc.iotkit.dao.DeviceCache;
+import cc.iotkit.dao.ProductCache;
 import cc.iotkit.dao.ProtocolComponentRepository;
+import cc.iotkit.model.device.DeviceInfo;
 import cc.iotkit.model.device.message.ThingModelMessage;
+import cc.iotkit.model.product.Product;
 import cc.iotkit.model.protocol.ProtocolComponent;
 import cc.iotkit.model.protocol.ProtocolConverter;
 import lombok.extern.slf4j.Slf4j;
@@ -46,6 +51,10 @@ public class DeviceComponentManager {
     private ComponentConfig componentConfig;
     @Autowired
     private ProtocolComponentRepository componentRepository;
+    @Autowired
+    private DeviceCache deviceCache;
+    @Autowired
+    ProductCache productCache;
 
     @PostConstruct
     public void init() {
@@ -84,6 +93,7 @@ public class DeviceComponentManager {
                     resolve(ProtocolConverter.SCRIPT_FILE_NAME).toFile(), "UTF-8");
 
             scriptConverter.setScript(converterScript);
+            scriptConverter.putScriptEnv("component", componentInstance);
             componentInstance.setConverter(scriptConverter);
 
             String componentScript = FileUtils.readFileToString(path.
@@ -143,16 +153,29 @@ public class DeviceComponentManager {
             throw new BizException("there is no components");
         }
 
+        DeviceInfo deviceInfo = deviceCache.getDeviceInfo(service.getProductKey(), service.getDeviceName());
+        Product product = productCache.findById(service.getProductKey());
+        String linkPk = service.getProductKey();
+        String linkDn = service.getDeviceName();
+
+        if (product.isTransparent()) {
+            //如果是透传设备,取父级设备进行链路查找
+            DeviceInfo parent = deviceCache.get(deviceInfo.getParentId());
+            linkPk = parent.getProductKey();
+            linkDn = parent.getDeviceName();
+        }
+
         for (IDeviceComponent com : components.values()) {
-            if (com.exist(service.getProductKey(), service.getDeviceName())) {
+            if (com.exist(linkPk, linkDn)) {
+                Device device = new Device(deviceInfo.getDeviceId(), deviceInfo.getModel(), product.isTransparent());
                 //对下发消息进行编码转换
-                DeviceMessage message = com.getConverter().encode(service, null);
+                DeviceMessage message = com.getConverter().encode(service, device);
                 if (message == null) {
                     throw new BizException("encode send message failed");
                 }
                 //保存设备端mid与平台mid对应关系
                 redisTemplate.opsForValue().set(
-                        CacheKey.getKeyCmdMid(service.getDeviceName(), message.getMid()),
+                        CacheKey.getKeyCmdMid(message.getDeviceName(), message.getMid()),
                         service.getMid(), com.getConfig().getCmdTimeout(), TimeUnit.SECONDS);
                 com.send(message);
 

+ 1 - 1
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/DeviceMessageHandler.java

@@ -156,7 +156,7 @@ public class DeviceMessageHandler implements IMessageHandler {
 
         //服务回复需要重新对应mid
         if (thingModelMessage.getIdentifier().endsWith("_reply")) {
-            String platformMid = deviceComponentManager.getPlatformMid(message.getDeviceName(), message.getMid());
+            String platformMid = deviceComponentManager.getPlatformMid(thingModelMessage.getDeviceName(), message.getMid());
             if (platformMid == null) {
                 platformMid = UniqueIdUtil.newRequestId();
             }

+ 76 - 35
protocol-gateway/component-server/src/main/java/cc/iotkit/comps/service/DeviceBehaviourService.java

@@ -8,13 +8,13 @@ import cc.iotkit.common.utils.UniqueIdUtil;
 import cc.iotkit.comp.model.DeviceState;
 import cc.iotkit.comp.model.RegisterInfo;
 import cc.iotkit.comps.config.ServerConfig;
-import cc.iotkit.dao.DeviceCache;
-import cc.iotkit.dao.DeviceRepository;
-import cc.iotkit.dao.ProductRepository;
+import cc.iotkit.dao.*;
 import cc.iotkit.model.device.DeviceInfo;
 import cc.iotkit.model.device.message.ThingModelMessage;
 import cc.iotkit.model.product.Product;
+import cc.iotkit.model.product.ProductModel;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.pulsar.client.api.Producer;
 import org.apache.pulsar.client.api.PulsarClient;
 import org.apache.pulsar.client.api.PulsarClientException;
@@ -34,6 +34,10 @@ public class DeviceBehaviourService {
     @Autowired
     private ProductRepository productRepository;
     @Autowired
+    private ProductModelRepository productModelRepository;
+    @Autowired
+    private ProductCache productCache;
+    @Autowired
     private DeviceRepository deviceRepository;
     @Autowired
     private ServerConfig serverConfig;
@@ -69,17 +73,6 @@ public class DeviceBehaviourService {
                                     subDevice.getTag(), null));
                 }
             }
-
-            //设备注册消息
-            ThingModelMessage modelMessage = new ThingModelMessage(
-                    UniqueIdUtil.newRequestId(), "",
-                    info.getProductKey(), info.getDeviceName(),
-                    ThingModelMessage.TYPE_LIFETIME, "register",
-                    0, new HashMap<>(), System.currentTimeMillis(),
-                    System.currentTimeMillis()
-            );
-
-            reportMessage(modelMessage);
         } catch (BizException e) {
             log.error("register device error", e);
             throw e;
@@ -91,33 +84,75 @@ public class DeviceBehaviourService {
 
     public DeviceInfo register(String parentId, RegisterInfo info) {
         String pk = info.getProductKey();
+        String dn = info.getDeviceName();
+        String model = info.getModel();
+
+        //子设备注册处理
+        if (parentId != null) {
+            //透传设备:pk为空、model不为空,使用model查询产品
+            if (StringUtils.isBlank(pk) && StringUtils.isNotBlank(model)) {
+                ProductModel productModel = productModelRepository.findByModel(model);
+                if (productModel == null) {
+                    throw new BizException("product model does not exist");
+                }
+                pk = productModel.getProductKey();
+            }
+        }
+
         Optional<Product> optProduct = productRepository.findById(pk);
         if (!optProduct.isPresent()) {
             throw new BizException("Product does not exist");
         }
-        String uid = optProduct.get().getUid();
+        Product product = optProduct.get();
+        String uid = product.getUid();
         DeviceInfo device = deviceRepository.findByProductKeyAndDeviceName(pk, info.getDeviceName());
+        boolean reportMsg = false;
 
         if (device != null) {
             log.info("device already registered");
-            //更换网关重新注册更新父级ID
+            device.setModel(model);
+        } else {
+            //不存在,注册新设备
+            device = new DeviceInfo();
+            device.setId(DeviceUtil.newDeviceId(dn));
             device.setParentId(parentId);
-            deviceRepository.save(device);
-            return device;
+            device.setUid(uid);
+            device.setDeviceId(device.getId());
+            device.setProductKey(pk);
+            device.setDeviceName(dn);
+            device.setModel(model);
+            //默认离线
+            device.setState(new DeviceInfo.State(false, null, null));
+            device.setCreateAt(System.currentTimeMillis());
+            reportMsg = true;
         }
-        //不存在,注册新设备
-        device = new DeviceInfo();
-        device.setId(DeviceUtil.newDeviceId(info.getDeviceName()));
-        device.setParentId(parentId);
-        device.setUid(uid);
-        device.setDeviceId(device.getId());
-        device.setProductKey(pk);
-        device.setDeviceName(info.getDeviceName());
-        device.setState(new DeviceInfo.State(false, null, null));
-        device.setCreateAt(System.currentTimeMillis());
 
+        //透传设备,默认在线
+        if (product.isTransparent()) {
+            device.setState(new DeviceInfo.State(true, System.currentTimeMillis(), null));
+        }
+
+        if (parentId != null) {
+            //子设备更换网关重新注册更新父级ID
+            device.setParentId(parentId);
+            reportMsg = true;
+        }
         deviceRepository.save(device);
-        log.info("device registered:{}", JsonUtil.toJsonString(device));
+
+        //新设备或更换网关需要产生注册消息
+        if (reportMsg) {
+            log.info("device registered:{}", JsonUtil.toJsonString(device));
+            //新注册设备注册消息
+            ThingModelMessage modelMessage = new ThingModelMessage(
+                    UniqueIdUtil.newRequestId(), "",
+                    pk, dn,
+                    ThingModelMessage.TYPE_LIFETIME, "register",
+                    0, new HashMap<>(), System.currentTimeMillis(),
+                    System.currentTimeMillis()
+            );
+
+            reportMessage(modelMessage);
+        }
 
         return device;
     }
@@ -155,11 +190,17 @@ public class DeviceBehaviourService {
         }
         deviceStateChange(device, online);
 
-        //可能是父设备,父设备离线,子设备也要离线
-        if (!online && device.getParentId() == null) {
-            List<DeviceInfo> subDevices = deviceRepository.findByParentId(device.getDeviceId());
-            for (DeviceInfo subDevice : subDevices) {
-                deviceStateChange(subDevice, false);
+        if (device.getParentId() != null) {
+            return;
+        }
+
+        List<DeviceInfo> subDevices = deviceRepository.findByParentId(device.getDeviceId());
+        for (DeviceInfo subDevice : subDevices) {
+            Product product = productCache.findById(subDevice.getProductKey());
+            Boolean transparent = product.getTransparent();
+            //透传设备父设备上线,子设备也上线。非透传设备父设备离线,子设备才离线
+            if (transparent != null && transparent || !online) {
+                deviceStateChange(subDevice, online);
             }
         }
     }
@@ -192,7 +233,7 @@ public class DeviceBehaviourService {
 
     public void reportMessage(ThingModelMessage message) {
         try {
-            DeviceInfo device = deviceCache.findByProductKeyAndDeviceName(message.getProductKey(),
+            DeviceInfo device = deviceCache.getDeviceInfo(message.getProductKey(),
                     message.getDeviceName());
             if (device == null) {
                 return;

+ 12 - 0
protocol-gateway/converter/src/main/java/cc/iotkit/converter/Device.java

@@ -1,9 +1,21 @@
 package cc.iotkit.converter;
 
+import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
+@NoArgsConstructor
+@AllArgsConstructor
 public class Device {
 
+    private String deviceId;
+
+    private String model;
+
+    /**
+     * 是否透传
+     */
+    private Boolean transparent;
 
 }

+ 1 - 0
protocol-gateway/converter/src/main/java/cc/iotkit/converter/IConverter.java

@@ -10,4 +10,5 @@ public interface IConverter {
 
     DeviceMessage encode(ThingService<?> service, Device device);
 
+    void putScriptEnv(String key, Object value);
 }

+ 10 - 1
protocol-gateway/converter/src/main/java/cc/iotkit/converter/ScriptConverter.java

@@ -29,7 +29,12 @@ public class ScriptConverter implements IConverter {
 
     public ThingModelMessage decode(DeviceMessage msg) {
         try {
-            ScriptObjectMirror result = (ScriptObjectMirror) engine.invokeMethod(scriptObj, "decode", msg);
+            Object rst = engine.invokeMethod(scriptObj, "decode", msg);
+            if (rst instanceof ThingModelMessage) {
+                return (ThingModelMessage) rst;
+            }
+
+            ScriptObjectMirror result = (ScriptObjectMirror) rst;
             ThingModelMessage modelMessage = new ThingModelMessage();
             BeanUtils.populate(modelMessage, result);
             return modelMessage;
@@ -53,4 +58,8 @@ public class ScriptConverter implements IConverter {
         return null;
     }
 
+    @Override
+    public void putScriptEnv(String key, Object value) {
+        engine.put(key, value);
+    }
 }

+ 0 - 12
protocol-gateway/http-biz-component/src/main/java/cc/iotkit/comp/biz/HttpBizComponent.java

@@ -15,19 +15,14 @@ import jdk.nashorn.api.scripting.NashornScriptEngine;
 import jdk.nashorn.api.scripting.ScriptObjectMirror;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.io.FileUtils;
 
 import javax.script.ScriptEngineManager;
 import javax.script.ScriptException;
-import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 @Data
 @Slf4j
 public class HttpBizComponent implements IComponent {
@@ -161,11 +156,4 @@ public class HttpBizComponent implements IComponent {
         return data;
     }
 
-    public static void main(String[] args) throws IOException {
-        HttpBizComponent component = new HttpBizComponent();
-        component.setScript(FileUtils.readFileToString(new File("/Users/sjg/home/gitee/open-source/iotkit-parent/protocol-gateway/http-biz-component/src/main/resources/component.js"), UTF_8));
-        component.create(new CompConfig(1000, "{\"port\":9081}"));
-        component.start();
-    }
-
 }

+ 1 - 0
protocol-gateway/mqtt-client-simulator/src/main/java/cc/iotkit/simulator/Application.java

@@ -30,6 +30,7 @@ public class Application {
             gateway2.addSubDevice("cGCrkK7Ex4FESAwe", "ABE12300001", "S1");
             gateway2.addSubDevice("cGCrkK7Ex4FESAwe", "ABE12300002", "S1");
             gateway2.addSubDevice("6kYp6jszrDns2yh4", "ABE12400001", "S1");
+            gateway2.addSubDevice("", "ABE12500001", "M1");
             gateway2.start();
         }).start();
 

+ 7 - 1
protocol-gateway/mqtt-client-simulator/src/main/java/cc/iotkit/simulator/service/Gateway.java

@@ -6,6 +6,7 @@ import cc.iotkit.simulator.config.Mqtt;
 import lombok.*;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.eclipse.paho.client.mqttv3.*;
 import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
 
@@ -120,9 +121,14 @@ public class Gateway extends Device {
                     //子设备注册成功
                     if (response.code == 0) {
                         Map<String, Object> data = response.getData();
+                        String productKey = data.get("productKey").toString();
+                        if (StringUtils.isBlank(productKey)) {
+                            return;
+                        }
+
                         //订阅子设备消息
                         String subTopic = String.format("/sys/%s/%s/c/#",
-                                data.get("productKey"), data.get("deviceName"));
+                                productKey, data.get("deviceName"));
                         log.info("subscribe topic:{}", subTopic);
                         client.subscribe(subTopic);
                     }

+ 13 - 0
protocol-gateway/mqtt-component/dependency-reduced-pom.xml

@@ -26,6 +26,7 @@
               <include>io.vertx:vertx-core</include>
               <include>io.vertx:vertx-mqtt</include>
               <include>io.netty:netty-codec-mqtt</include>
+              <include>org.luaj:luaj-jse</include>
             </includes>
           </artifactSet>
         </configuration>
@@ -70,6 +71,12 @@
       <version>1.7.32</version>
       <scope>compile</scope>
     </dependency>
+    <dependency>
+      <groupId>org.luaj</groupId>
+      <artifactId>luaj-jse</artifactId>
+      <version>3.0.1</version>
+      <scope>provided</scope>
+    </dependency>
     <dependency>
       <groupId>cc.iotkit</groupId>
       <artifactId>common</artifactId>
@@ -82,5 +89,11 @@
       <version>0.1.0-SNAPSHOT</version>
       <scope>compile</scope>
     </dependency>
+    <dependency>
+      <groupId>cc.iotkit</groupId>
+      <artifactId>dao</artifactId>
+      <version>0.1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
   </dependencies>
 </project>

+ 11 - 0
protocol-gateway/mqtt-component/pom.xml

@@ -38,6 +38,11 @@
             <artifactId>slf4j-api</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.luaj</groupId>
+            <artifactId>luaj-jse</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>cc.iotkit</groupId>
             <artifactId>common</artifactId>
@@ -48,6 +53,11 @@
             <artifactId>component</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cc.iotkit</groupId>
+            <artifactId>dao</artifactId>
+        </dependency>
+
     </dependencies>
 
     <build>
@@ -70,6 +80,7 @@
                             <include>io.vertx:vertx-core</include>
                             <include>io.vertx:vertx-mqtt</include>
                             <include>io.netty:netty-codec-mqtt</include>
+                            <include>org.luaj:luaj-jse</include>
                         </includes>
                     </artifactSet>
                 </configuration>

+ 20 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/IScripter.java

@@ -0,0 +1,20 @@
+package cc.iotkit.comp.mqtt;
+
+import cc.iotkit.converter.DeviceMessage;
+import cc.iotkit.converter.ThingService;
+import cc.iotkit.model.device.message.ThingModelMessage;
+
+public interface IScripter {
+
+    void setScript(String script);
+
+    /**
+     * 透传解码
+     */
+    ThingModelMessage decode(TransparentMsg msg);
+
+    /**
+     * 透传编码
+     */
+    TransparentMsg encode(ThingService<?> service);
+}

+ 19 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/JsScripter.java

@@ -0,0 +1,19 @@
+package cc.iotkit.comp.mqtt;
+
+import cc.iotkit.converter.ThingService;
+import cc.iotkit.model.device.message.ThingModelMessage;
+
+public class JsScripter implements IScripter {
+
+    @Override
+    public void setScript(String script) {
+    }
+
+    public ThingModelMessage decode(TransparentMsg msg) {
+        return null;
+    }
+
+    public TransparentMsg encode(ThingService<?> service) {
+        return null;
+    }
+}

+ 126 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/LuaScripter.java

@@ -0,0 +1,126 @@
+package cc.iotkit.comp.mqtt;
+
+import cc.iotkit.converter.ThingService;
+import cc.iotkit.model.device.message.ThingModelMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.beanutils.BeanUtils;
+import org.luaj.vm2.LuaTable;
+import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.script.LuaScriptEngine;
+
+import javax.script.Compilable;
+import javax.script.CompiledScript;
+import javax.script.ScriptEngineManager;
+import javax.script.SimpleBindings;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+public class LuaScripter implements IScripter {
+
+    private final LuaScriptEngine engine = (LuaScriptEngine) (
+            new ScriptEngineManager().getEngineByName("luaj"));
+
+    private LuaValue decoder;
+    private LuaValue encoder;
+
+    @Override
+    public void setScript(String script) {
+        try {
+            CompiledScript compiledScript = ((Compilable) engine).compile(script);
+            SimpleBindings bindings = new SimpleBindings();
+            compiledScript.eval(bindings);
+            decoder = (LuaValue) bindings.get("decode");
+            encoder = (LuaValue) bindings.get("encode");
+        } catch (Throwable e) {
+            log.error("compile script error", e);
+        }
+    }
+
+    public ThingModelMessage decode(TransparentMsg msg) {
+        try {
+            LuaTable table = new LuaTable();
+            table.set("model", msg.getModel());
+            table.set("mac", msg.getMac());
+            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());
+            return modelMessage;
+        } catch (Throwable e) {
+            log.error("execute decode script error", e);
+        }
+        return null;
+    }
+
+    public TransparentMsg encode(ThingService<?> service) {
+        try {
+            LuaTable table = new LuaTable();
+            table.set("identifier", service.getIdentifier());
+            table.set("type", service.getType());
+            table.set("productKey", service.getProductKey());
+            table.set("deviceName", service.getDeviceName());
+            table.set("mid", service.getMid());
+            Object params = service.getParams();
+            LuaTable tableParams = new LuaTable();
+            if (params instanceof Map) {
+                ((Map<?, ?>) params).forEach((key, val) -> tableParams.set(key.toString(), parse(val)));
+            }
+            table.set("params", tableParams);
+            LuaValue result = encoder.call(table);
+            Map map = (Map) parse(result);
+            TransparentMsg message = new TransparentMsg();
+            BeanUtils.populate(message, map);
+            return message;
+        } catch (Throwable e) {
+            log.error("execute encode script error", e);
+        }
+        return null;
+    }
+
+    private Object parse(LuaValue value) {
+        String type = value.typename();
+        switch (type) {
+            case "string":
+                return value.toString();
+            case "number":
+            case "int":
+                return value.toint();
+            case "table":
+                Map<String, Object> data = new HashMap<>();
+                LuaTable table = (LuaTable) value;
+                int arrLen = table.rawlen();
+                if (arrLen > 0) {
+                    //数组转换
+                    List<Object> list = new ArrayList<>();
+                    for (LuaValue key : table.keys()) {
+                        list.add(parse(table.get(key)));
+                    }
+                    return list;
+                } else {
+                    //map转换
+                    for (LuaValue key : table.keys()) {
+                        data.put(key.toString(), parse(table.get(key)));
+                    }
+                }
+                return data;
+        }
+        return null;
+    }
+
+    private LuaValue parse(Object value) {
+        if (value instanceof String) {
+            return LuaValue.valueOf(value.toString());
+        }
+        if (value instanceof Integer) {
+            return LuaValue.valueOf((Integer) value);
+        }
+        return new LuaTable();
+    }
+
+}

+ 21 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/MqttDeviceComponent.java

@@ -5,13 +5,17 @@ import cc.iotkit.common.utils.JsonUtil;
 import cc.iotkit.comp.AbstractDeviceComponent;
 import cc.iotkit.comp.CompConfig;
 import cc.iotkit.comp.model.DeviceState;
+import cc.iotkit.converter.Device;
 import cc.iotkit.converter.DeviceMessage;
+import cc.iotkit.converter.ThingService;
+import cc.iotkit.model.device.message.ThingModelMessage;
 import io.vertx.core.Future;
 import io.vertx.core.Vertx;
 import lombok.*;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.beanutils.BeanUtils;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
@@ -24,6 +28,7 @@ public class MqttDeviceComponent extends AbstractDeviceComponent {
     private String deployedId;
     private MqttVerticle mqttVerticle;
     private final Map<String, Device> deviceChildToParent = new HashMap<>();
+    private TransparentConverter transparentConverter = new TransparentConverter();
 
     public void create(CompConfig config) {
         super.create(config);
@@ -122,6 +127,22 @@ public class MqttDeviceComponent extends AbstractDeviceComponent {
         return config;
     }
 
+    /**
+     * 透传解码
+     */
+    public ThingModelMessage transparentDecode(Map<String, Object> msg) throws InvocationTargetException, IllegalAccessException {
+        TransparentMsg transparentMsg = new TransparentMsg();
+        BeanUtils.populate(transparentMsg, msg);
+        return transparentConverter.decode(transparentMsg);
+    }
+
+    /**
+     * 透传编码
+     */
+    public DeviceMessage transparentEncode(ThingService<?> service, cc.iotkit.converter.Device device) {
+        return transparentConverter.encode(service, device);
+    }
+
     @Data
     public static class Message {
         private String topic;

+ 88 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentConverter.java

@@ -0,0 +1,88 @@
+package cc.iotkit.comp.mqtt;
+
+
+import cc.iotkit.converter.Device;
+import cc.iotkit.converter.DeviceMessage;
+import cc.iotkit.converter.ThingService;
+import cc.iotkit.dao.DeviceCache;
+import cc.iotkit.dao.ProductCache;
+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 java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+public class TransparentConverter {
+
+    private final Map<String, IScripter> scripters = new HashMap<>();
+    private final Map<String, String> scripts = new HashMap<>();
+
+    /**
+     * 透传解码
+     */
+    public ThingModelMessage decode(TransparentMsg msg) {
+        //通过上报消息中的model取得对应的产品
+        String productKey = checkScriptUpdate(msg.getModel());
+        msg.setProductKey(productKey);
+        return scripters.get(productKey).decode(msg);
+    }
+
+    /**
+     * 透传编码
+     */
+    public DeviceMessage encode(ThingService<?> service, Device device) {
+        String productKey = service.getProductKey();
+        checkScriptUpdate(device.getModel());
+        TransparentMsg transparentMsg = scripters.get(productKey).encode(service);
+        //转换成网关消息
+        String deviceName = service.getDeviceName();
+        DeviceInfo gateway = getGatewayInfo(productKey, deviceName);
+        DeviceMessage message = new DeviceMessage();
+        message.setProductKey(gateway.getProductKey());
+        message.setDeviceName(gateway.getDeviceName());
+        message.setMid(transparentMsg.getMid());
+        //透传格式消息内容,mac、model、data
+        message.setContent(transparentMsg);
+        return message;
+    }
+
+    private ProductModel getScript(String model) {
+        return ProductCache.getInstance().getProductScriptByModel(model);
+    }
+
+    private DeviceInfo getGatewayInfo(String subPk, String subDn) {
+        String parentId = DeviceCache.getInstance().getDeviceInfo(subPk, subDn).getParentId();
+        return DeviceCache.getInstance().get(parentId);
+    }
+
+    /**
+     * 检查产品脚本是否更新
+     */
+    private String checkScriptUpdate(String model) {
+        ProductModel productModel = getScript(model);
+        String productKey = productModel.getProductKey();
+        String script = productModel.getScript();
+
+        String oldScript = scripts.get(productKey);
+        if (script.equals(oldScript)) {
+            return productKey;
+        }
+
+        String type = productModel.getType();
+        if (ProductModel.TYPE_LUA.equals(type)) {
+            scripters.putIfAbsent(productKey, new LuaScripter());
+        } else if (ProductModel.TYPE_JS.equals(type)) {
+            scripters.putIfAbsent(productKey, new JsScripter());
+        }
+
+        //更新脚本
+        IScripter scripter = scripters.get(productKey);
+        scripter.setScript(script);
+        scripts.put(productKey, script);
+        return productKey;
+    }
+
+}

+ 21 - 0
protocol-gateway/mqtt-component/src/main/java/cc/iotkit/comp/mqtt/TransparentMsg.java

@@ -0,0 +1,21 @@
+package cc.iotkit.comp.mqtt;
+
+import lombok.Data;
+
+@Data
+public class TransparentMsg {
+
+    private String productKey;
+
+    /**
+     * 生成给设备端的消息id
+     */
+    private String mid;
+
+    private String model;
+
+    private String mac;
+
+    private String data;
+
+}

+ 1 - 1
rule-engine/src/main/java/cc/iotkit/ruleengine/filter/DeviceCondition.java

@@ -41,7 +41,7 @@ public class DeviceCondition {
             deviceInfo = deviceCache.findByDeviceId(device);
         } else {
             //用pk/dn取
-            deviceInfo = deviceCache.findByProductKeyAndDeviceName(pkDn[0], pkDn[1]);
+            deviceInfo = deviceCache.getDeviceInfo(pkDn[0], pkDn[1]);
         }
         Object left = null;
         if ("property".equals(type)) {