Skip to main content

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
tip

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.
tip

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 identifier
    • name: Device name
    • additional: 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 entity
    • type: Entity type EntityType, including: Property Entity, Event Entity, Service Entity
    • name: Entity name
    • identifier: Entity identifier
    • attributes: @Attribute annotation declares entity attributes, including units, precision, maximum value, minimum value, maximum length, minimum length, enumeration format, etc.
    • accessMod: Entity access mode AccessMod, meaningful only for Property type entities, including: Read-only, Write-only, Read-Write
    • visible: 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 annotation
    • enumClass: Enumeration class
    • unit: Unit
    • fractionDigits: Precision
    • max: Maximum value
    • min: Minimum value
    • maxLength: Maximum length
    • minLength: Minimum length
    • format: 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;
}
}
tip
  • 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;
}
}
tip

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;
}
tip

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: 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();

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
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();

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.

  • 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();
...
}
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.

  • Define Device Deletion Entity
@Data
@EqualsAndHashCode(callSuper = true)
@Entities
public static class DeleteDevice extends ExchangePayload implements DeleteDeviceAware {
// Should be empty
}
Note

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();
...
}

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
info

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 QuantityDevice Type*Auto Save on LoadRecommended Method
LimitedLimitedYes@DeviceEntities / YAML
DynamicLimitedNo@DeviceTemplateEntities
DynamicDynamicNoProgrammatic Construction
tip

*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 EntitiesAuto Save on LoadRecommended Method
LimitedYes@IntegrationEntities / YAML
DynamicNoProgrammatic 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();
info

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 and getValues 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>"))
warning

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)