Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/fallback to generic device creation in case the sensor is not found #98

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<groupId>de.app.5gla</groupId>
<artifactId>api</artifactId>
<version>10.0.0</version>
<version>10.1.0</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
Expand Down Expand Up @@ -80,7 +80,7 @@
<dependency>
<groupId>de.app.5gla</groupId>
<artifactId>fiware-integration-layer</artifactId>
<version>6.1.0</version>
<version>6.2.0</version>
<exclusions>
<!-- SLF4J NOP -->
<exclusion>
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/de/app/fivegla/api/GlobalDefinitions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package de.app.fivegla.api;

/**
* This interface contains global definitions for the application.
*/
public interface GlobalDefinitions {
/**
* The INSTANT_JSON_PATTERN is a string constant that represents the pattern used to format
* and parse Instant objects in JSON format.
* <p>
* The pattern is defined as "yyyy-MM-dd'T'HH:mm:ss.SSS", where:
* - "yyyy" represents the year using four digits
* - "MM" represents the month using two digits
* - "dd" represents the day using two digits
* - "T" is a literal 'T' character, used to separate the date and time
* - "HH" represents the hour using two digits in the 24-hour format
* - "mm" represents the minute using two digits
* - "ss" represents the second using two digits
* - "SSS" represents the milliseconds using three digits
* <p>
* This pattern is commonly used in JSON APIs for representing Instant objects in a consistent manner.
* It can be used to format Instant objects into strings or parse strings into Instant objects.
* <p>
* Example usage:
* <pre>{@code
* String instantString = "2022-12-31T23:59:59.999";
*
* DateTimeFormatter formatter = DateTimeFormatter.ofPattern(INSTANT_JSON_PATTERN);
* Instant instant = Instant.parse(instantString, formatter);
*
* String formattedInstant = formatter.format(instant);
* System.out.println(formattedInstant);
* }</pre>
*/
String INSTANT_JSON_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import de.app.fivegla.controller.dto.request.SentekDataLoggingRequest;
import de.app.fivegla.controller.dto.request.WeenatDataLoggingRequest;
import de.app.fivegla.controller.security.SecuredApiAccess;
import de.app.fivegla.fiware.DeviceIntegrationService;
import de.app.fivegla.integration.agvolution.AgvolutionFiwareIntegrationServiceWrapper;
import de.app.fivegla.integration.agvolution.AgvolutionSensorIntegrationService;
import de.app.fivegla.integration.sentek.SentekFiwareIntegrationServiceWrapper;
Expand Down Expand Up @@ -41,18 +42,21 @@ public class DeviceMeasurementController implements SecuredApiAccess {
private final AgvolutionSensorIntegrationService agvolutionSensorIntegrationService;
private final AgvolutionFiwareIntegrationServiceWrapper agvolutionFiwareIntegrationServiceWrapper;

private final DeviceIntegrationService deviceIntegrationService;

public DeviceMeasurementController(SentekSensorIntegrationService sentekSensorIntegrationService,
SentekFiwareIntegrationServiceWrapper sentekFiwareIntegrationServiceWrapper,
WeenatPlotIntegrationService weenatPlotIntegrationService,
WeenatFiwareIntegrationServiceWrapper weenatFiwareIntegrationServiceWrapper,
AgvolutionSensorIntegrationService agvolutionSensorIntegrationService,
AgvolutionFiwareIntegrationServiceWrapper agvolutionFiwareIntegrationServiceWrapper) {
AgvolutionFiwareIntegrationServiceWrapper agvolutionFiwareIntegrationServiceWrapper, DeviceIntegrationService deviceIntegrationService) {
this.sentekSensorIntegrationService = sentekSensorIntegrationService;
this.sentekFiwareIntegrationServiceWrapper = sentekFiwareIntegrationServiceWrapper;
this.weenatPlotIntegrationService = weenatPlotIntegrationService;
this.weenatFiwareIntegrationServiceWrapper = weenatFiwareIntegrationServiceWrapper;
this.agvolutionSensorIntegrationService = agvolutionSensorIntegrationService;
this.agvolutionFiwareIntegrationServiceWrapper = agvolutionFiwareIntegrationServiceWrapper;
this.deviceIntegrationService = deviceIntegrationService;
}

/**
Expand Down Expand Up @@ -83,7 +87,15 @@ public ResponseEntity<String> sentek(@PathVariable @Parameter(description = "The
log.info("Persisting {} measurements for sensor {}.", request.getReadings().size(), sensorId);
sentekFiwareIntegrationServiceWrapper.persist(sensor, request.getReadings());
dataHasBeenLogged.set(true);
}, () -> log.error("No sensor found for id {}.", sensorId));
}, () -> {
log.warn("No sensor found for id {}.", sensorId);
log.info("Now looking for a generic sensor for id {}.", sensorId);
deviceIntegrationService.read(sentekFiwareIntegrationServiceWrapper.deviceIdOf(sensorId)).ifPresentOrElse(device -> {
log.info("Persisting {} measurements for sensor {}.", request.getReadings().size(), sensorId);
sentekFiwareIntegrationServiceWrapper.persist(device, request.getReadings());
dataHasBeenLogged.set(true);
}, () -> log.error("No sensor found for id {}.", sensorId));
});

if (dataHasBeenLogged.get()) {
return ResponseEntity.status(HttpStatus.CREATED).build();
Expand Down Expand Up @@ -126,8 +138,19 @@ public ResponseEntity<String> weenat(@PathVariable @Parameter(description = "The
log.info("Persisting {} measurements for plot {}.", request.getMeasurements().size(), plotId);
weenatFiwareIntegrationServiceWrapper.persist(plot, Measurements.builder().measurements(request.getMeasurements()).plot(plot).build());
dataHasBeenLogged.set(true);
}, () -> log.error("No plot found for id {}.", plotId));

}, () -> {
log.warn("No sensor found for id {}.", plotId);
log.info("Now looking for a generic sensor for id {}.", plotId);
deviceIntegrationService.read(weenatFiwareIntegrationServiceWrapper.deviceIdOf(plotId)).ifPresentOrElse(device -> {
log.info("Persisting {} measurements for plot {}.", request.getMeasurements().size(), plotId);
var plot = weenatPlotIntegrationService.plotFromDevice(device);
weenatFiwareIntegrationServiceWrapper.persist(plot, Measurements.builder()
.measurements(request.getMeasurements())
.plot(plot).build());
dataHasBeenLogged.set(true);
dataHasBeenLogged.set(true);
}, () -> log.error("No sensor found for id {}.", plotId));
});
if (dataHasBeenLogged.get()) {
return ResponseEntity.status(HttpStatus.CREATED).build();
} else {
Expand Down Expand Up @@ -169,7 +192,15 @@ public ResponseEntity<String> agvolution(@PathVariable @Parameter(description =
log.info("Persisting {} measurements for device {}.", request.getSeriesEntry().getTimeSeriesEntries().size(), deviceId);
agvolutionFiwareIntegrationServiceWrapper.persist(request.getSeriesEntry());
dataHasBeenLogged.set(true);
}, () -> log.error("No device found for id {}.", deviceId));
}, () -> {
log.warn("No sensor found for id {}.", deviceId);
log.info("Now looking for a generic sensor for id {}.", deviceId);
deviceIntegrationService.read(agvolutionFiwareIntegrationServiceWrapper.deviceIdOf(deviceId)).ifPresentOrElse(device -> {
log.info("Persisting {} series entries for device {}.", request.getSeriesEntry().getTimeSeriesEntries().size(), deviceId);
agvolutionFiwareIntegrationServiceWrapper.persist(request.getSeriesEntry());
dataHasBeenLogged.set(true);
}, () -> log.error("No sensor found for id {}.", deviceId));
});

if (dataHasBeenLogged.get()) {
return ResponseEntity.status(HttpStatus.CREATED).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public AgvolutionFiwareIntegrationServiceWrapper(DeviceIntegrationService device

public void persist(SeriesEntry seriesEntry) {
try {
persist(seriesEntry.getDeviceId());
persist(seriesEntry.getDeviceId(), seriesEntry.getLatitude(), seriesEntry.getLongitude());
seriesEntry.getTimeSeriesEntries().forEach(timeSeriesEntry -> {
var deviceMeasurements = createDeviceMeasurements(seriesEntry, timeSeriesEntry);
log.info("Persisting measurement for device: {}", seriesEntry.getDeviceId());
Expand All @@ -61,12 +61,16 @@ public void persist(SeriesEntry seriesEntry) {
}
}

private void persist(String deviceId) {
private void persist(String deviceId, double latitude, double longitude) {
var device = Device.builder()
.id(FiwareDeviceId.create(getManufacturerConfiguration(), deviceId))
.manufacturerSpecificId(deviceId)
.deviceCategory(DeviceCategory.builder()
.value(List.of(getManufacturerConfiguration().getKey()))
.build())
.location(Location.builder()
.coordinates(List.of(latitude, longitude))
.build())
.build();
deviceIntegrationService.persist(device);
fiwareEntityMonitor.sensorsSavedOrUpdated(Manufacturer.AGVOLUTION);
Expand Down Expand Up @@ -96,4 +100,14 @@ private List<DeviceMeasurement> createDeviceMeasurements(SeriesEntry seriesEntry
private CommonManufacturerConfiguration getManufacturerConfiguration() {
return applicationConfiguration.getSensors().agvolution();
}

/**
* Retrieves the unique device ID of the specified device.
*
* @param deviceId The unique identifier of the device.
* @return The device ID.
*/
public String deviceIdOf(String deviceId) {
return FiwareDeviceId.create(getManufacturerConfiguration(), deviceId);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.app.fivegla.integration.agvolution.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;

Expand All @@ -11,13 +12,22 @@
*/
@Getter
@Setter
@Schema(description = "Represents a device time-series entry.")
public class SeriesEntry {

@Schema(description = "The device ID.")
@JsonProperty("device")
private String deviceId;

@Schema(description = "The device type.")
@JsonProperty("lon")
private double longitude;

@Schema(description = "The device type.")
@JsonProperty("lat")
private double latitude;

@Schema(description = "The device type.")
@JsonProperty("timeseries")
private List<TimeSeriesEntry> timeSeriesEntries;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
package de.app.fivegla.integration.agvolution.model;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;

import java.util.List;

/**
* Represents an entry in a time series.
* The entry consists of a key, unit, aggregate, and a list of time series values.
*/
@Getter
@Setter
@Schema(description = "Represents an entry in a time series.")
public class TimeSeriesEntry {

@Schema(description = "The key of the entry.")
private String key;

@Schema(description = "The unit of the entry.")
private String unit;

@Schema(description = "The aggregate of the entry.")
private String aggregate;

@Schema(description = "The values of the entry.")
private List<TimeSeriesValue> values;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,44 @@
package de.app.fivegla.integration.agvolution.model;

import com.fasterxml.jackson.annotation.JsonFormat;
import de.app.fivegla.api.GlobalDefinitions;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;

import java.time.Instant;

/**
* Represents a value for a specific time in a time series.
* <p>
* This class is annotated with the Lombok's @Getter and @Setter annotations,
* which generate getters and setters for the private fields automatically.
* The class is also annotated with @Schema which can be used for generating
* documentation for API endpoints using Swagger or other similar frameworks.
* <p>
* An instance of this class contains information about the time and value of
* a specific data point in a time series. The time is represented as an
* Instant object using the UTC timezone. The value is a Double representing
* the numeric value associated with the time.
* <p>
* The time is formatted using the ISO 8601 standard date and time format,
* with milliseconds precision, and the UTC timezone.
* <p>
* Example usage:
* <p>
* TimeSeriesValue value = new TimeSeriesValue();
* value.setTime(Instant.now());
* value.setValue(42.0);
*/
@Getter
@Setter
@Schema(description = "Represents a value for a specific time in a time series.")
public class TimeSeriesValue {

@Schema(description = "The time of the value in the time series.")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = GlobalDefinitions.INSTANT_JSON_PATTERN, timezone = "UTC")
private Instant time;

@Schema(description = "The value of the value in the time series.")
private Double value;
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public void persist(Manufacturer manufacturer, String id, double latitude, doubl
.build();
var device = Device.builder()
.id(FiwareDeviceId.create(manufacturerConfiguration, id))
.manufacturerSpecificId(id)
.deviceCategory(DeviceCategory.builder()
.value(List.of(manufacturerConfiguration.getKey()))
.build())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
import de.app.fivegla.fiware.DeviceIntegrationService;
import de.app.fivegla.fiware.DroneDeviceMeasurementIntegrationService;
import de.app.fivegla.fiware.api.InstantFormatter;
import de.app.fivegla.fiware.model.Device;
import de.app.fivegla.fiware.model.DeviceCategory;
import de.app.fivegla.fiware.model.DeviceMeasurement;
import de.app.fivegla.fiware.model.DroneDeviceMeasurement;
import de.app.fivegla.fiware.model.*;
import de.app.fivegla.integration.micasense.model.MicaSenseImage;
import de.app.fivegla.monitoring.FiwareEntityMonitor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -53,12 +50,20 @@ public void createOrUpdateDevice(String droneId) {
.deviceCategory(DeviceCategory.builder()
.value(List.of(getManufacturerConfiguration().getKey()))
.build())
.location(fakeLocation())
.manufacturerSpecificId(droneId)
.id(fiwareId)
.build();
deviceIntegrationService.persist(device);
fiwareEntityMonitor.sensorsSavedOrUpdated(getManufacturerConfiguration().manufacturer());
}

private Location fakeLocation() {
return Location.builder()
.coordinates(List.of(0.0, 0.0))
.build();
}

/**
* Create a new drone device measurement in FIWARE.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,13 @@ public void persist(Logger logger, List<Reading> readings) {
private void persist(Logger logger) {
var device = Device.builder()
.id(FiwareDeviceId.create(getManufacturerConfiguration(), String.valueOf(logger.getId())))
.manufacturerSpecificId(String.valueOf(logger.getId()))
.deviceCategory(DeviceCategory.builder()
.value(List.of(getManufacturerConfiguration().getKey()))
.build())
.location(Location.builder()
.coordinates(List.of(logger.getLatitude(), logger.getLongitude()))
.build())
.build();
deviceIntegrationService.persist(device);
fiwareEntityMonitor.sensorsSavedOrUpdated(Manufacturer.SENTEK);
Expand All @@ -218,4 +222,27 @@ private CommonManufacturerConfiguration getManufacturerConfiguration() {
return applicationConfiguration.getSensors().sentek();
}

/**
* Persist the device readings to the logger.
*
* @param device The device object containing the ID and location coordinates.
* @param readings The list of readings to persist.
*/
public void persist(Device device, List<Reading> readings) {
var logger = new Logger();
logger.setId(Integer.parseInt(device.getManufacturerSpecificId()));
logger.setLatitude(device.getLocation().getCoordinates().get(0));
logger.setLongitude(device.getLocation().getCoordinates().get(1));
persist(logger, readings);
}

/**
* Retrieves the device ID for a given sensor ID.
*
* @param sensorId the ID of the sensor
* @return the device ID associated with the sensor ID
*/
public String deviceIdOf(int sensorId) {
return FiwareDeviceId.create(getManufacturerConfiguration(), String.valueOf(sensorId));
}
}
Loading