From 77a49dc6183314d5ac09b405039575091051bcd1 Mon Sep 17 00:00:00 2001 From: Sascha Doemer Date: Fri, 3 Nov 2023 08:40:39 +0100 Subject: [PATCH] Feature/fallback to generic device creation in case the sensor is not found (#98) --- pom.xml | 4 +- .../de/app/fivegla/api/GlobalDefinitions.java | 36 ++++++++++++++++ .../DeviceMeasurementController.java | 41 ++++++++++++++++--- ...lutionFiwareIntegrationServiceWrapper.java | 18 +++++++- .../agvolution/model/SeriesEntry.java | 10 +++++ .../agvolution/model/TimeSeriesEntry.java | 14 +++++++ .../agvolution/model/TimeSeriesValue.java | 31 ++++++++++++++ .../GenericDeviceIntegrationService.java | 1 + ...aSenseFiwareIntegrationServiceWrapper.java | 13 ++++-- ...SentekFiwareIntegrationServiceWrapper.java | 27 ++++++++++++ .../SentekSensorIntegrationService.java | 32 +++++++-------- ...WeenatFiwareIntegrationServiceWrapper.java | 20 +++++++-- .../WeenatMeasuresIntegrationService.java | 10 +++-- .../weenat/WeenatPlotIntegrationService.java | 17 ++++++++ .../integration/weenat/model/Measurement.java | 28 +++++++++++-- .../weenat/model/MeasurementValues.java | 32 +++++++++++++-- 16 files changed, 291 insertions(+), 43 deletions(-) create mode 100644 src/main/java/de/app/fivegla/api/GlobalDefinitions.java diff --git a/pom.xml b/pom.xml index 9f6126f1..b570bd99 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ de.app.5gla api - 10.0.0 + 10.1.0 17 @@ -80,7 +80,7 @@ de.app.5gla fiware-integration-layer - 6.1.0 + 6.2.0 diff --git a/src/main/java/de/app/fivegla/api/GlobalDefinitions.java b/src/main/java/de/app/fivegla/api/GlobalDefinitions.java new file mode 100644 index 00000000..96f4cfd0 --- /dev/null +++ b/src/main/java/de/app/fivegla/api/GlobalDefinitions.java @@ -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. + *

+ * 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 + *

+ * 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. + *

+ * Example usage: + *

{@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);
+     * }
+ */ + String INSTANT_JSON_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS"; +} diff --git a/src/main/java/de/app/fivegla/controller/DeviceMeasurementController.java b/src/main/java/de/app/fivegla/controller/DeviceMeasurementController.java index f8fd80be..9a52788d 100644 --- a/src/main/java/de/app/fivegla/controller/DeviceMeasurementController.java +++ b/src/main/java/de/app/fivegla/controller/DeviceMeasurementController.java @@ -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; @@ -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; } /** @@ -83,7 +87,15 @@ public ResponseEntity 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(); @@ -126,8 +138,19 @@ public ResponseEntity 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 { @@ -169,7 +192,15 @@ public ResponseEntity 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(); diff --git a/src/main/java/de/app/fivegla/integration/agvolution/AgvolutionFiwareIntegrationServiceWrapper.java b/src/main/java/de/app/fivegla/integration/agvolution/AgvolutionFiwareIntegrationServiceWrapper.java index bd333f36..98458a62 100644 --- a/src/main/java/de/app/fivegla/integration/agvolution/AgvolutionFiwareIntegrationServiceWrapper.java +++ b/src/main/java/de/app/fivegla/integration/agvolution/AgvolutionFiwareIntegrationServiceWrapper.java @@ -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()); @@ -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); @@ -96,4 +100,14 @@ private List 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); + } } diff --git a/src/main/java/de/app/fivegla/integration/agvolution/model/SeriesEntry.java b/src/main/java/de/app/fivegla/integration/agvolution/model/SeriesEntry.java index 90a7df7f..6e6f7e95 100644 --- a/src/main/java/de/app/fivegla/integration/agvolution/model/SeriesEntry.java +++ b/src/main/java/de/app/fivegla/integration/agvolution/model/SeriesEntry.java @@ -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; @@ -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 timeSeriesEntries; } diff --git a/src/main/java/de/app/fivegla/integration/agvolution/model/TimeSeriesEntry.java b/src/main/java/de/app/fivegla/integration/agvolution/model/TimeSeriesEntry.java index 81c072cd..74cd46ac 100644 --- a/src/main/java/de/app/fivegla/integration/agvolution/model/TimeSeriesEntry.java +++ b/src/main/java/de/app/fivegla/integration/agvolution/model/TimeSeriesEntry.java @@ -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 values; } diff --git a/src/main/java/de/app/fivegla/integration/agvolution/model/TimeSeriesValue.java b/src/main/java/de/app/fivegla/integration/agvolution/model/TimeSeriesValue.java index 7849f735..c7b7106e 100644 --- a/src/main/java/de/app/fivegla/integration/agvolution/model/TimeSeriesValue.java +++ b/src/main/java/de/app/fivegla/integration/agvolution/model/TimeSeriesValue.java @@ -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. + *

+ * 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. + *

+ * 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. + *

+ * The time is formatted using the ISO 8601 standard date and time format, + * with milliseconds precision, and the UTC timezone. + *

+ * Example usage: + *

+ * 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; } diff --git a/src/main/java/de/app/fivegla/integration/generic/GenericDeviceIntegrationService.java b/src/main/java/de/app/fivegla/integration/generic/GenericDeviceIntegrationService.java index b6ee86bb..c7e4feac 100644 --- a/src/main/java/de/app/fivegla/integration/generic/GenericDeviceIntegrationService.java +++ b/src/main/java/de/app/fivegla/integration/generic/GenericDeviceIntegrationService.java @@ -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()) diff --git a/src/main/java/de/app/fivegla/integration/micasense/MicaSenseFiwareIntegrationServiceWrapper.java b/src/main/java/de/app/fivegla/integration/micasense/MicaSenseFiwareIntegrationServiceWrapper.java index 1dc52bad..7477b1c9 100644 --- a/src/main/java/de/app/fivegla/integration/micasense/MicaSenseFiwareIntegrationServiceWrapper.java +++ b/src/main/java/de/app/fivegla/integration/micasense/MicaSenseFiwareIntegrationServiceWrapper.java @@ -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; @@ -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. * diff --git a/src/main/java/de/app/fivegla/integration/sentek/SentekFiwareIntegrationServiceWrapper.java b/src/main/java/de/app/fivegla/integration/sentek/SentekFiwareIntegrationServiceWrapper.java index 9a264108..51bb9a40 100644 --- a/src/main/java/de/app/fivegla/integration/sentek/SentekFiwareIntegrationServiceWrapper.java +++ b/src/main/java/de/app/fivegla/integration/sentek/SentekFiwareIntegrationServiceWrapper.java @@ -194,9 +194,13 @@ public void persist(Logger logger, List 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); @@ -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 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)); + } } diff --git a/src/main/java/de/app/fivegla/integration/sentek/SentekSensorIntegrationService.java b/src/main/java/de/app/fivegla/integration/sentek/SentekSensorIntegrationService.java index 001963ee..37f05d93 100644 --- a/src/main/java/de/app/fivegla/integration/sentek/SentekSensorIntegrationService.java +++ b/src/main/java/de/app/fivegla/integration/sentek/SentekSensorIntegrationService.java @@ -42,22 +42,22 @@ public SentekSensorIntegrationService(RestTemplate restTemplate) { */ public List fetchAll() { var headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.TEXT_PLAIN)); - var httpEntity = new HttpEntity(headers); - var uri = UriComponentsBuilder.fromHttpUrl(url + "/?cmd=getloggers&key={apiToken}") - .encode() - .toUriString(); - log.debug("Fetching sensor data from URI: {}", uri); - var uriVariables = Map.of("apiToken", getApiToken()); - var response = restTemplate.exchange(uri, HttpMethod.GET, httpEntity, String.class, uriVariables); - if (response.getStatusCode().is2xxSuccessful()) { - return parse(response.getBody()).getLoggers(); - } else { - var errorMessage = ErrorMessage.builder() - .error(Error.SENTEK_COULD_NOT_FETCH_SENSORS) - .message("Could not fetch sensors from Sentek API.") - .build(); - throw new BusinessException(errorMessage); + headers.setAccept(Collections.singletonList(MediaType.TEXT_PLAIN)); + var httpEntity = new HttpEntity(headers); + var uri = UriComponentsBuilder.fromHttpUrl(url + "/?cmd=getloggers&key={apiToken}") + .encode() + .toUriString(); + log.debug("Fetching sensor data from URI: {}", uri); + var uriVariables = Map.of("apiToken", getApiToken()); + var response = restTemplate.exchange(uri, HttpMethod.GET, httpEntity, String.class, uriVariables); + if (response.getStatusCode().is2xxSuccessful()) { + return parse(response.getBody()).getLoggers(); + } else { + var errorMessage = ErrorMessage.builder() + .error(Error.SENTEK_COULD_NOT_FETCH_SENSORS) + .message("Could not fetch sensors from Sentek API.") + .build(); + throw new BusinessException(errorMessage); } } diff --git a/src/main/java/de/app/fivegla/integration/weenat/WeenatFiwareIntegrationServiceWrapper.java b/src/main/java/de/app/fivegla/integration/weenat/WeenatFiwareIntegrationServiceWrapper.java index e3b1f74d..5ce503a0 100644 --- a/src/main/java/de/app/fivegla/integration/weenat/WeenatFiwareIntegrationServiceWrapper.java +++ b/src/main/java/de/app/fivegla/integration/weenat/WeenatFiwareIntegrationServiceWrapper.java @@ -180,7 +180,7 @@ public void persist(Plot plot, Measurements measurements) { fiwareEntityMonitor.entitiesSavedOrUpdated(Manufacturer.WEENAT); } - var solarIrridiance = deviceMeasurement.numValue(measurement.getMeasurementValues().getSolarIrridiance()) + var solarIrridiance = deviceMeasurement.numValue(measurement.getMeasurementValues().getSolarIrradiance()) .unit("W/m²") .build(); if (solarIrridiance != null) { @@ -188,7 +188,7 @@ public void persist(Plot plot, Measurements measurements) { fiwareEntityMonitor.entitiesSavedOrUpdated(Manufacturer.WEENAT); } - var minimumSolarIrridiance = deviceMeasurement.numValue(measurement.getMeasurementValues().getMinimumSolarIrridiance()) + var minimumSolarIrridiance = deviceMeasurement.numValue(measurement.getMeasurementValues().getMinSolarIrradiance()) .unit("W/m²") .build(); if (minimumSolarIrridiance != null) { @@ -196,7 +196,7 @@ public void persist(Plot plot, Measurements measurements) { fiwareEntityMonitor.entitiesSavedOrUpdated(Manufacturer.WEENAT); } - var maximumSolarIrridiance = deviceMeasurement.numValue(measurement.getMeasurementValues().getMaximumSolarIrridiance()) + var maximumSolarIrridiance = deviceMeasurement.numValue(measurement.getMeasurementValues().getMaxSolarIrradiance()) .unit("W/m²") .build(); if (maximumSolarIrridiance != null) { @@ -252,9 +252,13 @@ public void persist(Plot plot, Measurements measurements) { private void persist(Plot plot) { var device = Device.builder() .id(FiwareDeviceId.create(getManufacturerConfiguration(), String.valueOf(plot.getId()))) + .manufacturerSpecificId(String.valueOf(plot.getId())) .deviceCategory(DeviceCategory.builder() .value(List.of(getManufacturerConfiguration().getKey())) .build()) + .location(Location.builder() + .coordinates(List.of(plot.getLatitude(), plot.getLongitude())) + .build()) .build(); deviceIntegrationService.persist(device); fiwareEntityMonitor.sensorsSavedOrUpdated(Manufacturer.WEENAT); @@ -276,4 +280,14 @@ private DeviceMeasurement.DeviceMeasurementBuilder createDeviceMeasurement(Plot private WeenatConfiguration getManufacturerConfiguration() { return applicationConfiguration.getSensors().weenat(); } + + /** + * Returns the device ID for a given plot ID. + * + * @param plotId the ID of the plot + * @return the device ID + */ + public String deviceIdOf(int plotId) { + return FiwareDeviceId.create(getManufacturerConfiguration(), String.valueOf(plotId)); + } } diff --git a/src/main/java/de/app/fivegla/integration/weenat/WeenatMeasuresIntegrationService.java b/src/main/java/de/app/fivegla/integration/weenat/WeenatMeasuresIntegrationService.java index 3b915a2e..c2f7f7a2 100644 --- a/src/main/java/de/app/fivegla/integration/weenat/WeenatMeasuresIntegrationService.java +++ b/src/main/java/de/app/fivegla/integration/weenat/WeenatMeasuresIntegrationService.java @@ -77,10 +77,12 @@ public Map fetchAll(Instant start) { //noinspection unchecked var measures = (HashMap) objectMapper.readValue(metadataResponse, type); List measurements = new ArrayList<>(); - measures.forEach((timestamp, measurementValues) -> measurements.add(Measurement.builder() - .timestamp(Instant.ofEpochSecond(timestamp)) - .measurementValues(measurementValues) - .build())); + measures.forEach((timestamp, measurementValues) -> { + var measurement = new Measurement(); + measurement.setTimestamp(Instant.ofEpochSecond(timestamp)); + measurement.setMeasurementValues(measurementValues); + measurements.add(measurement); + }); if (!measures.isEmpty()) { plotsWithMeasurements.put(plot, measurementsBuilder.measurements(measurements).build()); } diff --git a/src/main/java/de/app/fivegla/integration/weenat/WeenatPlotIntegrationService.java b/src/main/java/de/app/fivegla/integration/weenat/WeenatPlotIntegrationService.java index fee6aa85..026da9fd 100644 --- a/src/main/java/de/app/fivegla/integration/weenat/WeenatPlotIntegrationService.java +++ b/src/main/java/de/app/fivegla/integration/weenat/WeenatPlotIntegrationService.java @@ -3,6 +3,7 @@ import de.app.fivegla.api.Error; import de.app.fivegla.api.ErrorMessage; import de.app.fivegla.api.exceptions.BusinessException; +import de.app.fivegla.fiware.model.Device; import de.app.fivegla.integration.weenat.model.Plot; import lombok.Getter; import lombok.Setter; @@ -62,4 +63,20 @@ public List fetchAll() { } } + /** + * Retrieves information from a device and creates a `Plot` object. + * + * @param device the device from which to extract the information + * @return the `Plot` object created from the device information + */ + public Plot plotFromDevice(Device device) { + var plot = new Plot(); + plot.setId(Long.valueOf(device.getManufacturerSpecificId())); + plot.setName("Plot " + device.getManufacturerSpecificId()); + plot.setLatitude(device.getLocation().getCoordinates().get(0)); + plot.setLongitude(device.getLocation().getCoordinates().get(1)); + plot.setDeviceCount(1); + return plot; + + } } diff --git a/src/main/java/de/app/fivegla/integration/weenat/model/Measurement.java b/src/main/java/de/app/fivegla/integration/weenat/model/Measurement.java index 09b0cf7f..ca9934c7 100644 --- a/src/main/java/de/app/fivegla/integration/weenat/model/Measurement.java +++ b/src/main/java/de/app/fivegla/integration/weenat/model/Measurement.java @@ -1,13 +1,33 @@ package de.app.fivegla.integration.weenat.model; -import lombok.Builder; +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. + */ @Getter -@Builder +@Setter +@Schema(description = "Represents a value for a specific time in a time series.") public class Measurement { - private final Instant timestamp; - private final MeasurementValues measurementValues; + + /** + * Represents the timestamp of a value in a time series. + *

+ * This variable is used to store the time at which a value was recorded in the time series. + */ + @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 timestamp; + + /** + * The value of the value in the time series. + */ + @Schema(description = "The value of the value in the time series.") + private MeasurementValues measurementValues; } diff --git a/src/main/java/de/app/fivegla/integration/weenat/model/MeasurementValues.java b/src/main/java/de/app/fivegla/integration/weenat/model/MeasurementValues.java index 98d19fa9..6120e6f1 100644 --- a/src/main/java/de/app/fivegla/integration/weenat/model/MeasurementValues.java +++ b/src/main/java/de/app/fivegla/integration/weenat/model/MeasurementValues.java @@ -1,6 +1,7 @@ package de.app.fivegla.integration.weenat.model; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; @@ -12,77 +13,102 @@ */ @Getter @Setter +@Schema(description = "Represents a single measurement entry with various weather-related properties.") public class MeasurementValues { + @Schema(description = "The temperature of the measurement.") @JsonProperty("T") private Double temperature; + @Schema(description = "The relative humidity of the measurement.") @JsonProperty("U") private Double relativeHumidity; + @Schema(description = "The cumulative rainfall of the measurement.") @JsonProperty("RR") private Double cumulativeRainfall; + @Schema(description = "The wind speed of the measurement.") @JsonProperty("FF") private Double windSpeed; + @Schema(description = "The wind gust speed of the measurement.") @JsonProperty("FXY") private Double windGustSpeed; + @Schema(description = "The soil temperature in 15 cm of the measurement.") @JsonProperty("T_15") private Double soilTemperature15; + @Schema(description = "The soil temperature in 30 cm of the measurement.") @JsonProperty("T_30") private Double soilTemperature30; + @Schema(description = "The soil temperature in 50 cm of the measurement.") @JsonProperty("T_60") private Double soilTemperature60; + @Schema(description = "The soil water potential in 15 cm of the measurement.") @JsonProperty("WHYD_15") private Double soilWaterPotential15; + @Schema(description = "The soil water potential in 30 cm of the measurement.") @JsonProperty("WHYD_30") private Double soilWaterPotential30; + @Schema(description = "The soil water potential in 60 cm of the measurement.") @JsonProperty("WHYD_60") private Double soilWaterPotential60; + @Schema(description = "The dry temperature of the measurement.") @JsonProperty("T_DRY") private Double dryTemperature; + @Schema(description = "The wet temperature of the measurement.") @JsonProperty("T_WET") private Double wetTemperature; + @Schema(description = "The leaf wetness duration of the measurement.") @JsonProperty("LW_DRY") private Double leafWetnessDuration; + @Schema(description = "The leaf wetness voltage of the measurement.") @JsonProperty("LW_V") private Double leafWetnessVoltage; + @Schema(description = "The soil temperature of the measurement.") @JsonProperty("T_SOIL") private Double soilTemperature; + @Schema(description = "The solar irradiance of the measurement.") @JsonProperty("SSI") - private Double solarIrridiance; + private Double solarIrradiance; + @Schema(description = "The minimum solar irradiance of the measurement.") @JsonProperty("SSI_MIN") - private Double minimumSolarIrridiance; + private Double minSolarIrradiance; + @Schema(description = "The maximum solar irradiance of the measurement.") @JsonProperty("SSI_MAX") - private Double maximumSolarIrridiance; + private Double maxSolarIrradiance; + @Schema(description = "The photosynthetically active radiation of the measurement.") @JsonProperty("PPFD") private Double photosyntheticallyActiveRadiation; + @Schema(description = "The minimum photosynthetically active radiation of the measurement.") @JsonProperty("PPFD_MIN") private Double minimumPhotosyntheticallyActiveRadiation; + @Schema(description = "The maximum photosynthetically active radiation of the measurement.") @JsonProperty("PPFD_MAX") private Double maximumPhotosyntheticallyActiveRadiation; + @Schema(description = "The dew point of the measurement.") @JsonProperty("T_DEW") private Double dewPoint; + @Schema(description = "The potential evapotranspiration of the measurement.") @JsonProperty("ETP") private Double potentialEvapotranspiration; }