设备/实体
概述
本章节主要介绍Beaver IoT平台关键对象: 设备、实体。以及如何基于注解、编程式、YAML构建它们。
关键对象
Device
Device是设备的实例,里面有:
- id
 - 所属集成的id
 - 名称
 - 额外数据: 采用Map结构存储设备的额外信息,如设备的序列号或者生产日期等其它自定义信息
 - key:设备key, key规则详见关键编码概念介绍章节
 - identifier: 设备identifier, identifier规则详见关键编码概念介绍章节
 - 包含的实体
 
设备在保存后,除了名称和额外数据都不建议再改变其它元数据。
Entity
Entity是实体的实例,对象内容是实体的元数据(不包含实体的值),包括:
- id
 - 设备key: 如果是设备的实体,那么会含有设备的key,规则详见关键编码概念介绍章节
 - 集成ID
 - 实体名称
 - 访问权限: 只对Property类型的实体有意义,只读/只写/读写
 - identifier:实体identifier, identifier规则详见关键编码概念介绍章节
 - 实体值类型:包括:STRING, LONG, DOUBLE, BOOLEAN, BINARY, OBJECT
 - 实体类型: 包括:属性实体, 事件实体, 服务实体
 - 实体属性: 实体的属性,如单位, 精度, 最大值, 最小值, 最大长度, 最小长度, 枚举等
 - 子实体:实体的子实体,当前最多支持两层关系
 - key: 实体key, key规则详见关键编码概念介绍章节
 
实体在保存后,除了名称和实体属性都不建议再改变其它元数据。
对象构建 
本节将会介绍设备和实体的构建方法。
基于注解构建
注解说明
类注解
@IntegrationEntities:标识当前类为集成实体类@DeviceTemplateEntities:标识当前类为设备实体模板类name:设备模板名称
@DeviceEntities:标识当前类为设备实体类identifier:设备identifiername:设备名称additional:设备额外数据,通过@KeyValue注解声明
@Entities:标识当前类为子实体类
字段注解
@Entity:标识当前属性为实体type:实体类型EntityType,包括:属性实体, 事件实体, 服务实体name:实体名称identifier:实体identifierattributes:@Attribute注解声明实体属性,包括如单位, 精度, 最大值, 最小值, 最大长度, 最小长度, 枚举格式等accessMod:实体访问方式AccessMod,只对Property类型的实体有意义,包括:只读, 只写, 读写visible:实体是否对用户可见visible。部分集成内部管理使用的实体,包括添加和删除设备的服务实体等,不需要对用户可见
@Attribute:实体属性注解enumClass:枚举类unit:单位fractionDigits:精度max:最大值min:最小值maxLength:最大长度minLength:最小长度format:格式
构建集成实体
- 定义集成实体
 
@Data
@EqualsAndHashCode(callSuper = true)
@IntegrationEntities
public class MyIntegrationEntities extends ExchangePayload {
    @Entity(type = EntityType.EVENT, name = "Event Entity Name", identifier = "event_entity")
    private String eventEntity;
    @Entity(type = EntityType.PROPERTY, name = "Property Entity Name", identifier = "property_entity", accessMod = AccessMod.R)
    private Boolean propertyEntity;
    
    @Entity(type = EntityType.SERVICE, identifier = "service_entity", attributes = @Attribute(enumClass = SampleEnum.class))
    private Long serviceEntity;
    public enum SampleEnum {
        SAMPLE_ENUM_1, SAMPLE_ENUM_2;
    }
}
- 默认情况下,@Entity实体注解采用对象字段(驼峰转下划线)名作为实体name、identifier,开发者可通过name、identifier属性自定义实体名称、identifier
 - 当订阅实体事件时,注解的实体对象同时可用于接收ExchangePayload事件数据,即可通过Getter方法获取到属性值,从而简化代码开发,可参见事件订阅章节。 需注意当用于接收事件数据时,实体对象需继承ExchangePayload类,并且实体属性包含对应的Getter方法
 
- 定义集成子实体
 
@Data
@EqualsAndHashCode(callSuper = true)
@IntegrationEntities
public class MyIntegrationEntities extends ExchangePayload {
    
    @Entity(type = EntityType.EVENT, name = "Parent Entity Name", identifier = "parent_entity")
    private ParentEntity parentEntity;
    @Data
    @EqualsAndHashCode(callSuper = true)
    @Entities
    public static class ParentEntity extends ExchangePayload {
        // Entity type EVENT inherits from ParentEntity
        @Entity(name = "Child 1 Name", identifier = "child_1")
        private Long childEntity1;
        @Entity(name = "Child 2 Name", identifier = "child_2")
        private Long childEntity2;
    }
}
设备的子实体定义方式相同,需在子实体类上添加@Entities注解,即可完成子实体的构建,子实体不能再包含子实体。子实体默认继承父实体的属性,即子实体不需要设置type类型。父实体的数据类型为OBJECT。
定义设备实体模板
在实际场景中,集成下可能会有很多相同类型的设备,因此每个设备都含有相同种类的实体,如:一个集成可以对接多个环境传感器,每个传感器都有属于自己的温度和湿度实体。
@Data
@EqualsAndHashCode(callSuper = true)
@DeviceTemplateEntities(name="Environment Device Template")
public class EnvironmentDeviceEntities extends ExchangePayload {
  @Entity(name = "temperature", identifier = "temperature", accessMod = AccessMod.RW, type = EntityType.PROPERTY)
  private Double temperature;
  @Entity
  private Long humidity;
}
定义设备实体
@Data
@EqualsAndHashCode(callSuper = true)
@DeviceEntities(name="Default Device Name", additional = {@KeyValue(key = "seriesNumber", value = "sample_number")}, identifier = "default_device")
public class MyDeviceEntities extends ExchangePayload {
  @Entity(name = "temperature", identifier = "temperature", accessMod = AccessMod.RW, type = EntityType.PROPERTY)
  private Double temperature;
  @Entity
  private Long humidity;
}
当实体类为设备实体,Beaver IoT平台会初始化这个设备并添加到数据库中。
编程式构建
Beaver IoT 平台提供了DeviceBuilder、EntityBuilder等Builder类,开发者可通过编程式构建设备、实体等对象。
构建集成实体
- 不包含子实体
 
  Entity entityConfig = new EntityBuilder(integrationId)    //设置集成标识
          .identifier("webhookStatus")        //设置实体标识
          .property("webhookStatus", AccessMod.R) //设置作为属性实体
//          .service("accessKey")             //设置作为服务实体
//          .event("accessKey")               //设置作为事件实体
          .attributes(new AttributeBuilder().maxLength(300).enums(IntegrationStatus.class).build())      //设置实体属性,也可以使用attributes(Supplier<Map<String, Object>> supplier)方法进行构建
          .valueType(EntityValueType.STRING)    //设置实体值类型
          .build();
- 包含子实体
 
- 示例1
 - 示例2
 - 示例3
 
  //示例1: 通过EntityBuilder的children()方法构建子实体
  Entity entityConfig = new EntityBuilder(integrationId)
          .identifier("settings")
          .property("settings", AccessMod.RW)
          .valueType(EntityValueType.OBJECT)
          .children()           //设置子实体
              .valueType(EntityValueType.STRING).property("accessKey", AccessMod.RW).end()
          .children()
              .valueType(EntityValueType.STRING).property("secretKey", AccessMod.RW).end()
          .build();
  //示例2: 通过EntityBuilder的children(Supplier<List<Entity>> supplier)方法设置子实体
  Entity parentEntity = new EntityBuilder(integrationId)
        .identifier("settings")
        .property("settings", AccessMod.RW)
        .valueType(EntityValueType.OBJECT)
        .children(()->{
            Entity childEntity = new EntityBuilder()  //定义子实体
                    .identifier("accessKey")
                    .property("accessKey", AccessMod.RW)
                    .valueType(EntityValueType.STRING)
                    .build();
            return List.of(childEntity);
        })  //设置子实体,可以是List<Entity>或是单个实体
        .build();
  //示例3: 通过EntityBuilder的children(List<Entity> entities)方法设置子实体
  Entity childEntity = new EntityBuilder()  //定义子实体
        .identifier("accessKey")
        .property("accessKey", AccessMod.RW)
        .valueType(EntityValueType.STRING)
        .build();
  Entity parentEntity = new EntityBuilder(integrationId)
        .identifier("settings")
        .property("settings", AccessMod.RW)
        .valueType(EntityValueType.OBJECT)
        .children(childEntity)  //设置子实体,可以是List<Entity>或是单个实体
        .build();
构建设备及实体
- 构建设备
 
Device device = new DeviceBuilder(integrationConfig.getId())
        .name("deviceDemo")
        .identifier("deviceDemoIdentifier")
        .additional(Map.of("sn", "demoSN"))
        .build();
- 构建设备实体
 
- 示例1(推荐)
 - 示例2
 - 示例3
 - 示例4(@DeviceTemplateEntities)
 
Device device = new DeviceBuilder(integrationConfig.getId())
    .name("deviceDemo")
    .identifier("deviceDemoIdentifier")
    .entity(entity)
    .additional(Map.of("sn", "demoSN"))
    .entity(()->{
        return new EntityBuilder(integrationId)
              .identifier("temperature")
              .property("temperature", AccessMod.R)
              .valueType(EntityValueType.STRING) 
              .build();
      })
    .build();
Entity entity = new EntityBuilder(integrationId)
      .identifier("temperature")
      .property("temperature", AccessMod.R)
      .valueType(EntityValueType.STRING)
      .build();
Device device = new DeviceBuilder(integrationConfig.getId())
        .name("deviceDemo")
        .identifier("deviceDemoIdentifier")
        .entity(entity)
        .additional(Map.of("sn", "demoSN"))
        .build();
Device device = new DeviceBuilder(integrationConfig.getId())
    .name("deviceDemo")
    .identifier("deviceDemoIdentifier")
    .entity(entityConfig)
    .additional(Map.of("sn", "demoSN"))
    .build();
s    //highlight-next-line
Entity entity = new EntityBuilder(integrationId, device.getKey()) 
    .identifier("temperature")
    .property("temperature", AccessMod.R) 
    .valueType(EntityValueType.STRING)
    .build();
device.setEntities(Collections.singletonList(entity));
Device device = new DeviceBuilder(INTEGRATION_ID)
    .name(deviceName)
    .identifier("deviceDemoIdentifier")
    .additional(Map.of("sn", "demoSN"))
    .entities(()-> new AnnotatedTemplateEntityBuilder(INTEGRATION_ID, "deviceDemoIdentifier")
      .build(MyDeviceEntities.class))
    .build();
构建实体属性
Beaver IoT 平台提供了AttributeBuilder类,开发者可通过编程式构建实体属性。当前平台支持的属性包括:单位、精度、最大值、最小值、最大长度、最小长度、枚举格式等,也可以支持开发者自定义属性,例如:
    Map<String, Object> build = new AttributeBuilder()
        .unit("s") // 单位为秒
        .fractionDigits(0) // 小数位数为0,表示精确到秒
        .min(0.0) // 设定合理的最小值(根据实际需求调整)
        .maxLength(10) // 设定合理的最大长度(根据实际需求调整)
        .minLength(1) // 设定合理的最小长度(根据实际需求调整)
        .format("yyyy-MM-dd HH:mm:ss") // 时间格式精确到秒
        .build();
添加/删除设备实体
添加或者删除设备在Beaver IoT中是集成的两个特殊的服务类型的实体。集成如果需要支持添加或者删除设备,需要定义这两个实体,并且处理他们的调用事件,最后将这两个实体显式地放到集成定义中。
新增设备
新增设备事件,平台将会在ExchangePayload上下文中携带设备名称device_name,开发者可以从ExchangePayload的上下文中获取,或实现AddDeviceAware接口获取新增的设备信息。
- 方式1(推荐)
 - 方式2
 
- 定义实体
 
    @Data
    @EqualsAndHashCode(callSuper = true)
    @Entities
    public static class AddDevice extends ExchangePayload implements AddDeviceAware {
      @Entity
      private String ip;
    }
- 获取设备名
 
  @EventSubscribe(payloadKeyExpression = "my-integration.integration.add_device.*")
  public void onAddDevice(Event<MyIntegrationEntities.AddDevice> event) {
      String deviceName = event.getPayload().getAddDeviceName();
      ...
  }
- 获取设备名
 
  @EventSubscribe(payloadKeyExpression = "my-integration.integration.add_device.*")
  public void onAddDevice(Event<MyIntegrationEntities.AddDevice> event) {
      String deviceName = event.getPayload().getContext().get(ExchangeContextKeys.DEVICE_NAME_ON_ADD);
      ...
  }
删除设备
删除设备事件,平台将会在ExchangePayload上下文中携带删除的设备device,开发者可以从ExchangePayload的上下文中获取,或实现DeleteDeviceAware接口获取新增的设备信息。
- 方式1(推荐)
 - 方式2
 
- 定义设备删除的实体
 
  @Data
  @EqualsAndHashCode(callSuper = true)
  @Entities
  public static class DeleteDevice extends ExchangePayload implements DeleteDeviceAware {
    // 应当为空
  }
设备删除的实体不应该包含任何实体,即这个类应该为空。
- 获取删除的设备
 
  @EventSubscribe(payloadKeyExpression = "my-integration.integration.delete_device")
  public void onDeleteDevice(Event<MyIntegrationEntities.DeleteDevice> event) {
      Device device = event.getPayload().getDeletedDevice();
      ...
  }
- 获取删除的设备
 
  @EventSubscribe(payloadKeyExpression = "my-integration.integration.delete_device")
  public void onDeleteDevice(Event<MyIntegrationEntities.DeleteDevice> event) {
      Device device = event.getPayload().getContext().get(ExchangeContextKeys.DEVICE_ON_DELETE);
      ...
  }
基于YAML构建
为方便开发,Beaver IoT平台也提供了基于YAML的方式构建设备、实体等对象。开发者只需在集成的yaml文件中定义设备、实体等对象即可完成设备、实体的构建。平台将会在集成启动时加载对应的实体、集成并初始化。
integration:
  my-integration: # integration identifier
    # ...
    initial-entities: # initial entities
      - identifier: 'connect' # entity identifier
        name: connect         # entity name
        value_type: object    # entity value type
        type: service         # entity type
        access_mod: RW        # entity access mode
        children:             # children entities
          - identifier: 'url'
            name: connectUrl    
            value_type: string
            type: service
    initial-devices: # initial devices
      - identifier: 'demoDevice' # device identifier
        name: demoDevice         # device name
        entities:             # device entities
          - identifier: 'temperature'
            name: temperature
            value_type: long
            access_mod: RW
            type: property
如果场景十分简单则可以选择基于YAML构建,一般情况下不推荐这种方式。逻辑比较复杂时,YAML定义的设备和实体难以和业务代码结合。
选择合适的构建方式
有的构建方式会在Beaver IoT装载这个集成的时候自动保存到数据库中,有的构建方式则需要手动保存构建完成的设备和实体。
构建设备及其实体的方式
| 设备数量 | 设备类型* | 装载时自动保存 | 选择的构建方式 | 
|---|---|---|---|
| 有限的 | 有限的 | 是 | @DeviceEntities / YAML | 
| 动态的 | 有限的 | 否 | @DeviceTemplateEntities | 
| 动态的 | 动态的 | 否 | 编程式构建 | 
*设备类型包括设备的基本定义(如设备名称、设备额外数据等),以及设备的实体定义。
构建集成实体的方式
| 当前集成的实体 | 装载时自动保存 | 选择的构建方式 | 
|---|---|---|
| 有限的 | 是 | @IntegrationEntities / YAML | 
| 动态的 | 否 | 编程式构建 | 
设备/实体保存
保存设备或者实体
保存设备
- 方法见文档
 - 保存设备会保存设备本身的数据和属于这台设备的实体的数据
 
保存实体
- 方法见文档
 - 保存实体会保存实体本身的数据和其子实体的数据
 - 保存实体并不会保存实体的值,只保存实体的元数据
 
保存/获取实体的值
通过实体Wrapper保存
普通实体类
@IntegrationEntities定义的集成实体类@DeviceEntities定义的设备实体类- 以上两种方式定义下,用
@Entities定义的子实体类 
可以通过AnnotatedEntityWrapper来更新相应实体的值
比如,我们定义了集成的实体MyIntegrationEntities如下所示
@Data
@EqualsAndHashCode(callSuper = true)
@IntegrationEntities
public class MyIntegrationEntities extends ExchangePayload {
    @Entity
    private String entity1;
    @Entity
    private String entity2;
}
然后就可以创建相应的Wrapper
AnnotatedEntityWrapper<MyIntegrationEntities> wrapper = new AnnotatedEntityWrapper<>();
如果要更新实体entity1的值为sample,可以使用saveValue方法
wrapper.saveValue(MyIntegrationEntities::getEntity1, "sample").publishSync();
如果要更新实体entity1和entity2的值为sample,可以使用saveValues方法
wrapper.saveValues(Map.of(
                MyIntegrationEntities::getEntity1, "sample",
                MyIntegrationEntities::getEntity2, "sample"
        )).publishSync();
saveValue和saveValues会返回ExchangeEventPublisher类。一般情况下,保存一个值都需要发布相应的事件,来通知其它订阅者(其它集成、Beaver IoT内部的方法等)实体值的改变。
ExchangeEventPublisher.publishSync和ExchangeEventPublisher.publishAsync对应事件发布的同步和异步两种方式,具体区别可以参考文档。
如果要获取entity1的值,可以使用getValue方法
String value = (String) wrapper.getValue(MyIntegrationEntities::getEntity1).orElse(null);
如果要获取entity1和entity2的值,可以使用getValues方 法
Map<String, Object> values = wrapper.getValues(MyIntegrationEntities::getEntity1, MyIntegrationEntities::getEntity2);
设备模板实体类
@DeviceTemplateEntities定义的集成实体类
比如,我们定义了一个设备实体模板
@Data
@EqualsAndHashCode(callSuper = true)
@DeviceTemplateEntities(name = "Template Device")
public class MyDeviceEntities extends ExchangePayload {
    @Entity
    private String entity1;
}
可以创建相应的Wrapper
AnnotatedTemplateEntityWrapper<MyDeviceEntities> wrapper = new AnnotatedEntityWrapper<>(device.getIdentifier());
如果要更新设备实体entity1的值为sample,可以使用saveValue方法
wrapper
        .saveValue(MyDeviceEntities::getEntity1, "sample")
        .publishSync();
和AnnotatedEntityWrapper相似:
- 同时更新多个值可以使用
saveValues方法 - 获取单个或者多个值可以使用
getValue和getValues方法 
实体类实例
如果要更新实体的实例entity的值为sample,做法是
new EntityWrapper(entity)
        .saveValue("sample")
        .publishSync();
通过实体key构建Exchange
另一种更加灵活的方式是直接根据实体的key来构建ExchangePayload最后保存,ExchangePayload提供了create方法,可以从一个Map对象中直接创建实例。
构建更新单个实体的的payload
ExchangePayload exchangePayload = ExchangePayload.create("<entityKey>", "<entityValue>");
构建更新多个实体的payload
ExchangePayload exchangePayload = ExchangePayload.create(Map.of(
  "<entityKey1>", "<entityValue1>",
  "<entityKey2>", "<entityValue2>",
  "<entityKey3>", "<entityValue3>"
  // ...
));
最后保存
entityValueServiceProvider.saveValuesAndPublishSync(exchangePayload)
通过实体key获取值
获取单个实体的值
entityValueServiceProvider.findValueByKey("<entityKey>")
获取多个实体的值
entityValueServiceProvider.findValuesByKeys(List.of("<entityKey1>", "<entityKey2>"))
findValueByKey和findValuesByKeys这两个方法只会获取当前实体的值,如果有子实体并不会获取子实体的值
获取父实体的子实体的值
entityValueServiceProvider.findValuesByKey("<parentEntityKey>", ParentEntity.class)