Explorar o código

Local dump of changes

Lukas Cerny hai 2 meses
pai
achega
f771a01328
Modificáronse 21 ficheiros con 633 adicións e 170 borrados
  1. 5 0
      .dockerignore
  2. 35 0
      config/openMeteoEra5LandToSenslog.yaml
  3. 76 0
      config/openMeteoIconEUToSenslog.yaml
  4. 0 52
      config/openMeteoToSenslog.yaml
  5. 3 2
      config/soilscountToSenslog.yaml
  6. 2 2
      connector-app/src/main/java/cz/senslog/connector/app/Application.java
  7. 7 5
      connector-app/src/main/java/cz/senslog/connector/app/config/Connector.java
  8. 3 3
      connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/FofrFetcher.java
  9. 17 3
      connector-fetch-metno/src/main/java/cz/senslog/connector/fetch/metno/MetnoFetcher.java
  10. 3 4
      connector-fetch-metno/src/test/java/cz/senslog/connector/fetch/metno/MetnoFetcherTest.java
  11. 84 13
      connector-fetch-openmeteo/src/main/java/cz/senslog/connector/fetch/openmeteo/OpenMeteoConfig.java
  12. 56 23
      connector-fetch-openmeteo/src/main/java/cz/senslog/connector/fetch/openmeteo/OpenMeteoFetcher.java
  13. 65 2
      connector-fetch-openmeteo/src/test/java/cz/senslog/connector/fetch/openmeteo/OpenMeteoFetcherTest.java
  14. 16 2
      connector-fetch-soilscount/src/main/java/cz/senslog/connector/fetch/soilscount/SoilScountConfig.java
  15. 13 7
      connector-fetch-soilscount/src/test/java/cz/senslog/connector/fetch/soilscount/SoilScountFetcherTest.java
  16. 1 1
      connector-model/src/main/java/cz/senslog/connector/model/converter/FofrModelTelemetryModelConverter.java
  17. 170 0
      connector-model/src/main/java/cz/senslog/connector/model/converter/SoilscountSenslogV1Converter.java
  18. 29 6
      connector-push-senslog/src/main/java/cz/senslog/connector/push/senslog/SensLogPusher.java
  19. 41 25
      docker-compose.yaml
  20. 5 7
      docker/Dockerfile
  21. 2 13
      docker/start.sh

+ 5 - 0
.dockerignore

@@ -0,0 +1,5 @@
+.gitignore
+.idea
+docker-compose.yaml
+README.md
+*.tar

+ 35 - 0
config/openMeteoEra5LandToSenslog.yaml

@@ -0,0 +1,35 @@
+settings:
+  - OpenMeteo:
+      name: "Open Meteo Era5 Land"
+      provider: "cz.senslog.connector.fetch.openmeteo.ConnectorFetchOpenMeteoProvider"
+
+      baseUrl: "https://archive-api.open-meteo.com/v1/archive"
+
+      timeZone: "Europe/Prague"
+      startDate: "2025-09-15"
+#      endDate: "2025-09-14"
+      period: 24 # 1 day
+
+      allowedStationURL: "https://sensor.lesprojekt.cz/senslogOTS3/rest/forecast/era5land/plan"
+      allowedStationURLParams:
+          latitude: ${latitude}
+          longitude: ${longitude}
+          models: era5_land
+          hourly: [temperature_2m, relative_humidity_2m, dew_point_2m, precipitation, rain, snowfall, pressure_msl, surface_pressure,
+                    et0_fao_evapotranspiration, cloud_cover,wind_speed_10m, wind_direction_10m, wind_gusts_10m, 
+                    soil_temperature_0_to_7cm,soil_temperature_7_to_28cm,soil_temperature_28_to_100cm,soil_temperature_100_to_255cm,
+                    soil_moisture_0_to_7cm,soil_moisture_7_to_28cm,soil_moisture_28_to_100cm,soil_moisture_100_to_255cm]
+
+  - Senslog:
+      name: "Senslog Pass Trough"
+      provider: "cz.senslog.connector.push.senslog.ConnectorPushSensLogProvider"
+
+      baseUrl: "https://sensor.lesprojekt.cz/senslogOTS3/rest/forecast/era5land"
+
+connectors:
+  - era5LandSenslog:
+      fetcher: "OpenMeteo"
+      pusher: "Senslog"
+      period: 86_400 # 24h
+      startAt: "01:00:00" # hh:mm:ss  # non-mandatory attribute
+#      initDelay: 10        # non-mandatory attribute

+ 76 - 0
config/openMeteoIconEUToSenslog.yaml

@@ -0,0 +1,76 @@
+settings:
+
+  - iconeu:
+      name: "[RT] Open Meteo Icon EU"
+      provider: "cz.senslog.connector.fetch.openmeteo.ConnectorFetchOpenMeteoProvider"
+
+      baseUrl: "https://previous-runs-api.open-meteo.com/v1/forecast"
+
+      timeZone: "UTC"
+#      timeZone: "Europe/Prague"
+      startDate: "now"
+      period: -1 # -1 = disabled
+
+      allowedStationURL: "https://sensor.lesprojekt.cz/senslogOTS3/rest/forecast/openmeteo/plan"
+      allowedStationURLParams:
+        latitude: ${latitude}
+        longitude: ${longitude}
+        models: icon_eu
+        forecast_days: 3
+        hourly: [temperature_2m,temperature_2m_previous_day2,relative_humidity_2m,relative_humidity_2m_previous_day2,
+                 dew_point_2m,dew_point_2m_previous_day1,dew_point_2m_previous_day2,apparent_temperature,apparent_temperature_previous_day1,apparent_temperature_previous_day2,
+                 precipitation,precipitation_previous_day1,precipitation_previous_day2,rain,rain_previous_day1,rain_previous_day2,showers,snowfall,weather_code,surface_pressure,cloud_cover,
+                 wind_direction_10m,wind_speed_10m,showers_previous_day1,snowfall_previous_day1,
+                 weather_code_previous_day1,surface_pressure_previous_day1,cloud_cover_previous_day1,wind_speed_10m_previous_day1,wind_direction_10m_previous_day1,
+                 showers_previous_day2,snowfall_previous_day2,weather_code_previous_day2,surface_pressure_previous_day2,cloud_cover_previous_day2,wind_speed_10m_previous_day2,
+                 wind_direction_10m_previous_day2,relative_humidity_2m_previous_day1,temperature_2m_previous_day1]
+
+  - iconeu_past:
+      name: "[past] Open Meteo Icon EU"
+      provider: "cz.senslog.connector.fetch.openmeteo.ConnectorFetchOpenMeteoProvider"
+
+      baseUrl: "https://previous-runs-api.open-meteo.com/v1/forecast"
+
+      timeZone: "UTC"
+
+#      timeZone: "Europe/Prague"
+      startDate: "2025-09-07"
+      endDate: "2025-09-13"
+      period: 24 # 1 day
+
+      allowedStationURL: "https://sensor.lesprojekt.cz/senslogOTS3/rest/forecast/openmeteo/plan"
+      allowedStationURLParams:
+        latitude: ${latitude}
+        longitude: ${longitude}
+        models: icon_eu
+        start_date: ${currentDate}
+        end_date: ${currentDate}
+        hourly: [temperature_2m,temperature_2m_previous_day2,relative_humidity_2m,relative_humidity_2m_previous_day2,
+                 dew_point_2m,dew_point_2m_previous_day1,dew_point_2m_previous_day2,apparent_temperature,apparent_temperature_previous_day1,apparent_temperature_previous_day2,
+                 precipitation,precipitation_previous_day1,precipitation_previous_day2,rain,rain_previous_day1,rain_previous_day2,showers,snowfall,weather_code,surface_pressure,cloud_cover,
+                 wind_direction_10m,wind_speed_10m,showers_previous_day1,snowfall_previous_day1,
+                 weather_code_previous_day1,surface_pressure_previous_day1,cloud_cover_previous_day1,wind_speed_10m_previous_day1,wind_direction_10m_previous_day1,
+                 showers_previous_day2,snowfall_previous_day2,weather_code_previous_day2,surface_pressure_previous_day2,cloud_cover_previous_day2,wind_speed_10m_previous_day2,
+                 wind_direction_10m_previous_day2,relative_humidity_2m_previous_day1,temperature_2m_previous_day1]
+
+
+  - Senslog:
+      name: "Senslog Pass Trough"
+      provider: "cz.senslog.connector.push.senslog.ConnectorPushSensLogProvider"
+
+      baseUrl: "https://sensor.lesprojekt.cz/senslogOTS3/rest/forecast/openmeteo"
+
+connectors:
+  - IconEUSenslog_RT:
+      fetcher: "iconeu"
+      pusher: "Senslog"
+      period: 10_800          # 3h
+      startAt: "03:00:00" # hh:mm:ss  # non-mandatory attribute
+#      initDelay: 10        # non-mandatory attribute
+
+#  - IconEUSenslog_PAST:
+#      fetcher: "iconeu_past"
+#      pusher: "Senslog"
+#      period: 10           # seconds
+##      startAt: "22:10:00" # hh:mm:ss  # non-mandatory attribute
+#      initDelay: 10        # non-mandatory attribute

+ 0 - 52
config/openMeteoToSenslog.yaml

@@ -1,52 +0,0 @@
-settings:
-  - OpenMeteo:
-      name: "Open Meteo"
-      provider: "cz.senslog.connector.fetch.openmeteo.ConnectorFetchOpenMeteoProvider"
-
-#      baseUrl: "https://previous-runs-api.open-meteo.com/v1/forecast"
-      baseUrl: "https://archive-api.open-meteo.com/v1/archive"
-
-      timeZone: "Europe/Prague"
-      startDate: "2024-01-03"
-
-      allowedStationURL: "https://sensor.lesprojekt.cz/senslogOTS3/rest/forecast/era5land/plan"
-      allowedStationURLParams:
-          models: era5_land
-          hourly: [temperature_2m, relative_humidity_2m, dew_point_2m, precipitation, rain, snowfall, pressure_msl, surface_pressure,
-                    et0_fao_evapotranspiration, cloud_cover,wind_speed_10m, wind_direction_10m, wind_gusts_10m, 
-                    soil_temperature_0_to_7cm,soil_temperature_7_to_28cm,soil_temperature_28_to_100cm,soil_temperature_100_to_255cm,
-                    soil_moisture_0_to_7cm,soil_moisture_7_to_28cm,soil_moisture_28_to_100cm,soil_moisture_100_to_255cm]
-
-#      allowedStations:
-#        - latitude: 49.72680307543005
-#          longitude: 13.351931666613327
-#          timezone: GMT
-#          past_days: 1
-#          hourly: [temperature_2m,temperature_2m_previous_day1,temperature_2m_previous_day2,temperature_2m_previous_day3,temperature_2m_previous_day4,temperature_2m_previous_day5,
-#                   relative_humidity_2m,relative_humidity_2m_previous_day1,relative_humidity_2m_previous_day2,relative_humidity_2m_previous_day3,relative_humidity_2m_previous_day4,relative_humidity_2m_previous_day5,
-#                   dew_point_2m,dew_point_2m_previous_day1,dew_point_2m_previous_day2,dew_point_2m_previous_day3,dew_point_2m_previous_day4,dew_point_2m_previous_day5,
-#                   apparent_temperature,apparent_temperature_previous_day1,apparent_temperature_previous_day2,apparent_temperature_previous_day3,apparent_temperature_previous_day4,apparent_temperature_previous_day5,
-#                   precipitation,precipitation_previous_day1,precipitation_previous_day2,precipitation_previous_day3,precipitation_previous_day4,precipitation_previous_day5,
-#                   rain,rain_previous_day1,rain_previous_day2,rain_previous_day3,rain_previous_day4,rain_previous_day5,
-#                   showers,showers_previous_day1,showers_previous_day2,showers_previous_day3,showers_previous_day4,showers_previous_day5,
-#                   snowfall,snowfall_previous_day1,snowfall_previous_day2,snowfall_previous_day3,snowfall_previous_day4,snowfall_previous_day5,
-#                   weather_code,weather_code_previous_day1,weather_code_previous_day2,weather_code_previous_day3,weather_code_previous_day4,weather_code_previous_day5,
-#                   pressure_msl,pressure_msl_previous_day1,pressure_msl_previous_day2,pressure_msl_previous_day3,pressure_msl_previous_day4,pressure_msl_previous_day5,
-#                   surface_pressure,surface_pressure_previous_day1,surface_pressure_previous_day2,surface_pressure_previous_day3,surface_pressure_previous_day4,surface_pressure_previous_day5,
-#                   cloud_cover,cloud_cover_previous_day1,cloud_cover_previous_day2,cloud_cover_previous_day3,cloud_cover_previous_day4,cloud_cover_previous_day5,
-#                   wind_speed_10m,wind_speed_10m_previous_day1,wind_speed_10m_previous_day2,wind_speed_10m_previous_day3,wind_speed_10m_previous_day4,wind_speed_10m_previous_day5,
-#                   wind_direction_10m,wind_direction_10m_previous_day1,wind_direction_10m_previous_day2,wind_direction_10m_previous_day3,wind_direction_10m_previous_day4,wind_direction_10m_previous_day5]
-
-  - Senslog:
-      name: "Senslog Pass Trough"
-      provider: "cz.senslog.connector.push.senslog.ConnectorPushSensLogProvider"
-
-      baseUrl: "https://sensor.lesprojekt.cz/senslogOTS3/rest/forecast/era5land"
-
-connectors:
-  - OpenMeteoSenslog:
-      fetcher: "OpenMeteo"
-      pusher: "Senslog"
-      period: 300          # 5 minutes
-      startAt: "00:10:00" # hh:mm:ss  # non-mandatory attribute
-#      initDelay: 10        # non-mandatory attribute

+ 3 - 2
config/soilscountToSenslog.yaml

@@ -3,7 +3,8 @@ settings:
       name: "Soil Scount"
       provider: "cz.senslog.connector.fetch.soilscount.ConnectorFetchSoilscountProvider"
 
-      startDate: "2024-11-10T00:00:00"
+      startDate: "now"
+#      startDate: "2024-11-10T00:00:00"
       period: 1  # period in hours
 
 #      OpenAPI Spec for login: https://www.soilscouts.fi/api/v1/
@@ -18,7 +19,7 @@ settings:
         "username": "mkepka@kgm.zcu.cz"
         "password": "Asense2023"
 
-      allowedDevices: [28779, 28780, 28781, 28782, 28783, 28784, 28785, 28786]
+      allowedDevices: [1241, 1090, 947, 776, 1900, 207, 94, 120, 662, 1844, 1541, 1096, 451, 1491, 28779, 28780, 28781, 28782, 28783, 28784, 28785, 28786]
 
   - SenslogV1:
       name: "Senslog V1"

+ 2 - 2
connector-app/src/main/java/cz/senslog/connector/app/Application.java

@@ -142,6 +142,6 @@ class Application extends Thread {
             logger.warn("No connectors were loaded.");
         }
 
-//        interrupt();
+        interrupt();
     }
-}
+}

+ 7 - 5
connector-app/src/main/java/cz/senslog/connector/app/config/Connector.java

@@ -16,9 +16,7 @@ import java.time.LocalTime;
 import java.time.temporal.ChronoUnit;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.*;
 
 import static cz.senslog.connector.tools.util.Pipeline.of;
 import static java.util.Optional.ofNullable;
@@ -165,8 +163,12 @@ public final class Connector {
         new Thread(() -> {
             try {
                 future.get();
-            } catch (ModuleInterruptedException e) {
-                logger.warn(e.getMessage());
+            } catch (ExecutionException e) {
+                if (e.getCause() instanceof ModuleInterruptedException) {
+                    logger.warn(e.getMessage());
+                } else {
+                    logger.catching(e);
+                }
             } catch (Exception e) {
                 logger.catching(e);
             } finally {

+ 3 - 3
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/FofrFetcher.java

@@ -128,8 +128,7 @@ public class FofrFetcher implements ConnectorFetcher<VoidSession, FofrModel> {
 //                }
 //            }
 //        }
-
-        orderInfos = Arrays.asList(new OrderInfo(202500034, 2590166192L, 1305167562660700L));
+        orderInfos = Arrays.asList(new OrderInfo(250022, 2590171820L, 1305167563014004L));
 
         List<ParcelUpdate> parcelsToUpdate = new ArrayList<>();
         for (OrderInfo orderInfo : orderInfos) {
@@ -230,7 +229,8 @@ public class FofrFetcher implements ConnectorFetcher<VoidSession, FofrModel> {
                         (String) parcelInfo.get("prij_mesto"),
                         (String) parcelInfo.get("prij_ulice"),
                         (String) parcelInfo.get("prij_psc"),
-                        49.7700992, 13.5921767  // TODO MIKE
+//                        49.7700992, 13.5921767  // TODO MIKE
+                        49.7412633, 13.5940422  // Rokycany
                 );
 
                 ParcelInfo parcel = new ParcelInfo(parcelId, officeBranch, exportDate, trackingStatuses, dispatcherAddress, recipientAddress);

+ 17 - 3
connector-fetch-metno/src/main/java/cz/senslog/connector/fetch/metno/MetnoFetcher.java

@@ -6,6 +6,7 @@ import com.google.gson.JsonObject;
 import cz.senslog.connector.fetch.api.ConnectorFetcher;
 import cz.senslog.connector.model.api.VoidSession;
 import cz.senslog.connector.model.metno.MetnoModel;
+import cz.senslog.connector.tools.exception.SyntaxException;
 import cz.senslog.connector.tools.http.HttpClient;
 import cz.senslog.connector.tools.http.HttpRequest;
 import cz.senslog.connector.tools.http.HttpResponse;
@@ -14,6 +15,7 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.time.OffsetDateTime;
+import java.time.ZonedDateTime;
 import java.util.*;
 
 import static cz.senslog.connector.tools.json.BasicJson.jsonToObject;
@@ -49,7 +51,9 @@ public class MetnoFetcher implements ConnectorFetcher<VoidSession, MetnoModel> {
             HttpResponse res = httpClient.send(req.build());
 
             if (res.isError()) {
-                throw logger.throwing(new RuntimeException(String.format("Error while getting the metno stations from %s.", config.getAllowedStationURL())));
+                logger.error("Request error <{}> with reason: {}", res.getStatus(), res.getBody());
+                logger.error("Error while getting the stations from <{}>.", config.getAllowedStationURL());
+                return new MetnoModel(null, null, Collections.emptyList());
             }
 
             JsonArray listJSON = jsonToObject(res.getBody(), JsonArray.class);
@@ -84,8 +88,18 @@ public class MetnoFetcher implements ConnectorFetcher<VoidSession, MetnoModel> {
                 continue;
             }
 
-            JsonObject geoJson = jsonToObject(res.getBody(), JsonObject.class);
-            predictions.add(geoJson);
+            try {
+                JsonObject geoJson = jsonToObject(res.getBody(), JsonObject.class);
+                predictions.add(geoJson);
+
+                OffsetDateTime updatedAt = ZonedDateTime.parse(geoJson.getAsJsonObject("properties").getAsJsonObject("meta").get("updated_at").getAsString()).toOffsetDateTime();
+                minPredictionDate = updatedAt.isBefore(minPredictionDate) ? updatedAt : minPredictionDate;
+                maxPredictionDate = updatedAt.isAfter(maxPredictionDate) ? updatedAt : maxPredictionDate;
+
+            } catch (SyntaxException e) {
+                logger.error("Error while parsing JSON: {}", e.getLocalizedMessage());
+                logger.error(res.getBody());
+            }
         }
 
         return new MetnoModel(minPredictionDate, maxPredictionDate, predictions);

+ 3 - 4
connector-fetch-metno/src/test/java/cz/senslog/connector/fetch/metno/MetnoFetcherTest.java

@@ -21,10 +21,9 @@ class MetnoFetcherTest {
         defaultConfig.setProperty("baseUrl", "https://api.met.no/weatherapi/locationforecast/2.0/complete");
         defaultConfig.setProperty("timeZone", "Europe/Prague");
 
-        Map<String, Object> st1 = new HashMap<String, Object>() {{
-            put("location", Arrays.asList(13.351931666613327, 49.72680307543005));
-        }};
-
+//        Map<String, Object> st1 = new HashMap<String, Object>() {{
+//            put("location", Arrays.asList(13.351931666613327, 49.72680307543005));
+//        }};
 //        defaultConfig.setProperty("allowedStations", Collections.singletonList(st1));
         defaultConfig.setProperty("allowedStationURL", "https://sensor.lesprojekt.cz/senslogOTS3/rest/forecast/locationforecast/plan");
 

+ 84 - 13
connector-fetch-openmeteo/src/main/java/cz/senslog/connector/fetch/openmeteo/OpenMeteoConfig.java

@@ -2,29 +2,97 @@ package cz.senslog.connector.fetch.openmeteo;
 
 import cz.senslog.connector.model.config.DefaultConfig;
 import cz.senslog.connector.model.config.PropertyConfig;
+import cz.senslog.connector.tools.util.ClassUtils;
 
 import java.time.LocalDate;
 import java.util.*;
+import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class OpenMeteoConfig {
 
+    private static final Pattern pattern = Pattern.compile("\\$\\{(?<name>\\w+)\\}", Pattern.CASE_INSENSITIVE);
+
+    public static final class Param {
+        private final boolean isDynamic;
+        private final String value;
+
+        public Param(Object value) {
+            if (value instanceof String) {
+                Matcher matcher = pattern.matcher((String) value);
+                if (matcher.matches()) {
+                    this.isDynamic = true;
+                    this.value = matcher.group("name");
+                } else {
+                    this.isDynamic = false;
+                    this.value = (String) value;
+                }
+            } else if (value instanceof List) {
+                Collection<?> list = (Collection<?>) value;
+                List<String> res = new ArrayList<>(list.size());
+                for (Object o : list) {
+                    res.add(ClassUtils.cast(o, String.class));
+                }
+                this.isDynamic = false;
+                this.value = String.join(",", res);
+            } else if (value instanceof Integer) {
+                this.isDynamic = false;
+                this.value = ((Integer) value).toString();
+            } else {
+                this.isDynamic = false;
+                this.value = null;
+            }
+        }
+
+        public boolean isDynamic() {
+            return isDynamic;
+        }
+
+        public String getValue() {
+            return value;
+        }
+    }
+
     private final String url;
     private final TimeZone timeZone;
-    private final LocalDate startDate;
+    private final Supplier<LocalDate> startDateSupplier;
+    private LocalDate startDate;
+    private final LocalDate endDate;
+    private final int period;
     private final String allowedStationURL;
-    private final List<String> hourly;
-    private final String models;
+    private final Map<String, Param> allowedStationParams;
 
     OpenMeteoConfig(DefaultConfig defaultConfig) {
         this.url = defaultConfig.getStringProperty("baseUrl");
-        this.startDate = LocalDate.parse(defaultConfig.getStringProperty("startDate"));
+        String startDateString = defaultConfig.getStringProperty("startDate");
+        if (startDateString.equalsIgnoreCase("now")) {
+            this.startDateSupplier = LocalDate::now;
+            this.startDate = null;
+        } else {
+            this.startDateSupplier = null;
+            this.startDate = LocalDate.parse(startDateString);
+        }
+        if (defaultConfig.containsProperty("endDate")) {
+            this.endDate = LocalDate.parse(defaultConfig.getStringProperty("endDate"));
+        } else {
+            this.endDate = null;
+        }
+        this.period = defaultConfig.getIntegerProperty("period");
         this.timeZone = TimeZone.getTimeZone(defaultConfig.getStringProperty("timeZone"));
         this.allowedStationURL = defaultConfig.containsProperty("allowedStationURL") ? defaultConfig.getStringProperty("allowedStationURL") : null;
 
+
         PropertyConfig params = defaultConfig.getPropertyConfig("allowedStationURLParams");
-        this.hourly = params.getListProperty("hourly", String.class);
-        this.models = params.getStringProperty("models");
+        Set<String> attributes = params.getAttributes();
+        this.allowedStationParams = new HashMap<>(attributes.size());
+        for (String attr : attributes) {
+            this.allowedStationParams.put(attr, new Param(params.getProperty(attr)));
+        }
+    }
 
+    public int getPeriod() {
+        return period;
     }
 
     public String getUrl() {
@@ -36,18 +104,21 @@ public class OpenMeteoConfig {
     }
 
     public LocalDate getStartDate() {
-        return startDate;
+        if (startDate == null) {
+            startDate = startDateSupplier.get();
+        }
+        return  startDate;
     }
 
-    public String getAllowedStationURL() {
-        return allowedStationURL;
+    public LocalDate getEndDate() {
+        return endDate;
     }
 
-    public List<String> getHourly() {
-        return hourly;
+    public String getAllowedStationURL() {
+        return allowedStationURL;
     }
 
-    public String getModels() {
-        return models;
+    public Map<String, Param> getAllowedStationParams() {
+        return allowedStationParams;
     }
 }

+ 56 - 23
connector-fetch-openmeteo/src/main/java/cz/senslog/connector/fetch/openmeteo/OpenMeteoFetcher.java

@@ -6,6 +6,8 @@ import com.google.gson.JsonObject;
 import cz.senslog.connector.fetch.api.ConnectorFetcher;
 import cz.senslog.connector.model.api.VoidSession;
 import cz.senslog.connector.model.openmeteo.OpenMeteoModel;
+import cz.senslog.connector.tools.exception.ModuleInterruptedException;
+import cz.senslog.connector.tools.exception.SyntaxException;
 import cz.senslog.connector.tools.http.HttpClient;
 import cz.senslog.connector.tools.http.HttpRequest;
 import cz.senslog.connector.tools.http.HttpResponse;
@@ -15,6 +17,7 @@ import org.apache.logging.log4j.Logger;
 
 import java.time.*;
 import java.util.*;
+import java.util.function.Supplier;
 
 import static cz.senslog.connector.tools.json.BasicJson.jsonToObject;
 import static java.time.format.DateTimeFormatter.ofPattern;
@@ -27,7 +30,7 @@ public class OpenMeteoFetcher implements ConnectorFetcher<VoidSession, OpenMeteo
     private final OpenMeteoConfig config;
 
     private final Set<Station> stations;
-    private LocalDate currentDate;
+    private LocalDateTime currentDateTime;
 
 
     public OpenMeteoFetcher(OpenMeteoConfig config, HttpClient httpClient) {
@@ -37,18 +40,25 @@ public class OpenMeteoFetcher implements ConnectorFetcher<VoidSession, OpenMeteo
     }
 
     @Override
-    public void init() {
-        this.currentDate = config.getStartDate();
-    }
+    public void init() {}
+
 
     @Override
     public OpenMeteoModel fetch(Optional<VoidSession> session) {
 
+        currentDateTime = currentDateTime != null ? currentDateTime : LocalDateTime.of(config.getStartDate(), LocalTime.now());
         stations.clear();
 
-        if (currentDate.isAfter(LocalDate.now())) {
-            LocalDateTime futureExecution = LocalDateTime.of(currentDate, LocalTime.now());
-            logger.info("Waiting for current date. Execution in {} minutes", Duration.between(futureExecution, LocalDateTime.now()).toMinutes());
+
+        if (config.getEndDate() != null && currentDateTime.toLocalDate().isAfter(config.getEndDate())) {
+            throw new ModuleInterruptedException(String.format("The connector reached the end: %s", config.getEndDate()));
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        if (currentDateTime.isAfter(now)) {
+            logger.warn("The current execution can't be in the future: {} > {}", currentDateTime, now);
+            LocalDateTime futureExecution = currentDateTime.plusHours(config.getPeriod());
+            logger.info("Waiting for current date. Execution in {} minutes", Duration.between(LocalDateTime.now(), futureExecution).toMinutes());
             return new OpenMeteoModel(null, null, Collections.emptyList());
         }
 
@@ -59,7 +69,9 @@ public class OpenMeteoFetcher implements ConnectorFetcher<VoidSession, OpenMeteo
             HttpResponse res = httpClient.send(req.build());
 
             if (res.isError()) {
-                throw logger.throwing(new RuntimeException(String.format("Error while getting the metno stations from %s.", config.getAllowedStationURL())));
+                logger.error("Request error <{}> with reason: {}", res.getStatus(), res.getBody());
+                logger.error("Error while getting the stations from <{}>.", config.getAllowedStationURL());
+                return new OpenMeteoModel(null, null, Collections.emptyList());
             }
 
             JsonArray listJSON = jsonToObject(res.getBody(), JsonArray.class);
@@ -76,18 +88,32 @@ public class OpenMeteoFetcher implements ConnectorFetcher<VoidSession, OpenMeteo
 
         List<JsonObject> results = new ArrayList<>(stations.size());
         for (Station station : stations) {
-            HttpRequest.Builder req = HttpRequest.newBuilder().GET()
-                    .url(URLBuilder.newBuilder(config.getUrl())
-                            .addParam("latitude", Double.toString(station.getLatitude()))
-                            .addParam("longitude", Double.toString(station.getLongitude()))
-                            .addParam("start_date", currentDate.format(ofPattern("yyyy-MM-dd")))
-                            .addParam("end_date", currentDate.format(ofPattern("yyyy-MM-dd")))
-//                            .addParam("timezone", station.timezone)
-//                            .addParam("past_days", Integer.toString(station.past_days))
-                            .addParam("hourly", String.join(",", config.getHourly()))
-                            .addParam("models", config.getModels())
-                            .build());
 
+            Map<String, Supplier<String>> dynamicParamMapping = new HashMap<>();
+            dynamicParamMapping.put("latitude", () -> Double.toString(station.getLatitude()));
+            dynamicParamMapping.put("longitude", () -> Double.toString(station.getLongitude()));
+            dynamicParamMapping.put("currentDate", () -> currentDateTime.format(ofPattern("yyyy-MM-dd")));
+
+            URLBuilder urlBuilder = URLBuilder.newBuilder(config.getUrl());
+
+            for (Map.Entry<String, OpenMeteoConfig.Param> paramEntry : config.getAllowedStationParams().entrySet()) {
+                String paramName = paramEntry.getKey();
+                OpenMeteoConfig.Param param = paramEntry.getValue();
+
+                if (param.isDynamic()) {
+                    Supplier<String> valueSupplier = dynamicParamMapping.get(param.getValue());
+                    if (valueSupplier != null) {
+                        urlBuilder.addParam(paramName, valueSupplier.get());
+                    } else {
+                        logger.warn("Dynamic param {} not found.", paramName);
+                        return new OpenMeteoModel(null, null, Collections.emptyList());
+                    }
+                } else {
+                    urlBuilder.addParam(paramName, param.getValue());
+                }
+            }
+
+            HttpRequest.Builder req = HttpRequest.newBuilder().GET().url(urlBuilder.build());
             HttpResponse res = httpClient.send(req.build());
 
             if (res.isError()) {
@@ -95,13 +121,20 @@ public class OpenMeteoFetcher implements ConnectorFetcher<VoidSession, OpenMeteo
                 continue;
             }
 
-            JsonObject geoJson = jsonToObject(res.getBody(), JsonObject.class);
-            results.add(geoJson);
+            String jsonBody = res.getBody();
+            try {
+                JsonObject geoJson = jsonToObject(jsonBody, JsonObject.class);
+                results.add(geoJson);
+            } catch (SyntaxException e) {
+                logger.error("Error while parsing JSON: {}", e.getLocalizedMessage());
+                logger.error(jsonBody);
+            }
         }
 
-        OffsetDateTime currentDateTime = ZonedDateTime.of(currentDate, LocalTime.now(), config.getTimeZone().toZoneId()).toOffsetDateTime();
+        OffsetDateTime currentDateTime = ZonedDateTime.of(this.currentDateTime, config.getTimeZone().toZoneId()).toOffsetDateTime();
 
-        currentDate = currentDate.plusDays(1);
+        // do not move by period step if period < 0
+        this.currentDateTime = config.getPeriod() < 0 ? LocalDateTime.now() : this.currentDateTime.plusHours(config.getPeriod());
 
         return new OpenMeteoModel(currentDateTime, currentDateTime, results);
     }

+ 65 - 2
connector-fetch-openmeteo/src/test/java/cz/senslog/connector/fetch/openmeteo/OpenMeteoFetcherTest.java

@@ -15,9 +15,10 @@ import static org.junit.jupiter.api.Assertions.*;
 
 class OpenMeteoFetcherTest {
 
+    private final ConnectorFetchOpenMeteoProvider provider = new ConnectorFetchOpenMeteoProvider();
+
     @Test
-    void fetch() throws Exception {
-        ConnectorFetchOpenMeteoProvider provider = new ConnectorFetchOpenMeteoProvider();
+    void fetch_era5land() throws Exception {
 
         DefaultConfig defaultConfig = new DefaultConfig("", null);
 
@@ -64,4 +65,66 @@ class OpenMeteoFetcherTest {
         fetcher.execute();
 
     }
+
+    @Test
+    void fetch_iconeu() throws Exception {
+        DefaultConfig defaultConfig = new DefaultConfig("", null);
+
+        defaultConfig.setProperty("baseUrl", "https://previous-runs-api.open-meteo.com/v1/forecast");
+        defaultConfig.setProperty("timeZone", "Europe/Prague");
+        defaultConfig.setProperty("startDate", "2025-04-23");
+        defaultConfig.setProperty("period", -1);
+
+        defaultConfig.setProperty("allowedStationURL", "https://sensor.lesprojekt.cz/senslogOTS3/rest/forecast/openmeteo/plan");
+        Map<String, Object> params = new HashMap<String, Object>(){{
+            put("latitude", "${latitude}");
+            put("longitude", "${longitude}");
+            put("forecast_days", 3);
+            put("models", "icon_eu");
+            put("hourly", Arrays.asList("temperature_2m","temperature_2m_previous_day2","relative_humidity_2m","relative_humidity_2m_previous_day2",
+                    "dew_point_2m","dew_point_2m_previous_day1","dew_point_2m_previous_day2","apparent_temperature","apparent_temperature_previous_day1","apparent_temperature_previous_day2",
+                    "precipitation","precipitation_previous_day1","precipitation_previous_day2","rain","rain_previous_day1","rain_previous_day2","showers","snowfall","weather_code","surface_pressure","cloud_cover",
+                    "wind_direction_10m","wind_speed_10m","showers_previous_day1","snowfall_previous_day1",
+                    "weather_code_previous_day1","surface_pressure_previous_day1","cloud_cover_previous_day1","wind_speed_10m_previous_day1","wind_direction_10m_previous_day1",
+                    "showers_previous_day2","snowfall_previous_day2","weather_code_previous_day2","surface_pressure_previous_day2","cloud_cover_previous_day2","wind_speed_10m_previous_day2",
+                    "wind_direction_10m_previous_day2","relative_humidity_2m_previous_day1","temperature_2m_previous_day1"));
+        }};
+        defaultConfig.setProperty("allowedStationURLParams", params);
+
+        ExecutableFetcher<OpenMeteoModel> fetcher = provider.createExecutableFetcher(defaultConfig);
+        fetcher.getRawFetcher().init();
+        fetcher.execute();
+    }
+
+    @Test
+    void fetch_iconeu_past() throws Exception {
+        DefaultConfig defaultConfig = new DefaultConfig("", null);
+
+        defaultConfig.setProperty("baseUrl", "https://previous-runs-api.open-meteo.com/v1/forecast");
+        defaultConfig.setProperty("timeZone", "UTC");
+//        defaultConfig.setProperty("timeZone", "Europe/Prague");
+        defaultConfig.setProperty("startDate", "2025-04-01");
+        defaultConfig.setProperty("endDate", "2025-04-23");
+
+        defaultConfig.setProperty("allowedStationURL", "https://sensor.lesprojekt.cz/senslogOTS3/rest/forecast/openmeteo/plan");
+        Map<String, Object> params = new HashMap<String, Object>(){{
+            put("latitude", "${latitude}");
+            put("longitude", "${longitude}");
+            put("start_date", "${currentDate}");
+            put("end_date", "${currentDate}");
+            put("models", "icon_eu");
+            put("hourly", Arrays.asList("temperature_2m","temperature_2m_previous_day2","relative_humidity_2m","relative_humidity_2m_previous_day2",
+                    "dew_point_2m","dew_point_2m_previous_day1","dew_point_2m_previous_day2","apparent_temperature","apparent_temperature_previous_day1","apparent_temperature_previous_day2",
+                    "precipitation","precipitation_previous_day1","precipitation_previous_day2","rain","rain_previous_day1","rain_previous_day2","showers","snowfall","weather_code","surface_pressure","cloud_cover",
+                    "wind_direction_10m","wind_speed_10m","showers_previous_day1","snowfall_previous_day1",
+                    "weather_code_previous_day1","surface_pressure_previous_day1","cloud_cover_previous_day1","wind_speed_10m_previous_day1","wind_direction_10m_previous_day1",
+                    "showers_previous_day2","snowfall_previous_day2","weather_code_previous_day2","surface_pressure_previous_day2","cloud_cover_previous_day2","wind_speed_10m_previous_day2",
+                    "wind_direction_10m_previous_day2","relative_humidity_2m_previous_day1","temperature_2m_previous_day1"));
+        }};
+        defaultConfig.setProperty("allowedStationURLParams", params);
+
+        ExecutableFetcher<OpenMeteoModel> fetcher = provider.createExecutableFetcher(defaultConfig);
+        fetcher.getRawFetcher().init();
+        fetcher.execute();
+    }
 }

+ 16 - 2
connector-fetch-soilscount/src/main/java/cz/senslog/connector/fetch/soilscount/SoilScountConfig.java

@@ -4,7 +4,9 @@ import cz.senslog.connector.model.config.DefaultConfig;
 import cz.senslog.connector.model.config.PropertyConfig;
 
 import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.Set;
+import java.util.function.Supplier;
 
 public class SoilScountConfig {
 
@@ -27,7 +29,6 @@ public class SoilScountConfig {
         }
     }
 
-    private final LocalDateTime startDate;
     private final int period;
     private final Set<Integer> allowedDevices;
     private final String authUrl;
@@ -35,9 +36,17 @@ public class SoilScountConfig {
     private final String devicesUrl;
     private final String measurementsUrl;
     private final AuthConfig authConfig;
+    private final LocalDateTime initDateTime;
+    private LocalDateTime startDate;
 
     SoilScountConfig(DefaultConfig defaultConfig) {
-        this.startDate = defaultConfig.getLocalDateTimeProperty("startDate");
+        this.initDateTime = LocalDateTime.now();
+        String startDateStr = defaultConfig.getStringProperty("startDate");
+        if (startDateStr.equalsIgnoreCase("now")) {
+            startDate = null;
+        } else {
+            startDate = defaultConfig.getLocalDateTimeProperty("startDate");
+        }
         this.period = defaultConfig.getIntegerProperty("period");
         this.allowedDevices = defaultConfig.getSetProperty("allowedDevices", Integer.class);
         this.authUrl = defaultConfig.getStringProperty("authUrl");
@@ -49,6 +58,11 @@ public class SoilScountConfig {
     }
 
     public LocalDateTime getStartDate() {
+        if (startDate == null) {
+            LocalDateTime startDateNow = LocalDateTime.now();
+            long delay = ChronoUnit.SECONDS.between(initDateTime, startDateNow);
+            startDate = startDateNow.minusSeconds(delay).minusHours(period);
+        }
         return startDate;
     }
 

+ 13 - 7
connector-fetch-soilscount/src/test/java/cz/senslog/connector/fetch/soilscount/SoilScountFetcherTest.java

@@ -1,13 +1,15 @@
 package cz.senslog.connector.fetch.soilscount;
 
 import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.converter.SoilscountSenslogV1Converter;
+import cz.senslog.connector.model.soilscount.SoilscountModel;
+import cz.senslog.connector.model.v1.Record;
+import cz.senslog.connector.model.v1.SenslogV1Model;
 import cz.senslog.connector.tools.http.HttpClient;
 import org.junit.jupiter.api.Test;
 
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Optional;
+import java.util.*;
+import java.util.stream.Collectors;
 
 class SoilScountFetcherTest {
 
@@ -15,9 +17,13 @@ class SoilScountFetcherTest {
     void fetch() throws Exception {
 
         DefaultConfig defaultConfig = new DefaultConfig("id", null);
-        defaultConfig.setProperty("startDate", "2024-11-08T00:00:00");
+//        defaultConfig.setProperty("startDate", "2025-08-08T00:00:00");
+        defaultConfig.setProperty("startDate", "now");
         defaultConfig.setProperty("period", 12);
-        defaultConfig.setProperty("allowedDevices", new HashSet<>(Arrays.asList(28779, 28780, 28781, 28782, 28783, 28784, 28785, 28786)));
+        defaultConfig.setProperty("allowedDevices", new HashSet<>(Arrays.asList(
+                1241, 1090, 947, 776, 1900, 207, 94, 120, 662, 1844, 1541, 1096, 451, 1491
+              // , 28779, 28780, 28781, 28782, 28783, 28784, 28785, 28786
+        )));
         defaultConfig.setProperty("authUrl", "https://www.soilscouts.fi/api/v1/auth/login//");
         defaultConfig.setProperty("refreshUrl", "https://www.soilscouts.fi/api/v1/auth/login//");
         defaultConfig.setProperty("devicesUrl", "https://www.soilscouts.fi/api/v1/devices");
@@ -33,7 +39,7 @@ class SoilScountFetcherTest {
         fetcher.init();
         Optional<SessionModel> session = Optional.of(new SessionModel(true));
         for (int i = 0; i < 10; i++) {
-            fetcher.fetch(session);
+            SoilscountModel res = fetcher.fetch(session);
         }
     }
 }

+ 1 - 1
connector-model/src/main/java/cz/senslog/connector/model/converter/FofrModelTelemetryModelConverter.java

@@ -40,7 +40,7 @@ public class FofrModelTelemetryModelConverter implements Converter<FofrModel, Te
                 JsonArray coordinates = new JsonArray();
                 coordinates.add(location.getLongitude());
                 coordinates.add(location.getLatitude());
-                coordinates.add(0); // altitude
+                coordinates.add(0.0); // altitude
                 geometry.add("coordinates", coordinates);
 
             }

+ 170 - 0
connector-model/src/main/java/cz/senslog/connector/model/converter/SoilscountSenslogV1Converter.java

@@ -20,6 +20,176 @@ public class SoilscountSenslogV1Converter implements Converter<SoilscountModel,
     static {
         NM_OF_SENSORS = 3;
         DEVICE_TO_UNIT_ID = new HashMap<>();
+
+        /*
+        unit_id         /serial /device /description    /phenomenon     /sensor_id
+        246014917       14917   1241    Yield 1 Top     temperature     340620015
+        246014917       14917   1241                    moisture        410240015
+        246014917       14917   1241                    salinity        570060015
+         */
+        DEVICE_TO_UNIT_ID.put(1241L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014917L); setSensorId(340620015L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014917L); setSensorId(410240015L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014917L); setSensorId(570060015L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014536       14536   1090    Yield 1 Under   temperature     340620030
+        246014536       14536   1090                    moisture        410240030
+        246014536       14536   1090                    salinity        570060030
+        246014536       14536   1090                    battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(1090L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014536L); setSensorId(340620030L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014536L); setSensorId(410240030L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014536L); setSensorId(570060030L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014968       14968   947     Yield 2 Top     temperature     340620015
+        246014968       14968   947                     moisture        410240015
+        246014968       14968   947                     salinity        570060015
+        246014968       14968   947                     battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(947L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014968L); setSensorId(340620015L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014968L); setSensorId(410240015L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014968L); setSensorId(570060015L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014969       14969   776     Yield 2 Under   temperature     340620030
+        246014969       14969   776                     moisture        410240030
+        246014969       14969   776                     salinity        570060030
+        246014969       14969   776                     battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(776L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014969L); setSensorId(340620030L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014969L); setSensorId(410240030L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014969L); setSensorId(570060030L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014970       14970   1900    Yield 3 Top     temperature     340620015
+        246014970       14970   1900                    moisture        410240015
+        246014970       14970   1900                    salinity        570060015
+        246014970       14970   1900                    battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(1900L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014970L); setSensorId(340620015L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014970L); setSensorId(410240015L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014970L); setSensorId(570060015L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014971       14971   207     Yield 3 Under   temperature     340620030
+        246014971       14971   207                     moisture        410240030
+        246014971       14971   207                     salinity        570060030
+        246014971       14971   207                     battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(207L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014971L); setSensorId(340620030L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014971L); setSensorId(410240030L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014971L); setSensorId(570060030L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014972       14972   94      Yield 4 Top     temperature     340620015
+        246014972       14972   94                      moisture        410240015
+        246014972       14972   94                      salinity        570060015
+        246014972       14972   94                      battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(94L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014972L); setSensorId(340620015L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014972L); setSensorId(410240015L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014972L); setSensorId(570060015L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014973       14973   120     Yield 4 Under   temperature     340620030
+        246014973       14973   120                     moisture        410240030
+        246014973       14973   120                     salinity        570060030
+        246014973       14973   120                     battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(120L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014973L); setSensorId(340620030L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014973L); setSensorId(410240030L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014973L); setSensorId(570060030L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014974       14974   662     Yield 5 Top     temperature     340620015
+        246014974       14974   662                     moisture        410240015
+        246014974       14974   662                     salinity        570060015
+        246014974       14974   662                     battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(662L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014974L); setSensorId(340620015L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014974L); setSensorId(410240015L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014974L); setSensorId(570060015L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014975       14975   1844    Yield 5 Under   temperature     340620030
+        246014975       14975   1844                    moisture        410240030
+        246014975       14975   1844                    salinity        570060030
+        246014975       14975   1844                    battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(1844L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014975L); setSensorId(340620030L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014975L); setSensorId(410240030L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014975L); setSensorId(570060030L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014976       14976   1541    Yield 6 Top     temperature     340620015
+        246014976       14976   1541                    moisture        410240015
+        246014976       14976   1541                    salinity        570060015
+        246014976       14976   1541                    battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(1541L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014976L); setSensorId(340620015L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014976L); setSensorId(410240015L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014976L); setSensorId(570060015L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014977       14977   1096    Yield 6 Under   temperature     340620030
+        246014977       14977   1096                    moisture        410240030
+        246014977       14977   1096                    salinity        570060030
+        246014977       14977   1096                    battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(1096L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014977L); setSensorId(340620030L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014977L); setSensorId(410240030L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014977L); setSensorId(570060030L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014978       14978   451     Yield 7 Top     temperature     340620015
+        246014978       14978   451                     moisture        410240015
+        246014978       14978   451                     salinity        570060015
+        246014978       14978   451                     battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(451L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014978L); setSensorId(340620015L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014978L); setSensorId(410240015L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014978L); setSensorId(570060015L); setValue(u.getSalinity());       }}
+        ));
+
+        /*
+        246014979       14979   1491    Yield 7 Under   temperature     340620030
+        246014979       14979   1491                    moisture        410240030
+        246014979       14979   1491                    salinity        570060030
+        246014979       14979   1491                    battery         360270000
+         */
+        DEVICE_TO_UNIT_ID.put(1491L, (u) -> Arrays.asList(
+                new Observation(){{setUnitId(246014979L); setSensorId(340620030L); setValue(u.getTemperature());    }},
+                new Observation(){{setUnitId(246014979L); setSensorId(410240030L); setValue(u.getMoisture());       }},
+                new Observation(){{setUnitId(246014979L); setSensorId(570060030L); setValue(u.getSalinity());       }}
+        ));
+
+        /// ---
         DEVICE_TO_UNIT_ID.put(28779L, (u) -> Arrays.asList(
                 new Observation(){{setUnitId(428024647L); setSensorId(410240030L); setValue(u.getMoisture());      }},
                 new Observation(){{setUnitId(428024647L); setSensorId(340620030L); setValue(u.getTemperature());   }},

+ 29 - 6
connector-push-senslog/src/main/java/cz/senslog/connector/push/senslog/SensLogPusher.java

@@ -6,6 +6,12 @@ import cz.senslog.connector.tools.http.*;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.UUID;
+import java.util.concurrent.LinkedTransferQueue;
+
 import static cz.senslog.connector.tools.http.HttpContentType.APPLICATION_JSON;
 
 public class SensLogPusher implements ConnectorPusher<SensLogModel> {
@@ -15,9 +21,14 @@ public class SensLogPusher implements ConnectorPusher<SensLogModel> {
     private final SensLogConfig config;
     private final HttpClient httpClient;
 
+    private final List<HttpRequest> failedRequests;
+    private final Queue<HttpRequest> requestQueue;
+
     public SensLogPusher(SensLogConfig config, HttpClient httpClient) {
         this.config = config;
         this.httpClient = httpClient;
+        this.failedRequests = new ArrayList<>();
+        this.requestQueue = new LinkedTransferQueue<>();
     }
 
     @Override
@@ -29,7 +40,6 @@ public class SensLogPusher implements ConnectorPusher<SensLogModel> {
             logger.warn("Model has no observations."); return;
         }
 
-        int successfullyPushed = 0;
         for (SensLogModel.PassingData data : model.getPassThroughData()) {
 
             URLBuilder urlBuilder = URLBuilder.newBuilder(config.getBaseUrl());
@@ -40,21 +50,34 @@ public class SensLogPusher implements ConnectorPusher<SensLogModel> {
                     .contentType(APPLICATION_JSON)
                     .body(data.getPayload());
 
-            HttpResponse res = httpClient.send(req.build());
+            requestQueue.add(req.build());
+        }
+
+        if (!failedRequests.isEmpty()) {
+            logger.info("Adding <{}> failed requests to the queue.", failedRequests.size());
+            requestQueue.addAll(failedRequests);
+            failedRequests.clear();
+        }
+
+        int totalToPush = requestQueue.size();
+        int successfullyPushed = 0;
+        while (!requestQueue.isEmpty()) {
+            HttpRequest request = requestQueue.remove();
+            HttpResponse res = httpClient.send(request);
 
             if (res.isError()) {
                 logger.error("Request error <{}> with reason: {}", res.getStatus(), res.getBody());
+                logger.error(request.getBody());
+                failedRequests.add(request);
                 continue;
             }
 
             if (res.isOk()) {
-                logger.info("Pushed <{}> successfully: {}", res.getStatus(), res.getBody());
+                logger.debug("Pushed <{}> successfully: {}", res.getStatus(), res.getBody());
                 successfullyPushed++;
             }
-
         }
 
-        int totalSize = model.getPassThroughData().size();
-        logger.info("Pushed {}/{} payloads. For the time <{} - {}>", successfullyPushed, totalSize, model.getFrom(), model.getTo());
+        logger.info("Pushed <{}/{}> payloads. For the time <{} - {}>", successfullyPushed, totalToPush, model.getFrom(), model.getTo());
     }
 }

+ 41 - 25
docker-compose.yaml

@@ -1,6 +1,16 @@
 version: '3.8'
 
 services:
+  demo2logger:
+    container_name: demoLogger
+    build:
+      dockerfile: docker/Dockerfile
+      context: .
+      args:
+        MAVEN_PROFILE: DemoLogger
+    environment:
+      APP_PARAMS: -cf config/demoToLogger.yaml
+      DEBUG: "false"
 
   drutes2senslog:
     container_name: drutesSenslog1
@@ -108,53 +118,59 @@ services:
       APP_PARAMS: -cf config/fofrToTelemetry.yaml
       DEBUG: "true"
 
-  demo2logger:
-    container_name: demoLogger
+  soilscount2senslog:
+    container_name: soilscount2senslog
+    image: senslog/connector-period-soilscount
     build:
       dockerfile: docker/Dockerfile
       context: .
       args:
-        MAVEN_PROFILE: DemoLogger
+        MAVEN_PROFILE: SoilscountSenslog
+#    ports:
+#      - "5005:5005"
     environment:
-      APP_PARAMS: -cf config/demoToLogger.yaml
+      APP_PARAMS: -cf config/soilscountToSenslog.yaml
       DEBUG: "false"
 
-  soilscount2senslog:
-    container_name: soilscountsenslog
+  metno2senslog:
+    container_name: metno2senslog
+    image: senslog/connector-period-metno
     build:
       dockerfile: docker/Dockerfile
       context: .
       args:
-        MAVEN_PROFILE: SoilscountSenslog
-    ports:
-      - "5005:5005"
+        MAVEN_PROFILE: MetnoSenslog
+        CONFIG_FILE: ./config/metnoToSenslog.yaml
+    #    ports:
+#      - "5005:5005"
     environment:
-      APP_PARAMS: -cf config/soilscountToSenslog.yaml
-      DEBUG: "true"
+      DEBUG: "false"
 
-  metno2senslog:
-    container_name: metnosenslog
+  era5land2senslog:
+    container_name: era5land2senslog
+    image: senslog/connector-period-era5land
     build:
       dockerfile: docker/Dockerfile
       context: .
       args:
-        MAVEN_PROFILE: MetnoSenslog
-    ports:
-      - "5005:5005"
+        MAVEN_PROFILE: Era5LandSenslog
+        CONFIG_FILE: ./config/openMeteoEra5LandToSenslog.yaml
+    #    ports:
+#      - "5005:5005"
     environment:
-      APP_PARAMS: -cf config/metnoToSenslog.yaml
-      DEBUG: "true"
+      DEBUG: "false"
 
-  openmeteo2senslog:
-    container_name: openmeteosenslog
+  iconeu2senslog:
+    container_name: iconeu2senslog
+    image: senslog/connector-period-iconeu
     build:
       dockerfile: docker/Dockerfile
       context: .
       args:
-        MAVEN_PROFILE: OpenMeteoSenslog
-    ports:
-      - "5005:5005"
+        MAVEN_PROFILE: IconEUSenslog
+        CONFIG_FILE: ./config/openMeteoIconEUToSenslog.yaml
+#    ports:
+#      - "5005:5005"
     environment:
-      APP_PARAMS: -cf config/openMeteoToSenslog.yaml
-      DEBUG: "true"
+      DEBUG: "false"
 

+ 5 - 7
docker/Dockerfile

@@ -2,18 +2,16 @@
 FROM zenika/alpine-maven:3-jdk8
 
 ARG MAVEN_PROFILE
-ENV BUILD_PROFILE $MAVEN_PROFILE
+ARG CONFIG_FILE
 
-COPY docker/filebeat.yml /etc/conf.d/
-COPY docker/start.sh /app/
+ENV BUILD_PROFILE $MAVEN_PROFILE
 
 COPY . /app/
 
-WORKDIR /app
+COPY ./docker/start.sh /app/
+COPY $CONFIG_FILE /app/config.yaml
 
-#RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
-#RUN apk update
-#RUN apk add filebeat
+WORKDIR /app
 
 RUN mvn clean
 RUN mvn package -P $MAVEN_PROFILE -DskipTests=true

+ 2 - 13
docker/start.sh

@@ -1,18 +1,7 @@
 #!/bin/sh
 
-BUILD_FOLDER="bin"
-MAIN_CLASS="cz.senslog.connector.app.Main" 
-LOG_PATH="/var/log/connector-app"
-DEBUG_PORT="5005"
-
-FILEBEAT_CONFIG_FILE="/etc/conf.d/filebeat.yml"
-
 if [ $DEBUG = "true" ]; then
-    DEBUG_PARAM="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:$DEBUG_PORT"
-fi
-
-if [ $LOG_MONITOR = "true" ]; then
-    filebeat  -c $FILEBEAT_CONFIG_FILE & 
+    DEBUG_PARAM="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005"
 fi
 
-java -cp "$BUILD_FOLDER/*" -DlogPath=$LOG_PATH $DEBUG_PARAM $MAIN_CLASS $APP_PARAMS
+java -cp "bin/*" -DlogPath=/var/log/connector-app $DEBUG_PARAM cz.senslog.connector.app.Main -cf /app/config.yaml