Devices/Entities
Overview
This chapter primarily introduces the key objects of the Beaver IoT platform: Devices and Entities, and explains how to construct them using annotations, programming, or YAML.
Key Objects
Device
A Device
is an instance of a device, which includes:
- id
- integration id
- name
- additional data: Stores extra information about the device in a Map structure, such as the device's serial number or production date, among other custom data.
- key: Device key, with key rules detailed in the Key Coding Concept Introduction chapter.
- identifier: Device identifier, with identifier rules detailed in the Key Coding Concept Introduction chapter.
- contained entities
Once a device is saved, it is not recommended to change any metadata except for the name and additional data.
Entity
An Entity
is an instance of an entity, containing the entity's metadata (excluding the entity's value), which includes:
- id
- device key: If it is an entity of a device, it will include the device's key, with rules detailed in the Key Coding Concept Introduction chapter.
- integration ID
- entity name
- access permissions: Relevant only for Property type entities, with options for read-only, write-only, or read-write access.
- identifier: Entity identifier, with identifier rules detailed in the Key Coding Concept Introduction chapter.
- entity value type: Includes: STRING, LONG, DOUBLE, BOOLEAN, BINARY, OBJECT
- entity type: Includes: Property entity, Event entity, Service entity
- entity attributes: Attributes of the entity, such as unit, precision, maximum value, minimum value, maximum length, minimum length, enumeration, etc.
- sub-entities: Sub-entities of the entity, currently supporting up to two layers of relationships.
- key: Entity key, with key rules detailed in the Key Coding Concept Introduction chapter.
Once an entity is saved, it is not recommended to change any metadata except for the name and entity attributes.
Object Construction
This section will introduce methods for constructing devices and entities.
Construction Using Annotations
Annotation Description
Class Annotations
@IntegrationEntities
: Indicates that the current class is an integration entity class.@DeviceTemplateEntities
: Indicates that the current class is a device entity template class.name
: Device template name
@DeviceEntities
: Indicates that the current class is a device entity class.identifier
: Device identifiername
: Device nameadditional
: Additional device data, declared through the @KeyValue annotation
@Entities
: Indicates that the current class is a sub-entity class.
Field Annotations
@Entity
: Indicates the current property as an entitytype
: Entity typeEntityType
, including: Property Entity, Event Entity, Service Entityname
: Entity nameidentifier
: Entity identifierattributes
:@Attribute
annotation declares entity attributes, including units, precision, maximum value, minimum value, maximum length, minimum length, enumeration format, etc.accessMod
: Entity access modeAccessMod
, meaningful only for Property type entities, including: Read-only, Write-only, Read-Writevisible
: Specifies whether the entity is visible to the user. Some entities used for internal integration management, such as service entities for adding and deleting devices, do not need to be visible to the user.
@Attribute
: Entity attribute annotationenumClass
: Enumeration classunit
: UnitfractionDigits
: Precisionmax
: Maximum valuemin
: Minimum valuemaxLength
: Maximum lengthminLength
: Minimum lengthformat
: Format
Constructing Integration Entities
- Defining Integration Entities
@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;
}
}
- By default, the @Entity annotation uses the object field name (converted from camelCase to snake_case) as the entity name and identifier. Developers can customize the entity name and identifier using the name and identifier attributes.
- When subscribing to entity events, the annotated entity object can also be used to receive ExchangePayload event data. This allows developers to access attribute values through Getter methods, thereby simplifying code development. Refer to the Event Subscription chapter for more details. Note that when receiving event data, the entity object must extend the ExchangePayload class and include corresponding Getter methods for entity attributes.
- Defining Integration Sub-Entities
@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;
}
}
The definition method for device sub-entities is the same as for integration sub-entities. Add the @Entities annotation to the sub-entity class to complete the construction. Sub-entities cannot contain further sub-entities. Sub-entities inherit the attributes of the parent entity by default, meaning that the type does not need to be set for sub-entities. The data type of the parent entity is OBJECT
.
Defining Device Entity Templates
In practical scenarios, an integration may include many devices of the same type, each containing the same kinds of entities. For example, an integration might interface with multiple environmental sensors, each having its own temperature and humidity entities.
@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;
}
Defining Device Entities
@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;
}
When the entity class is a device entity, the Beaver IoT platform will initialize this device and add it to the database.
Programmatic Construction
The Beaver IoT platform provides DeviceBuilder
and EntityBuilder
classes, allowing developers to programmatically construct devices, entities, and other objects.
Constructing Integration Entities
- Without Sub-Entities
Entity entityConfig = new EntityBuilder(integrationId) // Set integration identifier
.identifier("webhookStatus") // Set entity identifier
.property("webhookStatus", AccessMod.R) // as property entity
// .service("accessKey") // as service entity
// .event("accessKey") // as event entity
.attributes(new AttributeBuilder().maxLength(300).enums(IntegrationStatus.class).build()) // Set entity attributes, or use attributes(Supplier<Map<String, Object>> supplier) method
.valueType(EntityValueType.STRING) // Set entity value type
.build();
- With Sub-Entities
- Example 1
- Example 2
- Example 3
// Example 1: Construct sub-entities using EntityBuilder's children() method
Entity entityConfig = new EntityBuilder(integrationId)
.identifier("settings")
.property("settings", AccessMod.RW)
.valueType(EntityValueType.STRING)
.children() // Set sub-entity
.valueType(EntityValueType.STRING).property("accessKey", AccessMod.RW).end()
.children()
.valueType(EntityValueType.STRING).property("secretKey", AccessMod.RW).end()
.build();
// Example 2: Set sub-entities using EntityBuilder's children(Supplier<List<Entity>> supplier) method
Entity parentEntity = new EntityBuilder(integrationId)
.identifier("settings")
.property("settings", AccessMod.RW)
.valueType(EntityValueType.STRING)
.children(() -> {
Entity childEntity = new EntityBuilder() // Define sub-entity
.identifier("accessKey")
.property("accessKey", AccessMod.RW)
.valueType(EntityValueType.STRING)
.build();
return List.of(childEntity);
}) // Set sub-entity, can be List<Entity> or a single entity
.build();
// Example 3: Set sub-entities using EntityBuilder's children(List<Entity> entities) method
Entity childEntity = new EntityBuilder() // Define sub-entity
.identifier("accessKey")
.property("accessKey", AccessMod.RW)
.valueType(EntityValueType.STRING)
.build();
Entity parentEntity = new EntityBuilder(integrationId)
.identifier("settings")
.property("settings", AccessMod.RW)
.valueType(EntityValueType.STRING)
.children(childEntity) // Set sub-entity, can be List<Entity> or a single entity
.build();
Constructing Devices and Entities
- Constructing a Device
Device device = new DeviceBuilder(integrationConfig.getId())
.name("deviceDemo")
.identifier("deviceDemoIdentifier")
.additional(Map.of("sn", "demoSN"))
.build();
- Constructing Device Entities
- Example 1 (Recommended)
- Example 2
- Example 3
- Example 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();
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();
Constructing Entity Attributes
The Beaver IoT platform provides the AttributeBuilder
class, allowing developers to programmatically construct entity attributes. The platform currently supports attributes such as unit, precision, maximum value, minimum value, maximum length, minimum length, enumeration format, etc., and also allows developers to define custom attributes. For example:
Map<String, Object> attributes = new AttributeBuilder()
.unit("s") // Unit in seconds
.fractionDigits(0) // Precision to 0 decimal places, indicating seconds
.min(0.0) // Set a reasonable minimum value (adjust as needed)
.maxLength(10) // Set a reasonable maximum length (adjust as needed)
.minLength(1) // Set a reasonable minimum length (adjust as needed)
.format("yyyy-MM-dd HH:mm:ss") // Time format precise to seconds
.build();
Adding/Removing Device Entities
Adding or removing devices in the Beaver IoT platform involves two special service-type entities. To support adding or removing devices, you need to define these entities and handle their event calls, then explicitly include these entities in the Integration Definition.
Adding a Device
For the add device event, the platform will carry the device name device_name
in the ExchangePayload context. Developers can retrieve this from the ExchangePayload context or implement the AddDeviceAware
interface to obtain the new device information.
- Method 1 (Recommended)
- Method 2
- Define Entity
@Data
@EqualsAndHashCode(callSuper = true)
@Entities
public static class AddDevice extends ExchangePayload implements AddDeviceAware {
@Entity
private String ip;
}
- Retrieve Device Name
@EventSubscribe(payloadKeyExpression = "my-integration.integration.add_device.*")
public void onAddDevice(Event<MyIntegrationEntities.AddDevice> event) {
String deviceName = event.getPayload().getAddDeviceName();
...
}
- Retrieve Device Name
@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);
...
}
Removing a Device
For the remove device event, the platform will carry the deleted device device
in the ExchangePayload context. Developers can retrieve this from the ExchangePayload context or implement the DeleteDeviceAware
interface to obtain the deleted device information.
- Method 1 (Recommended)
- Method 2
- Define Device Deletion Entity
@Data
@EqualsAndHashCode(callSuper = true)
@Entities
public static class DeleteDevice extends ExchangePayload implements DeleteDeviceAware {
// Should be empty
}
The device deletion entity should not contain any entities, meaning this class should be empty.
- Retrieve Deleted Device
@EventSubscribe(payloadKeyExpression = "my-integration.integration.delete_device")
public void onDeleteDevice(Event<MyIntegrationEntities.DeleteDevice> event) {
Device device = event.getPayload().getDeletedDevice();
...
}
- Retrieve Deleted Device
@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-Based Construction
To facilitate development, the Beaver IoT platform also supports constructing devices and entities using YAML. Developers can define devices, entities, and other objects in the integration's YAML file, and the platform will load and initialize the corresponding entities and integrations upon startup.
integration:
my-integration: # Integration identifier
# ...
initial-entities: # Initial entities
- identifier: 'connect' # Entity identifier
name: connect # Entity name
value_type: string # 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-based construction is suitable for simple scenarios. In general, it is not recommended for complex logic as YAML-defined devices and entities can be difficult to integrate with business code.
Choosing the Appropriate Construction Method
Some construction methods will automatically save to the database when Beaver IoT loads the integration, while others require manual saving of the constructed devices and entities.
Methods for Constructing Devices and Their Entities
Device Quantity | Device Type* | Auto Save on Load | Recommended Method |
---|---|---|---|
Limited | Limited | Yes | @DeviceEntities / YAML |
Dynamic | Limited | No | @DeviceTemplateEntities |
Dynamic | Dynamic | No | Programmatic Construction |
*Device type includes the basic definition of the device (e.g., device name, additional data) as well as the entity definition of the device.
Methods for Constructing Integration Entities
Current Integration Entities | Auto Save on Load | Recommended Method |
---|---|---|
Limited | Yes | @IntegrationEntities / YAML |
Dynamic | No | Programmatic Construction |
Saving Devices/Entities
Saving Devices or Entities
Saving Devices
- Refer to the documentation for methods.
- Saving a device will save both the device's own data and the data of the entities belonging to the device.
Saving Entities
- Refer to the documentation for methods.
- Saving an entity will save both the entity's own data and its sub-entities' data.
- Saving an entity does not save the entity's value, only the entity's metadata.
Saving/Retrieving Entity Values
Saving via Entity Wrapper
Standard Entity Classes
- Integration entity classes defined with
@IntegrationEntities
- Device entity classes defined with
@DeviceEntities
- Sub-entity classes defined with
@Entities
under the above two definitions
You can use AnnotatedEntityWrapper
to update the values of these entities.
For example, if we have defined an integration entity MyIntegrationEntities
as follows:
@Data
@EqualsAndHashCode(callSuper = true)
@IntegrationEntities
public class MyIntegrationEntities extends ExchangePayload {
@Entity
private String entity1;
@Entity
private String entity2;
}
You can then create the corresponding Wrapper:
AnnotatedEntityWrapper<MyIntegrationEntities> wrapper = new AnnotatedEntityWrapper<>();
To update the value of entity1
to sample
, you can use the saveValue
method:
wrapper.saveValue(MyIntegrationEntities::getEntity1, "sample").publishSync();
To update the values of both entity1
and entity2
to sample
, use the saveValues
method:
wrapper.saveValues(Map.of(
MyIntegrationEntities::getEntity1, "sample",
MyIntegrationEntities::getEntity2, "sample"
)).publishSync();
Both saveValue
and saveValues
return an instance of the ExchangeEventPublisher
class. Typically, saving a value necessitates the publication of a corresponding event to inform other subscribers (including other integrations and internal methods within Beaver IoT) of the change in entity values.
The methods ExchangeEventPublisher.publishSync
and ExchangeEventPublisher.publishAsync
pertain to synchronous and asynchronous event publishing, respectively. For detailed distinctions, please refer to the documentation.
To retrieve the value of entity1
, use the getValue
method:
String value = (String) wrapper.getValue(MyIntegrationEntities::getEntity1);
To retrieve the values of both entity1
and entity2
, use the getValues
method:
Map<String, Object> values = wrapper.getValues(MyIntegrationEntities::getEntity1, MyIntegrationEntities::getEntity2);
Device Template Entity Classes
- Integration entity classes defined with
@DeviceTemplateEntities
For example, if we define a device entity template:
@Data
@EqualsAndHashCode(callSuper = true)
@DeviceTemplateEntities(name = "Template Device")
public class MyDeviceEntities extends ExchangePayload {
@Entity
private String entity1;
}
You can create the corresponding Wrapper:
AnnotatedTemplateEntityWrapper<MyDeviceEntities> wrapper = new AnnotatedEntityWrapper<>(device.getIdentifier());
To update the value of the device entity entity1
to sample
, use the saveValue
method:
wrapper
.saveValue(MyDeviceEntities::getEntity1, "sample")
.publishSync();
Similar to AnnotatedEntityWrapper
:
- Use the
saveValues
method to update multiple values simultaneously. - Use the
getValue
andgetValues
methods to retrieve single or multiple values.
Entity Instance
To update the value of an entity instance entity
to sample
, use:
new EntityWrapper(entity)
.saveValue("sample")
.publishSync();
Building Exchange via Entity Key
Another flexible approach is to directly build an ExchangePayload using the entity key and then save it. ExchangePayload
provides a create
method to instantiate from a Map object.
To build a payload for updating a single entity:
ExchangePayload exchangePayload = ExchangePayload.create("<entityKey>", "<entityValue>");
To build a payload for updating multiple entities:
ExchangePayload exchangePayload = ExchangePayload.create(Map.of(
"<entityKey1>", "<entityValue1>",
"<entityKey2>", "<entityValue2>",
"<entityKey3>", "<entityValue3>"
// ...
));
Finally, save the payload:
entityValueServiceProvider.saveValuesAndPublishSync(exchangePayload)
Retrieving Values via Entity Key
To retrieve the value of a single entity:
entityValueServiceProvider.findValueByKey("<entityKey>")
To retrieve the values of multiple entities:
entityValueServiceProvider.findValuesByKeys(List.of("<entityKey1>", "<entityKey2>"))
findValueByKey
and findValuesByKeys
methods only retrieve the current values of the entities. They do not retrieve the values of any child entities.
To retrieve the values of child entities under a parent entity:
entityValueServiceProvider.findValuesByKey("<parentEntityKey>", ParentEntity.class)