The EMF data model¶
The sensiNact data model is defined in EMF and has a number of key parts:
The Provider¶
The Provider is the top part of the sensiNact EMF Model, and represents the template for a provider in the digital twin. All providers have a number of common attributes.
Inheritance¶
All providers are instances of the Provider interface, providing direct access to the provider id and the admin service
The Admin Service¶
The admin service is a special service of type Admin which extends Service and provides easy access to the built in resources such as friendlyName and location. All providers always have an admin service.
Services¶
The provider model class uses EReferences of type Service for each of the services in the model.
Resources¶
Each Service uses EAttributes for each data resource in the model. The type of the EAttribute is the type of the resource. Action resources are represented using an EOperation where the parameter types of the operation represent the parameters to the action, and the return type of the EOperation provides the type of the action resource.
Multiple Resources¶
Multiple resources are represented in the EMF model using the upperBound of the EAtrribute. Typically this will be set to 1 (representing a single-valued resource) or -1 (representing an unlimited multi-valued resource). It is valid to use other values for the upperBound, as well as using the lowerBound for the EAttribute. If these constraints are set then they will be enforced by the model, for example it is possible to require that a resource has exactly two values set and to reject any update that does not contain exactly two values.
Variable metadata¶
Every resource in sensiNact has associated metadata. This metadata is stored in a Metadata object at the Service level. The Service uses an EMap where the key is the ETypedElement representing the EAttribute or EOperation for the resource, and the value is the Metadata object. The Metadata object has a timestamp which represents the most recent update time for the resource (i.e. the most recent update to the resource value or resource metadata). The Metadata also provides a list of FeatureCustomMetadata representing the individual metadata values for the resource. Each FeatureCustomMetadata has a name, value and timestamp representing the metadata value and when it was last updated.
Important
The timestamp is used to determine the last update time for the resource. If the resource has never been set then there will be no timestamp set. Also, the timestamp represents the time of the last update, which may be later than the timestamps for each individual metadata timestamp if, for example, the most recent change was to delete a metadata value.
Annotated metadata¶
Provider model classes are annotated with an EAnnotation named model, the value of the name property in the annotation details is the name of the model.
Linked Providers¶
The Provider type includes a collection reference which can be used to link to other providers. This can be used to assemble a provider from other providers by listing them as links.
Linking a provider B into provider A does not cause any of the linked provider B’s services or resources to become directly visible in provider A, however it does change the result of a GET for the admin/location resource. The location for a provider with links is calculated as follows:
A GeoJSON Feature Collection object is created to hold the location
The provider’s location (if set) is added to the feature collection
If the location is a Geometry type then it is wrapped in a feature with the property
sensinact.provider.idset to the id of the providerIf the location is a Feature then it is used directly, with an additional property
sensinact.provider.idset to the id of the providerIf the location is a FeatureCollection then it is unwrapped and each feature is added to the created Feature Collection, with an additional property
sensinact.provider.idset to the id of the provider in each feature
For each linked provider get the location (if set) and repeat step 2. If at any point a cycle is detected then the link causing the cycle is skipped.
Creating a custom model¶
Creating a custom EMF model allows you to define structured, typed data models for your sensiNact providers. This is particularly useful when you want to enforce strict schemas, reuse model definitions, or work with existing EMF models from other systems.
Using an existing EMF model¶
If you already have an EMF model (e.g., from Eclipse Ecore Designer or another EMF-based system), you can use it directly with sensiNact. There are several approaches depending on whether your provider model is static (predefined services) or dynamic (services created at runtime).
Static providers with EMF models¶
For providers with a fixed, predefined structure, you can reference the EMF model classes directly using annotations:
@Provider("temperatureSensor1")
public class TemperatureSensorDTO {
@Model
public EClass providerModel = MyModelPackage.Literals.TEMPERATURE_SENSOR;
@Service
public EReference serviceRef = MyModelPackage.Literals.TEMPERATURE_SENSOR__TEMPERATURE;
@Resource("value")
@Data
public Double temperature;
@Timestamp(ChronoUnit.MILLIS)
public long timestamp;
}
In this example:
The
@Modelannotation specifies which EClass defines the provider structureThe
@Serviceannotation with anEReferenceindicates which service to use (the reference must exist in the provider model)The resource is mapped to the
temperatureattribute defined in the EMF model
Dynamic providers with EMF models¶
For dynamic providers (where services can be created at runtime), you need to specify the service structure. You can do this using either EClass references or String-based service model names.
Using EClass for service models:
@Provider("sensor1")
public class DynamicSensorDTO {
@Model
public EClass providerModel = MyModelPackage.Literals.DYNAMIC_SENSOR;
@ServiceModel
public EClass serviceType = MyModelPackage.Literals.TEMPERATURE_SERVICE;
@Service
public String serviceName = "temperature";
@Resource("value")
@Data
public Double value;
@Timestamp(ChronoUnit.MILLIS)
public long timestamp;
}
Using String for service models:
For convenience, you can also specify the service model by its EClass name as a String:
@Provider("sensor1")
public class DynamicSensorDTO {
@Model
public EClass providerModel = MyModelPackage.Literals.DYNAMIC_SENSOR;
@ServiceModel
public String serviceModel = "TemperatureService";
@Service
public String serviceName = "temperature";
@Resource("value")
@Data
public Double value;
@Timestamp(ChronoUnit.MILLIS)
public long timestamp;
}
The String-based approach is particularly useful when:
You want to keep your DTOs independent of EMF model dependencies
You’re generating DTOs dynamically or from configuration
You want simpler, more readable code
Important
When using @ServiceModel with a String value, sensiNact will look up the EClass by name within the provider’s model package. The String must exactly match the name of an EClass defined in your EMF model.
Understanding @Service vs @ServiceModel¶
It’s important to understand the difference between these two annotations:
@Service: Specifies the name of the service instance (e.g., “temperature”, “humidity”). For static providers, it can also be anEReferencepointing to a specific service in the provider model.@ServiceModel: Specifies the type of service to create (the EClass that defines the service structure). This is only needed for dynamic providers where services are created at runtime. It can be either anEClassreference or a String containing the EClass name.
Example combining both:
@Provider("weatherStation")
public class WeatherUpdateDTO {
@Model
public EClass providerModel = WeatherPackage.Literals.WEATHER_STATION;
// Define the type of service to create
@ServiceModel
public String serviceModel = "EnvironmentalSensor";
// Define the name of the service instance
@Service
public String serviceName = "outdoor";
@Resource("temperature")
@Data
public Double temp;
}
This creates a service named “outdoor” with the structure defined by the “EnvironmentalSensor” EClass.
Dynamic vs Static Providers¶
The key difference between dynamic and static providers in EMF models:
Static Providers:
Services are defined as
EReferencefields in the provider’s EClassThe structure is fixed and cannot be modified at runtime
Use
@Servicewith anEReferenceto reference existing servicesExample:
TestSensorwith predefinedtempandadminservices
Dynamic Providers:
Services can be created at runtime
The provider’s EClass has an
EMap<String, Service>to hold servicesUse
@ServiceModelto specify what type of service to createUse
@Serviceto specify the name of the service instanceExample:
DynamicTestSensorthat can have services added dynamically
Note
Attempting to use @ServiceModel with a static (non-dynamic) provider will result in an error, as static providers cannot have services created at runtime.
Deploying EMF models in OSGi¶
When deploying custom EMF models in an OSGi environment (which sensiNact runs in), you need to ensure your EMF model is properly registered. The recommended approach uses the Gecko EMF OSGi code generator, which automatically generates all the required registration code from your EMF model.
Using the Gecko EMF Code Generator¶
The easiest way to deploy an EMF model is to use the BND Gecko EMF code generator. This automatically generates:
The model implementation classes (EPackage, EFactory, etc.)
OSGi service registration components
Proper lifecycle management code
Step 1: Add the BND Generate Plugin
Add the bnd-generate-maven-plugin to your project’s pom.xml:
<build>
<plugins>
<plugin>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd-generate-maven-plugin</artifactId>
<version>${bnd.version}</version>
<configuration>
<externalPlugins>
<dependency>
<groupId>org.geckoprojects.emf</groupId>
<artifactId>org.gecko.emf.osgi.codegen</artifactId>
</dependency>
</externalPlugins>
<steps>
<step>
<trigger>src/main/resources/model/mymodel.genmodel</trigger>
<generateCommand>geckoEMF</generateCommand>
<output>src/main/java</output>
<clear>false</clear>
<properties>
<genmodel>src/main/resources/model/mymodel.genmodel</genmodel>
</properties>
</step>
</steps>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Step 2: Add Required Dependencies
Add the required dependencies for EMF model development:
<dependencies>
<!-- SensiNact Provider Model - Required for code generation -->
<dependency>
<groupId>org.eclipse.sensinact.gateway.core.models</groupId>
<artifactId>provider</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Gecko EMF OSGi API -->
<dependency>
<groupId>org.geckoprojects.emf</groupId>
<artifactId>org.gecko.emf.osgi.api</artifactId>
</dependency>
</dependencies>
Important
The sensiNact core provider model dependency is required for the code generator to work correctly. It provides the base Provider, DynamicProvider, and Service EClasses that your model must extend.
Step 3: Create Your EMF Model
Create your EMF model using the Eclipse Ecore Designer or any EMF tooling. Your model must follow these structural requirements:
Model Structure Requirements:
Provider Classes - Your provider EClass must extend one of:
Provider(fromhttp://eclipse.org/sensinact/core/provider/1.0) - For static providers with predefined servicesDynamicProvider(fromhttp://eclipse.org/sensinact/core/provider/1.0) - For providers where services can be created at runtime
Service Classes - All service EClasses must extend:
Service(fromhttp://eclipse.org/sensinact/core/provider/1.0) - Base class for all services
Example EMF Model Structure:
MyModel.ecore
├── MySensor (extends Provider)
│ ├── temperature : EReference (type: TemperatureService)
│ └── humidity : EReference (type: HumidityService)
│
├── TemperatureService (extends Service)
│ └── value : EAttribute (type: EDouble)
│
└── HumidityService (extends Service)
└── value : EAttribute (type: EDouble)
For dynamic providers:
MyModel.ecore
└── MyDynamicSensor (extends DynamicProvider)
└── services : EMap<String, Service> (inherited from DynamicProvider)
Ensure you have:
An
.ecorefile defining your model structure with proper inheritanceA
.genmodelfile configuring code generationReferences to the sensiNact provider model package (
http://eclipse.org/sensinact/core/provider/1.0)
Step 4: Generate the Code
Run Maven to generate the model implementation and OSGi registration code:
mvn clean generate-sources
The generator will create:
Model classes in
src/main/javaA
configurationpackage with:[YourModel]EPackageConfigurator- Handles EPackage registration[YourModel]ConfigurationComponent- OSGi component for lifecycle management
Step 5: Use the Model
Once generated and built, you can reference your model’s EClasses in your data transfer objects as shown in the examples above. The OSGi service registry will ensure your model is available to sensiNact and other bundles.
Tip
The generated code follows best practices for OSGi lifecycle management, including proper service registration and cleanup in the @Activate and @Deactivate methods.
Manual Registration (Advanced)¶
Warning
Manual registration is only needed if you cannot use the code generator. The generator is the recommended approach as it ensures all components are correctly configured.
If you must manually register your EMF model, you need to create two classes:
EPackage Configurator - Implements
org.gecko.emf.osgi.configurator.EPackageConfiguratorconfigureEPackage(): Registers the EPackage in the EMF registryunconfigureEPackage(): Unregisters on deactivationgetServiceProperties(): Provides service metadata (model name, NS URI, version)
Configuration Component - OSGi Declarative Services component
Annotated with
@Component@Activatemethod: Registers EPackage, EFactory, and EPackageConfigurator services@Deactivatemethod: Unregisters all services and removes from registry
The generator creates these files in a configuration package. You can examine the generated code in core/models/testdata/src/main/java/org/eclipse/sensinact/model/core/testdata/configuration/ for a complete reference implementation.
Required bundle imports:
Import-Package: org.eclipse.emf.ecore,
org.eclipse.emf.common.util,
org.gecko.emf.osgi.configurator,
org.gecko.emf.osgi.constants,
org.osgi.framework,
org.osgi.service.component.annotations
Best practices¶
When working with deployed EMF models:
Model reusability: Define service types (EClasses) that can be reused across different providers
Type safety: Use EClass references during development for compile-time safety, but consider String-based references for configuration-driven systems
Documentation: Document your EMF model’s EClasses and their intended usage
Validation: EMF models can include validation rules that sensiNact will respect
Versioning: Plan for model evolution by using EMF’s built-in versioning and migration support