Skip to content

Commit

Permalink
Feature/download openweather data during sensor data fetching (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
saschadoemer authored Aug 15, 2024
1 parent 101b5f1 commit d2cecd1
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Slf4j
Expand Down Expand Up @@ -41,4 +42,16 @@ public List<RegisteredDevice> findAll(Tenant tenant) {
log.info("Finding all registered devices for tenant: {}", tenant);
return registeredDeviceRepository.findAllByTenant(tenant);
}

/**
* Finds a registered device by tenant and sensor ID.
*
* @param tenant The tenant to find the registered device for.
* @param sensorId The sensor ID to find the registered device for.
* @return The registered device.
*/
public Optional<RegisteredDevice> findByTenantAndSensorId(Tenant tenant, String sensorId) {
log.info("Finding registered device for tenant {} and sensor ID {}.", tenant, sensorId);
return registeredDeviceRepository.findByTenantAndOid(tenant, sensorId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ public class BaseMappings {
public static final String GROUPS = SECURED_BY_TENANT + "/groups";
public static final String SUBSCRIPTION = SECURED_BY_TENANT + "/subscription";
public static final String REGISTERED_DEVICES = SECURED_BY_TENANT + "/registered-devices";
public static final String OPEN_WEATHER = SECURED_BY_TENANT + "/open-weather";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package de.app.fivegla.controller.tenant;

import de.app.fivegla.api.Response;
import de.app.fivegla.business.TenantService;
import de.app.fivegla.config.security.marker.TenantCredentialApiAccess;
import de.app.fivegla.controller.api.BaseMappings;
import de.app.fivegla.integration.openweather.OpenWeatherIntegrationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;
import java.time.LocalDate;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping(BaseMappings.OPEN_WEATHER)
public class OpenWeatherController implements TenantCredentialApiAccess {

private final OpenWeatherIntegrationService openWeatherIntegrationService;
private final TenantService tenantService;

@Operation(
operationId = "open-weather.import",
description = "Imports weather data from OpenWeather.",
tags = BaseMappings.OPEN_WEATHER
)
@ApiResponse(
responseCode = "200",
description = "The weather data was imported.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = Response.class)
)
)
@ApiResponse(
responseCode = "400",
description = "The request is invalid.",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = Response.class)
)
)
@PostMapping(value = "/import/{sensorId}/{startDateInThePast}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<? extends Response> importOpenWeatherDataFromThePast(@PathVariable @Schema(description = "The sensor ID.") String sensorId,
@PathVariable(value = "startDateInThePast") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDateInThePast,
Principal principal) {
var tenant = validateTenant(tenantService, principal);
log.info("Importing weather data from OpenWeather for sensor '{}'.", sensorId);
openWeatherIntegrationService.importWeatherDataFromThePast(tenant, sensorId, startDateInThePast);
return ResponseEntity.ok().build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,36 @@

import de.app.fivegla.api.Error;
import de.app.fivegla.api.ErrorMessage;
import de.app.fivegla.api.Manufacturer;
import de.app.fivegla.api.exceptions.BusinessException;
import de.app.fivegla.business.RegisteredDevicesService;
import de.app.fivegla.business.ThirdPartyApiConfigurationService;
import de.app.fivegla.integration.openweather.dto.OpenWeatherData;
import de.app.fivegla.integration.openweather.dto.OpenWeatherDataFromThePast;
import de.app.fivegla.persistence.entity.Tenant;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Slf4j
@Service
@RequiredArgsConstructor
public class OpenWeatherIntegrationService {

private static final String OPENWEATHERMAP_API_URL = "https://api.openweathermap.org/data/3.0/onecall?&exclude=minutely,hourly,daily,alerts&";
private static final String OPENWEATHERMAP_API_URL = "https://api.openweathermap.org/data/3.0/onecall?units=metric&exclude=minutely,hourly,daily,alerts";
private static final String OPENWEATHERMAP_API_TIMEMACHINE_URL = "https://api.openweathermap.org/data/3.0/onecall/timemachine?units=metric";

private final RestTemplate restTemplate;
private final RegisteredDevicesService registeredDevicesService;
private final ThirdPartyApiConfigurationService thirdPartyApiConfigurationService;

/**
* Fetches weather data from the OpenWeather API for the given longitude and latitude.
Expand All @@ -28,7 +44,8 @@ public class OpenWeatherIntegrationService {
public OpenWeatherData fetchWeatherData(String apiToken, double latitude, double longitude) {
try {
log.info("Importing weather data from OpenWeather for longitude {} and latitude {}.", longitude, latitude);
var concattedApiUrl = OPENWEATHERMAP_API_URL.concat("lat=").concat(String.valueOf(latitude))
var concattedApiUrl = OPENWEATHERMAP_API_URL
.concat("&lat=").concat(String.valueOf(latitude))
.concat("&lon=").concat(String.valueOf(longitude))
.concat("&appid=").concat(apiToken);
var openWeatherData = restTemplate.getForObject(concattedApiUrl, OpenWeatherData.class);
Expand All @@ -50,4 +67,67 @@ public OpenWeatherData fetchWeatherData(String apiToken, double latitude, double
.build());
}
}

/**
* Imports weather data from the past using the provided tenant, sensor ID, and start date.
*
* @param tenant the tenant associated with the weather data
* @param sensorId the ID of the sensor for which to import weather data
* @param startDateInThePast the start date from which to import weather data
*/
@Async
public void importWeatherDataFromThePast(Tenant tenant, String sensorId, LocalDate startDateInThePast) {
thirdPartyApiConfigurationService.findByManufacturer(tenant, Manufacturer.OPEN_WEATHER).ifPresent(thirdPartyApiConfiguration -> {
var apiToken = thirdPartyApiConfiguration.getApiToken();
registeredDevicesService.findByTenantAndSensorId(tenant, sensorId).ifPresent(registeredDevice -> {
var longitude = registeredDevice.getLongitude();
var latitude = registeredDevice.getLatitude();
var openWeatherData = fetchWeatherData(apiToken, latitude, longitude, startDateInThePast);
log.info("Imported weather data from OpenWeather for sensor '{}'.", sensorId);
log.debug("Imported weather data from OpenWeather for sensor '{}': {}", sensorId, openWeatherData);
});
});
}

private List<OpenWeatherDataFromThePast> fetchWeatherData(String apiToken, double latitude, double longitude, LocalDate startDateInThePast) {
try {
var weatherDataFromThePast = new ArrayList<OpenWeatherDataFromThePast>();
log.info("Iterating form the start date in the past, which is: {}", startDateInThePast);
var currentIterationDate = startDateInThePast;
while (currentIterationDate.isBefore(LocalDate.now())) {
log.info("Fetching weather data from OpenWeather for longitude {}, latitude {} and timestamp {}.", longitude, latitude, currentIterationDate);
var openWeatherData = fetchWeatherDataFromThePast(apiToken, latitude, longitude, currentIterationDate);
if (openWeatherData.isEmpty()) {
break;
} else {
weatherDataFromThePast.add(openWeatherData.get());
currentIterationDate = currentIterationDate.plusDays(1);
}
}
return weatherDataFromThePast;
} catch (Exception e) {
log.error("Failed to import weather data from OpenWeather for longitude {} and latitude {}.", longitude, latitude, e);
return new ArrayList<>();
}
}

private Optional<OpenWeatherDataFromThePast> fetchWeatherDataFromThePast(String apiToken, double latitude, double longitude, LocalDate startDateInThePast) {
try {
var concattedApiUrl = OPENWEATHERMAP_API_TIMEMACHINE_URL
.concat("&lat=").concat(String.valueOf(latitude))
.concat("&lon=").concat(String.valueOf(longitude))
.concat("&dt=").concat(String.valueOf(startDateInThePast.atStartOfDay(ZoneId.systemDefault()).toEpochSecond()))
.concat("&appid=").concat(apiToken);
var openWeatherDataFromThePast = restTemplate.getForObject(concattedApiUrl, OpenWeatherDataFromThePast.class);
if (openWeatherDataFromThePast == null) {
log.error("Failed to import weather data from OpenWeather for longitude {}, latitude {} and timestamp {}.", longitude, latitude, startDateInThePast);
return Optional.empty();
} else {
return Optional.of(openWeatherDataFromThePast);
}
} catch (Exception e) {
log.error("Failed to import weather data from OpenWeather for longitude {} and latitude {}.", longitude, latitude, e);
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.ToString;

/**
* This class represents the current weather data returned by the OpenWeather API.
*/
@ToString
@JsonIgnoreProperties(ignoreUnknown = true)
public class Current {
Expand Down
54 changes: 54 additions & 0 deletions src/main/java/de/app/fivegla/integration/openweather/dto/Data.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package de.app.fivegla.integration.openweather.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.ToString;

/**
* This class represents the current weather data returned by the OpenWeather API.
*/
@ToString
@JsonIgnoreProperties(ignoreUnknown = true)
public class Data {

@JsonProperty("dt")
private long timestamp;

@JsonProperty("sunrise")
private long sunrise;

@JsonProperty("sunset")
private long sunset;

@JsonProperty("temp")
private double temperature;

@JsonProperty("feels_like")
private double feelsLike;

@JsonProperty("pressure")
private int pressure;

@JsonProperty("humidity")
private int humidity;

@JsonProperty("dew_point")
private double dewPoint;

@JsonProperty("uvi")
private double uvi;

@JsonProperty("clouds")
private int clouds;

@JsonProperty("visibility")
private int visibility;

@JsonProperty("wind_speed")
private double windSpeed;

@JsonProperty("wind_deg")
private int windDegree;


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.app.fivegla.integration.openweather.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.ToString;

/**
* This class represents the data returned by the OpenWeather API.
*/
@ToString
@JsonIgnoreProperties(ignoreUnknown = true)
public class OpenWeatherDataFromThePast {

@JsonProperty("lat")
private double latitude;

@JsonProperty("lon")
private double longitude;

@JsonProperty("timezone")
private String timezone;

@JsonProperty("timezone_offset")
private int timezoneOffset;

@JsonProperty("current")
private Data data;

@JsonProperty("rain.1h")
private double rainWithinOneHour;

@JsonProperty("snow.1h")
private double snowWithinOneHour;

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;


/**
Expand All @@ -22,4 +23,12 @@ public interface RegisteredDeviceRepository extends JpaRepository<RegisteredDevi
*/
List<RegisteredDevice> findAllByTenant(Tenant tenant);

/**
* Finds a registered device by tenant and sensor ID.
*
* @param tenant The tenant to find the registered device for.
* @param sensorId The sensor ID to find the registered device for.
* @return The registered device.
*/
Optional<RegisteredDevice> findByTenantAndOid(Tenant tenant, String sensorId);
}

0 comments on commit d2cecd1

Please sign in to comment.