Skip to content

Commit

Permalink
Feature/fallback to generic device creation in case the sensor is not…
Browse files Browse the repository at this point in the history
… found (#98)
  • Loading branch information
saschadoemer authored Nov 3, 2023
1 parent 3b5883f commit 77a49dc
Show file tree
Hide file tree
Showing 16 changed files with 291 additions and 43 deletions.
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

0 comments on commit 77a49dc

Please sign in to comment.