3 次代码提交 29120536a2 ... fcc93f3786

作者 SHA1 备注 提交日期
  Lukas Cerny fcc93f3786 Others 1 年之前
  Lukas Cerny 950da31a25 Connector Theros 1 年之前
  Lukas Cerny fb5a4b967c Connector Fofr 1 年之前
共有 32 个文件被更改,包括 1145 次插入145 次删除
  1. 11 0
      README.md
  2. 33 0
      config/fofrToTelemetry.yaml
  3. 1 1
      config/senslog1Analytics.yaml
  4. 14 7
      config/senslogTelemetryTheros.yaml
  5. 1 0
      connector-fetch-alapans/src/main/java/cz/senslog/connector/fetch/alapans/AlapansConfig.java
  6. 1 1
      connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateProxySessionTest.java
  7. 27 2
      connector-fetch-fofr/pom.xml
  8. 77 0
      connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/FofrConfig.java
  9. 33 0
      connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/FofrFetchProvider.java
  10. 250 0
      connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/FofrFetcher.java
  11. 28 0
      connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/OrderInfo.java
  12. 144 0
      connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/ParcelInfo.java
  13. 88 0
      connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/ParcelTrackingStatus.java
  14. 26 0
      connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/WarehouseInfo.java
  15. 1 0
      connector-fetch-fofr/src/main/resources/META-INF/services/cz.senslog.connector.fetch.api.ConnectorFetchProvider
  16. 39 0
      connector-fetch-fofr/src/test/java/cz/senslog/connector/fetch/fofr/FofrFetcherTest.java
  17. 2 6
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/ConnectorFetchSenslogTelemetryProvider.java
  18. 29 0
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/EventInfo.java
  19. 15 1
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetryConfig.java
  20. 146 70
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetryFetcher.java
  21. 0 25
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetryProxySession.java
  22. 11 6
      connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetrySession.java
  23. 1 1
      connector-fetch-senslog-telemetry/src/main/resources/META-INF/services/cz.senslog.connector.fetch.api.ConnectorFetchProvider
  24. 20 10
      connector-fetch-senslog-telemetry/src/test/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetryFetcherTest.java
  25. 1 0
      connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetcher.java
  26. 4 3
      connector-fetch-senslog-v1/src/test/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetcherTest.java
  27. 26 7
      connector-push-telemetry/pom.xml
  28. 28 0
      connector-push-telemetry/src/main/java/cz/senslog/connector/push/telemetry/TelemetryConfig.java
  29. 31 0
      connector-push-telemetry/src/main/java/cz/senslog/connector/push/telemetry/TelemetryPushProvider.java
  30. 54 0
      connector-push-telemetry/src/main/java/cz/senslog/connector/push/telemetry/TelemetryPusher.java
  31. 1 0
      connector-push-telemetry/src/main/resources/META-INF/services/cz.senslog.connector.push.api.ConnectorPushProvider
  32. 2 5
      connector-push-theros/src/main/java/cz/senslog/connector/push/theros/TherosPusher.java

+ 11 - 0
README.md

@@ -54,6 +54,17 @@ $ docker compose up
 $ docker compose up <service>          // e.g. demoLogger
 ```
 
+To export docker image
+```shell
+$ docker save <image_name> > name.tar
+$ docker save connector-period-senslogtelemetry2theros > connector_telemetry2theros.tar
+```
+
+To run docker image
+```shell
+docker run --name <container_name> -d --ulimit nofile=122880:122880 -e APP_PARAMS="-cf config/<config_name>.yaml" <image_name>
+docker run --name senslog_connector_senslog2analytics -d --ulimit nofile=122880:122880 -e APP_PARAMS="-cf config/senslog1Analytics.yaml" connector-period-senslog2analytics
+```
 
 ## Java & Maven
 The application supports **Java 8+** and uses **Maven** as a build automation tool. Configuration for Maven contains

+ 33 - 0
config/fofrToTelemetry.yaml

@@ -0,0 +1,33 @@
+settings:
+  - Fofr:
+      name: "Fofr Fetcher"
+      provider: "cz.senslog.connector.fetch.fofr.FofrFetchProvider"
+
+      orderStatesUrl: "https://objednavky.fofrcz.cz/stavy/fofr/xml"
+      warehouseLocationsUrl: "https://objednavky.fofrcz.cz/seznam-dep/json"
+      parcelTrackingUrl: "https://objednavky.fofrcz.cz/procedure/track_and_trace_sledovani_zasilek.php"
+      parcelInfoUrl: "https://objednavky.fofrcz.cz/zasilka-info/%s/%d/json"
+      activeOrdersUrl: "http://172.17.0.1:8085/integration/tracking/all"
+
+      deliveryStatus: 6
+
+      missingWarehouses:
+        - code: CP
+          location: [48.9763139, 14.5065361]
+
+  - Telemetry:
+      name: "SensLog Telemetry"
+      provider: "cz.senslog.connector.push.telemetry.TelemetryPushProvider"
+
+      infoUrl: "http://172.17.0.1:8085/info"
+      stopDeliveryUrl: "http://172.17.0.1:8085/integration/tracking/stop"
+      updateLocationsUrl: "http://172.17.0.1:8085/campaigns/4/units/observations/locations"
+
+
+connectors:
+  - FofrTelemetry:
+      fetcher: "Fofr"
+      pusher: "Telemetry"    # name of the pusher from 'settings'
+      period: 10          # 86_400 = 24h
+#      startAt: "02:30:00" # hh:mm:ss  # non-mandatory attribute
+      initDelay: 5        # non-mandatory attribute

+ 1 - 1
config/senslog1Analytics.yaml

@@ -6,7 +6,7 @@ settings:
 
         timeZone: "Europe/Prague"
         startDate: now # 2024-04-17T11:00:00+02:00
-        delayStartDate: 60 # minutes
+        delayStartDate: 20 # minutes
         interval: 10 # minutes
         user: "osek"
 

+ 14 - 7
config/senslogTelemetryTheros.yaml

@@ -2,15 +2,22 @@
 settings:
     - Telemetry:
         name: "SensLog Telemetry"
-        provider: "cz.senslog.fetch.senslog.telemetry.ConnectorFetchSenslogTelemetryProvider"
+        provider: "cz.senslog.connector.fetch.senslog.telemetry.ConnectorFetchSenslogTelemetryProvider"
 
         baseUrl: "https://theros.wirelessinfo.cz/"
         bearerToken: "#123"
 
-        startAt: "2023-08-25T00:00:00+00:00"
-        interval: 4    # hours
-        campaignId: 2
-        limit: 10
+        startAt: "now" # "2024-12-01T11:00:00+00:00"
+        interval: 1    # hours
+        limit: 100
+
+        campaignId: 4   # SumavaProduct testing campaign
+        actionId: 1     # DELIVERY
+        entityId: 1     # SUMAVA
+        allowedStations: # units
+            - 1305167563014004
+            - 1305167562660700
+
 
     - Theros:
         name: "Theros"
@@ -28,6 +35,6 @@ connectors:
     - TelemetryTheros:
         fetcher: "Telemetry"
         pusher: "Theros"
-        period: 5 # 86_400 # 24h
+        period: 900 # 300 = 5min 86_400 # 24h
 #        startAt: "02:30:00" # hh:mm:ss
-        initDelay: 5
+        initDelay: 900

+ 1 - 0
connector-fetch-alapans/src/main/java/cz/senslog/connector/fetch/alapans/AlapansConfig.java

@@ -15,6 +15,7 @@ public class AlapansConfig {
 
     private final LocalTime newDataExecutionTime;
     private final AlapansSessionProxyConfig sessionProxyConfig;
+
     public AlapansConfig(DefaultConfig defaultConfig) {
         this.baseUrl = defaultConfig.getStringProperty("baseUrl");
         this.sessionProxyConfig = defaultConfig.containsProperty("sessionProxy") ?

+ 1 - 1
connector-fetch-fieldclimate/src/test/java/cz/senslog/connector/fetch/fieldclimate/FieldClimateProxySessionTest.java

@@ -1,7 +1,7 @@
 package cz.senslog.connector.fetch.fieldclimate;
 
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
 import cz.senslog.connector.model.config.DefaultConfig;
-import cz.senslog.connector.fetch.gfs.ExecutableFetcher;
 import cz.senslog.connector.tools.http.HttpClient;
 import cz.senslog.connector.tools.http.HttpRequest;
 import cz.senslog.connector.tools.http.HttpResponse;

+ 27 - 2
connector-fetch-fofr/pom.xml

@@ -9,12 +9,37 @@
         <version>1.0-SNAPSHOT</version>
     </parent>
 
-    <artifactId>connector-fetch-fofr</artifactId>
-
     <properties>
         <maven.compiler.source>17</maven.compiler.source>
         <maven.compiler.target>17</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
 
+    <artifactId>connector-fetch-fofr</artifactId>
+    <name>fetch-senslog-telemetry</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>cz.senslog</groupId>
+            <artifactId>connector-fetch-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.dataformat</groupId>
+            <artifactId>jackson-dataformat-xml</artifactId>
+            <version>2.11.1</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
 </project>

+ 77 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/FofrConfig.java

@@ -0,0 +1,77 @@
+package cz.senslog.connector.fetch.fofr;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+
+import java.util.*;
+
+public class FofrConfig {
+
+    private final String orderStatesUrl;
+    private final String warehouseLocationsUrl;
+    private final String parcelTrackingUrl;
+    private final String parcelInfoUrl;
+    private final String activeOrdersUrl;
+
+    private final int deliveryStatus;
+    private final Set<WarehouseInfo> missingWarehouses;
+
+    public FofrConfig(DefaultConfig defaultConfig) {
+        this.orderStatesUrl = defaultConfig.getStringProperty("orderStatesUrl");
+        this.warehouseLocationsUrl = defaultConfig.getStringProperty("warehouseLocationsUrl");
+        this.parcelTrackingUrl = defaultConfig.getStringProperty("parcelTrackingUrl");
+        this.parcelInfoUrl = defaultConfig.getStringProperty("parcelInfoUrl");
+        this.activeOrdersUrl = defaultConfig.getStringProperty("activeOrdersUrl");
+        this.deliveryStatus = defaultConfig.getIntegerProperty("deliveryStatus");
+        if (defaultConfig.containsProperty("missingWarehouses")) {
+            Object wObject = defaultConfig.getProperty("missingWarehouses");
+            if (wObject instanceof List) {
+                List<?> wList = (List<?>) wObject;
+                this.missingWarehouses = new HashSet<>(wList.size());
+                for (Object w : wList) {
+                    if (w instanceof Map) {
+                        Map<?, ?> wMap = (Map<?, ?>) w;
+                        List<?> coord = (List<?>) wMap.get("location");
+                        if (coord.size() == 2) {
+                            String code = (String) wMap.get("code");
+                            Double lat = (Double) coord.get(0);
+                            Double lon = (Double) coord.get(1);
+                            missingWarehouses.add(new WarehouseInfo(code, lat, lon));
+                        }
+                    }
+                }
+            } else {
+                this.missingWarehouses = Collections.emptySet();
+            }
+        } else {
+            this.missingWarehouses = Collections.emptySet();
+        }
+    }
+
+    public int getDeliveryStatus() {
+        return deliveryStatus;
+    }
+
+    public String getActiveOrdersUrl() {
+        return activeOrdersUrl;
+    }
+
+    public String getOrderStatesUrl() {
+        return orderStatesUrl;
+    }
+
+    public String getWarehouseLocationsUrl() {
+        return warehouseLocationsUrl;
+    }
+
+    public String getParcelTrackingUrl() {
+        return parcelTrackingUrl;
+    }
+
+    public String getParcelInfoUrl() {
+        return parcelInfoUrl;
+    }
+
+    public Set<WarehouseInfo> getMissingWarehouses() {
+        return missingWarehouses;
+    }
+}

+ 33 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/FofrFetchProvider.java

@@ -0,0 +1,33 @@
+package cz.senslog.connector.fetch.fofr;
+
+import cz.senslog.connector.fetch.api.ExecutableFetcher;
+import cz.senslog.connector.model.api.AbstractModel;
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.fofr.FofrModel;
+import cz.senslog.connector.tools.http.HttpClient;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class FofrFetchProvider implements cz.senslog.connector.fetch.api.ConnectorFetchProvider {
+
+    private static final Logger logger = LogManager.getLogger(FofrFetchProvider.class);
+
+    @Override
+    public ExecutableFetcher<? extends AbstractModel> createExecutableFetcher(DefaultConfig defaultConfig) {
+        logger.info("Initialization a new fetch provider {}.", FofrFetchProvider.class.getSimpleName());
+
+        logger.debug("Creating a new configuration.");
+        FofrConfig config = new FofrConfig(defaultConfig);
+        logger.info("Configuration for {} was created successfully.", FofrFetcher.class.getSimpleName());
+
+
+        logger.debug("Creating a new instance of {}.", FofrFetcher.class);
+        FofrFetcher fetcher = new FofrFetcher(config, HttpClient.newHttpSSLClient());
+        logger.info("Fetcher for {} was created successfully.", FofrFetcher.class.getSimpleName());
+
+        ExecutableFetcher<FofrModel> executor = ExecutableFetcher.create(fetcher);
+        logger.info("Fetcher executor for {} was created successfully.", FofrFetcher.class.getSimpleName());
+
+        return executor;
+    }
+}

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

@@ -0,0 +1,250 @@
+package cz.senslog.connector.fetch.fofr;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import cz.senslog.connector.fetch.api.ConnectorFetcher;
+import cz.senslog.connector.model.api.VoidSession;
+import cz.senslog.connector.model.fofr.FofrModel;
+import cz.senslog.connector.model.fofr.ParcelUpdate;
+import cz.senslog.connector.tools.http.HttpClient;
+import cz.senslog.connector.tools.http.HttpRequest;
+import cz.senslog.connector.tools.http.HttpResponse;
+import cz.senslog.connector.tools.http.URLBuilder;
+import cz.senslog.connector.tools.util.Encoding;
+import cz.senslog.connector.tools.util.Tuple;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static cz.senslog.connector.tools.json.BasicJson.jsonToObject;
+import static cz.senslog.connector.tools.json.BasicJson.objectToJson;
+import static java.lang.String.format;
+import static java.time.format.DateTimeFormatter.ofPattern;
+
+public class FofrFetcher implements ConnectorFetcher<VoidSession, FofrModel> {
+
+    private static final Logger logger = LogManager.getLogger(FofrFetcher.class);
+
+
+    private final FofrConfig config;
+    private final HttpClient httpClient;
+    private final XmlMapper xmlMapper;
+
+    private Map<String, WarehouseInfo> warehouses;
+    private Map<Integer, String> orderStates;
+
+    protected FofrFetcher(FofrConfig config, HttpClient httpClient) {
+        this.config = config;
+        this.httpClient = httpClient;
+        this.xmlMapper = new XmlMapper();
+
+        this.warehouses = Collections.emptyMap();
+        this.orderStates = Collections.emptyMap();
+    }
+
+    @Override
+    public void init() throws Exception {
+        {
+            HttpRequest request = HttpRequest.newBuilder().GET()
+                    .url(URLBuilder.newBuilder(config.getWarehouseLocationsUrl()).build())
+                    .build();
+
+            HttpResponse response = httpClient.send(request);
+
+            if (response.isError()) {
+                throw logger.throwing(new Exception(format(
+                        "Can not get information about the warehouses. %s", response.getBody()
+                )));
+            }
+
+            String responseBody = response.getBody();
+            List<?> responseDTO = jsonToObject(responseBody, List.class);
+            warehouses = responseDTO.stream().map(Map.class::cast).map(w -> new WarehouseInfo(
+                        (String)w.get("KOD_STREDISKA"),
+                        Double.parseDouble((String)w.get("GEOCODE_LAT")),
+                        Double.parseDouble((String)w.get("GEOCODE_LNG"))
+                    )).collect(Collectors.toMap(WarehouseInfo::getCode, Function.identity()));
+
+            for (WarehouseInfo missingWarehouse : config.getMissingWarehouses()) {
+                warehouses.put(missingWarehouse.getCode(), missingWarehouse);
+            }
+        }
+
+        {
+
+            HttpRequest request = HttpRequest.newBuilder().GET()
+                    .url(URLBuilder.newBuilder(config.getOrderStatesUrl()).build())
+                    .build();
+
+            HttpResponse response = httpClient.send(request, Encoding.WINDOWS_1250);
+
+            if (response.isError()) {
+                throw logger.throwing(new Exception(format(
+                        "Can not get information about the statuses. %s", response.getBody()
+                )));
+            }
+
+            String responseBody = response.getBody();
+            List<?> xml = xmlMapper.readValue(responseBody, List.class);
+            orderStates = xml.stream().map(Map.class::cast).map(m -> Tuple.of(
+                    Integer.parseInt((String)m.get("ID")),
+                    (String)m.get("POPIS"))
+            ).collect(Collectors.toMap(Tuple::getItem1, Tuple::getItem2));
+
+        }
+    }
+
+    @Override
+    public FofrModel fetch(Optional<VoidSession> session) {
+
+        List<OrderInfo> orderInfos = Collections.emptyList();
+//
+//        {
+//            HttpRequest request = HttpRequest.newBuilder().GET()
+//                    .url(URLBuilder.newBuilder(config.getActiveOrdersUrl()).build())
+//                    .build();
+//
+//            HttpResponse response = httpClient.send(request);
+//
+//            if (response.isOk()) {
+//                String responseBody = response.getBody();
+//                List<?> orderList = jsonToObject(responseBody, List.class);
+//                orderInfos = new ArrayList<>(orderList.size());
+//                for (Object orderO : orderList) {
+//                    if (orderO instanceof Map) {
+//                        Map<?, ?> orderMap = (Map<?, ?>) orderO;
+//                        orderInfos.add(new OrderInfo(
+//                                ((Double) orderMap.get("orderId")).longValue(),
+//                                ((Double) orderMap.get("parcelId")).longValue(),
+//                                ((Double) orderMap.get("unitId")).longValue()
+//                        ));
+//                    }
+//                }
+//            }
+//        }
+
+        orderInfos = Arrays.asList(new OrderInfo(0, 2590161966L, 0));
+
+        List<ParcelUpdate> parcelsToUpdate = new ArrayList<>();
+        for (OrderInfo orderInfo : orderInfos) {
+
+            long parcelId = orderInfo.getParcelId();
+
+            HttpRequest reqTrackingInfo = HttpRequest.newBuilder().GET()
+                    .url(URLBuilder.newBuilder(config.getParcelTrackingUrl())
+                            .addParam("shipment", parcelId)
+                            .build())
+                    .build();
+
+            HttpResponse resTrackingInfo = httpClient.send(reqTrackingInfo, Encoding.WINDOWS_1250);
+            if (resTrackingInfo.isError()) {
+                logger.error(resTrackingInfo.getBody()); continue;
+            }
+
+            try {
+                String trackingInfoBody = resTrackingInfo.getBody();
+                ParcelTrackingStatus trackingInfo = xmlMapper.readValue(trackingInfoBody, ParcelTrackingStatus.class);
+
+                if (trackingInfo.getId() != (parcelId)) {
+                    logger.warn("Received tracking ID does not match: {} != {}", parcelId, trackingInfo.getId()); continue;
+                }
+
+                // TODO compare last status with the one from session if any updates
+                // TODO if no update, continue to next parcelId
+
+                if (trackingInfo.getStatuses().isEmpty()) {
+                    logger.warn("No status for the parcel ID {}", trackingInfo.getId()); continue;
+                }
+
+                int parcelYear = trackingInfo.getStatuses().get(0).getTimestamp().getYear();
+
+                HttpRequest reqParcelInfo = HttpRequest.newBuilder().GET()
+                        .url(URLBuilder.newBuilder(String.format(config.getParcelInfoUrl(), parcelId, parcelYear)
+                                ).build())
+                        .build();
+
+                HttpResponse resParcelInfo = httpClient.send(reqParcelInfo);
+                if (resParcelInfo.isError()) {
+                    logger.error(resParcelInfo.getBody()); continue;
+                }
+
+                String parcelInfoBody = resParcelInfo.getBody();
+                List<?> parcelInfoList = jsonToObject(parcelInfoBody, List.class);
+                if (parcelInfoList.isEmpty()) {
+                    logger.error("Parcel info of the ID " + parcelId + " not found"); continue;
+                }
+
+                if (!(parcelInfoList.get(0) instanceof Map)) {
+                    logger.error("Wrong type of parcel info."); continue;
+                }
+
+                Map<?,?> parcelInfo = (Map<?,?>) parcelInfoList.get(0);
+                long shipmentNum = Long.parseLong((String) parcelInfo.get("shipment_num"));
+                if (parcelId != (shipmentNum)) {
+                    logger.warn("Received parcel info data does not match: {} != {}", parcelId, trackingInfo.getId()); continue;
+                }
+
+                String officeBranchCode = (String) parcelInfo.get("kod_pobocky");
+                WarehouseInfo officeBranch = warehouses.get(officeBranchCode);
+
+                int lastTrackingStatusCode = ((Double)parcelInfo.get("posledni_statut")).intValue();
+                List<ParcelTrackingStatus.Status> statuses = trackingInfo.getStatuses();
+                if (statuses.get(statuses.size()-1).getCode() != lastTrackingStatusCode) {
+                    statuses.sort(Comparator.comparing(ParcelTrackingStatus.Status::getTimestamp));
+                }
+                List<ParcelInfo.TrackingStatus> trackingStatuses = new ArrayList<>(statuses.size());
+                for (ParcelTrackingStatus.Status status : statuses) {
+                    String statusName = orderStates.get(status.getCode());
+                    if (statusName == null) {
+                        logger.warn("No status name for '{}'", status.getCode());
+                    }
+                    WarehouseInfo transitWarehouse = warehouses.get(status.getWarehouseCode());
+                    if (transitWarehouse == null) {
+                        logger.warn("No warehouse for '{}'", status.getWarehouseCode());
+                    }
+                    trackingStatuses.add(new ParcelInfo.TrackingStatus(
+                            status.getCode(), statusName, status.getTimestamp(), transitWarehouse
+                    ));
+                }
+
+                LocalDateTime exportDate = LocalDateTime.parse((String)parcelInfo.get("datum_exportu"), ofPattern("yyyy-MM-dd HH:mm:ss"));
+
+                ParcelInfo parcel = new ParcelInfo(parcelId, officeBranch, exportDate, trackingStatuses,
+                        new ParcelInfo.Address(
+                                (String) parcelInfo.get("odes_firma"),
+                                (String) parcelInfo.get("odes_stat"),
+                                (String) parcelInfo.get("odes_mesto"),
+                                (String) parcelInfo.get("odes_ulice"),
+                                (String) parcelInfo.get("odes_psc")
+                        ),
+                        new ParcelInfo.Address(
+                                (String) parcelInfo.get("prij_firma"),
+                                (String) parcelInfo.get("prij_stat"),
+                                (String) parcelInfo.get("prij_mesto"),
+                                (String) parcelInfo.get("prij_ulice"),
+                                (String) parcelInfo.get("prij_psc")
+                        )
+                );
+
+                if (parcel.lastTrackingStatus().getId() == config.getDeliveryStatus()) {
+                    // TODO parcel is delivered
+                    // GET GPS coordination from delivery address
+                }
+
+                System.out.println(objectToJson(parcel));
+
+            } catch (JsonProcessingException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        return new FofrModel(parcelsToUpdate, OffsetDateTime.now(), OffsetDateTime.now());
+    }
+
+
+}

+ 28 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/OrderInfo.java

@@ -0,0 +1,28 @@
+package cz.senslog.connector.fetch.fofr;
+
+import java.time.OffsetDateTime;
+
+public class OrderInfo {
+
+    private final long orderId;
+    private final long parcelId;
+    private final long unitId;
+
+    OrderInfo(long orderId, long parcelId, long unitId) {
+        this.orderId = orderId;
+        this.parcelId = parcelId;
+        this.unitId = unitId;
+    }
+
+    public long getOrderId() {
+        return orderId;
+    }
+
+    public long getParcelId() {
+        return parcelId;
+    }
+
+    public long getUnitId() {
+        return unitId;
+    }
+}

+ 144 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/ParcelInfo.java

@@ -0,0 +1,144 @@
+package cz.senslog.connector.fetch.fofr;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+
+public class ParcelInfo {
+
+    public static class Address {
+        private final String name;
+        private final String country;
+        private final String city;
+        private final String street;
+        private final String zip;
+
+        public Address(String name, String country, String city, String street, String zip) {
+            this.name = name;
+            this.country = country;
+            this.city = city;
+            this.street = street;
+            this.zip = zip;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getCountry() {
+            return country;
+        }
+
+        public String getCity() {
+            return city;
+        }
+
+        public String getStreet() {
+            return street;
+        }
+
+        public String getZip() {
+            return zip;
+        }
+    }
+
+    public static class TrackingStatus {
+        private final int id;
+        private final String name;
+        private final LocalDateTime timestamp;
+        private final WarehouseInfo warehouseInfo;
+
+        public TrackingStatus(int id, String name, LocalDateTime timestamp, WarehouseInfo warehouseInfo) {
+            this.id = id;
+            this.name = name;
+            this.timestamp = timestamp;
+            this.warehouseInfo = warehouseInfo;
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public LocalDateTime getTimestamp() {
+            return timestamp;
+        }
+
+        public WarehouseInfo getWarehouseInfo() {
+            return warehouseInfo;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            TrackingStatus that = (TrackingStatus) o;
+            return id == that.id && Objects.equals(timestamp, that.timestamp);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(id, timestamp);
+        }
+    }
+
+    private final long parcelId;
+    private final WarehouseInfo warehouseOrigin;
+    private final LocalDateTime exportDatetime;
+    private final List<TrackingStatus> trackingStatuses;
+    private final Address dispatcherAddress;
+    private final Address recipientAddress;
+
+    public ParcelInfo(long parcelId, WarehouseInfo warehouseOrigin, LocalDateTime exportDatetime, List<TrackingStatus> trackingStatuses, Address dispatcherAddress, Address recipientAddress) {
+        this.parcelId = parcelId;
+        this.warehouseOrigin = warehouseOrigin;
+        this.exportDatetime = exportDatetime;
+        this.trackingStatuses = trackingStatuses;
+        this.dispatcherAddress = dispatcherAddress;
+        this.recipientAddress = recipientAddress;
+    }
+
+    public long getParcelId() {
+        return parcelId;
+    }
+
+    public WarehouseInfo getWarehouseOrigin() {
+        return warehouseOrigin;
+    }
+
+    public LocalDateTime getExportDatetime() {
+        return exportDatetime;
+    }
+
+    public List<TrackingStatus> getTrackingStatuses() {
+        return trackingStatuses;
+    }
+
+    public Address getDispatcherAddress() {
+        return dispatcherAddress;
+    }
+
+    public Address getRecipientAddress() {
+        return recipientAddress;
+    }
+
+    public TrackingStatus lastTrackingStatus() {
+        return trackingStatuses.get(trackingStatuses.size()-1);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ParcelInfo that = (ParcelInfo) o;
+        return parcelId == that.parcelId && Objects.equals(exportDatetime, that.exportDatetime);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(parcelId, exportDatetime);
+    }
+}

+ 88 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/ParcelTrackingStatus.java

@@ -0,0 +1,88 @@
+package cz.senslog.connector.fetch.fofr;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+@JacksonXmlRootElement(localName = "trackandtrace")
+public class ParcelTrackingStatus implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    public static class Fofr {
+
+        @JacksonXmlProperty(isAttribute = true, localName = "ID")
+        private String id;
+        @JacksonXmlProperty(isAttribute = true, localName = "CentreOrigin")
+        private String warehouseOrigin;
+        @JacksonXmlProperty(isAttribute = true, localName = "CentreFrom")
+        private String CentreFrom;
+        @JacksonXmlProperty(isAttribute = true, localName = "CentreTo")
+        private String warehouseDestination;
+        @JacksonXmlProperty(isAttribute = true, localName = "Code")
+        private String Code;
+        @JacksonXmlProperty(isAttribute = true, localName = "Colli")
+        private String Colli;
+        @JacksonXmlProperty(isAttribute = true, localName = "Weight")
+        private String Weight;
+        @JacksonXmlProperty(isAttribute = true, localName = "Volume")
+        private String Volume;
+
+        @JacksonXmlElementWrapper(localName = "Statuses")
+        @JacksonXmlProperty(localName = "Status")
+        List<Status> statuses;
+    }
+
+    public static class Status {
+        @JacksonXmlProperty(isAttribute = true, localName = "StCode")
+        private String code;
+
+        @JacksonXmlProperty(isAttribute = true, localName = "StDate")
+        private String timestamp;
+
+        @JacksonXmlProperty(isAttribute = true, localName = "Depo")
+        private String warehouseCode;
+
+        @JacksonXmlProperty(isAttribute = true, localName = "Kontakt")
+        private String contact;
+
+        @JacksonXmlProperty(isAttribute = true, localName = "StInfo")
+        private String info;
+
+        public int getCode() {
+            return Integer.parseInt(code);
+        }
+
+        public LocalDateTime getTimestamp() {
+            return LocalDateTime.parse(timestamp, DateTimeFormatter.ofPattern("yyyy-MM-d H:m"));
+        }
+
+        public String getWarehouseCode() {
+            return warehouseCode;
+        }
+    }
+
+    @JacksonXmlElementWrapper(localName = "FOFR")
+    private Fofr parcelInfo;
+
+    public long getId() {
+        return Long.parseLong(parcelInfo.id);
+    }
+
+    public String getWarehouseOrigin() {
+        return parcelInfo.warehouseOrigin;
+    }
+
+    public String getWarehouseDestination() {
+        return parcelInfo.warehouseDestination;
+    }
+
+    public List<Status> getStatuses() {
+        return parcelInfo.statuses;
+    }
+}

+ 26 - 0
connector-fetch-fofr/src/main/java/cz/senslog/connector/fetch/fofr/WarehouseInfo.java

@@ -0,0 +1,26 @@
+package cz.senslog.connector.fetch.fofr;
+
+public class WarehouseInfo {
+
+    private final String code;
+    private final double latitude;
+    private final double longitude;
+
+    public WarehouseInfo(String code, double latitude, double longitude) {
+        this.code = code;
+        this.latitude = latitude;
+        this.longitude = longitude;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public double getLatitude() {
+        return latitude;
+    }
+
+    public double getLongitude() {
+        return longitude;
+    }
+}

+ 1 - 0
connector-fetch-fofr/src/main/resources/META-INF/services/cz.senslog.connector.fetch.api.ConnectorFetchProvider

@@ -0,0 +1 @@
+cz.senslog.connector.fetch.fofr.FofrFetchProvider

+ 39 - 0
connector-fetch-fofr/src/test/java/cz/senslog/connector/fetch/fofr/FofrFetcherTest.java

@@ -0,0 +1,39 @@
+package cz.senslog.connector.fetch.fofr;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.tools.http.HttpClient;
+import cz.senslog.connector.tools.util.DateTrunc;
+import org.junit.jupiter.api.Test;
+
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FofrFetcherTest {
+
+    @Test
+    void fetch() throws Exception {
+
+        DefaultConfig defaultConfig = new DefaultConfig("fofr", null);
+        defaultConfig.setProperty("orderStatesUrl", "https://objednavky.fofrcz.cz/stavy/fofr/xml");
+            defaultConfig.setProperty("warehouseLocationsUrl", "https://objednavky.fofrcz.cz/seznam-dep/json");
+        defaultConfig.setProperty("parcelTrackingUrl", "https://objednavky.fofrcz.cz/procedure/track_and_trace_sledovani_zasilek.php");
+        defaultConfig.setProperty("parcelInfoUrl", "https://objednavky.fofrcz.cz/zasilka-info/%d/%d/json");
+        defaultConfig.setProperty("activeOrdersUrl", "http://172.17.0.1:8085/integration/tracking/all");
+        defaultConfig.setProperty("deliveryStatus", 6);
+
+        defaultConfig.setProperty("missingWarehouses", Collections.singletonList(
+                new HashMap<String, Object>() {{
+                    put("code", "CB");
+                    put("location", Arrays.asList(49.4854338, 16.6436640));
+                }}
+        ));
+
+        FofrConfig config = new FofrConfig(defaultConfig);
+
+        FofrFetcher fofrFetcher = new FofrFetcher(config, HttpClient.newHttpClient());
+
+        fofrFetcher.init();
+        fofrFetcher.fetch(Optional.empty());
+    }
+}

+ 2 - 6
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/ConnectorFetchSenslogTelemetryProvider.java

@@ -1,4 +1,4 @@
-package cz.senslog.fetch.senslog.telemetry;
+package cz.senslog.connector.fetch.senslog.telemetry;
 
 import cz.senslog.connector.fetch.api.ExecutableFetcher;
 import cz.senslog.connector.model.api.GeoJsonModel;
@@ -15,17 +15,13 @@ public class ConnectorFetchSenslogTelemetryProvider implements cz.senslog.connec
     public ExecutableFetcher<GeoJsonModel> createExecutableFetcher(DefaultConfig defaultConfig) {
         logger.info("Initialization a new fetch provider {}.", ConnectorFetchSenslogTelemetryProvider.class.getSimpleName());
 
-        logger.debug("Creating a new configuration.");
         SensLogTelemetryConfig config = new SensLogTelemetryConfig(defaultConfig);
         logger.info("Configuration for {} was created successfully.", SensLogTelemetryFetcher.class.getSimpleName());
 
-
-        logger.debug("Creating a new instance of {}.", SensLogTelemetryFetcher.class);
         SensLogTelemetryFetcher fetcher = new SensLogTelemetryFetcher(config, HttpClient.newHttpSSLClient());
         logger.info("Fetcher for {} was created successfully.", SensLogTelemetryFetcher.class.getSimpleName());
 
-        logger.debug("Creating a new instance of {}.", SensLogTelemetryProxySession.class);
-        ExecutableFetcher<GeoJsonModel> executor = ExecutableFetcher.createWithProxySession(new SensLogTelemetryProxySession(fetcher));
+        ExecutableFetcher<GeoJsonModel> executor = ExecutableFetcher.create(fetcher);
         logger.info("Fetcher executor for {} was created successfully.", SensLogTelemetryFetcher.class.getSimpleName());
 
         return executor;

+ 29 - 0
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/EventInfo.java

@@ -0,0 +1,29 @@
+package cz.senslog.connector.fetch.senslog.telemetry;
+
+import java.time.OffsetDateTime;
+import java.util.Objects;
+
+public final class EventInfo {
+
+    public enum EventStatus {
+        ONGOING, FINISHED
+    }
+
+    long id;
+    long orderId;
+    EventStatus status;
+    OffsetDateTime fromTime, toTime;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        EventInfo eventInfo = (EventInfo) o;
+        return id == eventInfo.id;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id);
+    }
+}

+ 15 - 1
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetryConfig.java

@@ -1,4 +1,4 @@
-package cz.senslog.fetch.senslog.telemetry;
+package cz.senslog.connector.fetch.senslog.telemetry;
 
 import cz.senslog.connector.model.config.DefaultConfig;
 
@@ -11,7 +11,11 @@ public class SensLogTelemetryConfig {
     private final String baseUrl;
     private final String bearerToken;
     private final LocalDateTime startAt;
+
     private final int campaignId;
+    private final int entityId;
+    private final int actionId;
+
     private final int limit;
     private final int interval;
     private final Set<Long> allowedStations;
@@ -27,6 +31,8 @@ public class SensLogTelemetryConfig {
             this.startAt = null;
         }
         this.campaignId = defaultConfig.getIntegerProperty("campaignId");
+        this.entityId = defaultConfig.getIntegerProperty("entityId");
+        this.actionId = defaultConfig.getIntegerProperty("actionId");
         this.limit = defaultConfig.getIntegerProperty("limit");
         this.interval = defaultConfig.getIntegerProperty("interval");
         if (defaultConfig.containsProperty("allowedStations")) {
@@ -56,6 +62,14 @@ public class SensLogTelemetryConfig {
         return campaignId;
     }
 
+    public int getEntityId() {
+        return entityId;
+    }
+
+    public int getActionId() {
+        return actionId;
+    }
+
     public int getInterval() {
         return interval;
     }

+ 146 - 70
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetryFetcher.java

@@ -1,9 +1,11 @@
-package cz.senslog.fetch.senslog.telemetry;
+package cz.senslog.connector.fetch.senslog.telemetry;
 
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
 import cz.senslog.connector.fetch.api.ConnectorFetcher;
 import cz.senslog.connector.model.api.GeoJsonModel;
+import cz.senslog.connector.model.api.VoidSession;
 import cz.senslog.connector.tools.http.HttpClient;
 import cz.senslog.connector.tools.http.HttpRequest;
 import cz.senslog.connector.tools.http.HttpResponse;
@@ -12,18 +14,19 @@ import cz.senslog.connector.tools.json.BasicJson;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
+import java.lang.reflect.Type;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.OffsetDateTime;
 import java.time.ZoneOffset;
-import java.time.format.DateTimeFormatter;
-import java.util.Optional;
+import java.util.*;
 
 import static cz.senslog.connector.tools.http.HttpContentType.APPLICATION_GEO_JSON;
 import static cz.senslog.connector.tools.http.HttpContentType.APPLICATION_JSON;
 import static cz.senslog.connector.tools.http.HttpHeader.*;
+import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
 
-public class SensLogTelemetryFetcher implements ConnectorFetcher<SensLogTelemetrySession, GeoJsonModel> {
+public class SensLogTelemetryFetcher implements ConnectorFetcher<VoidSession, GeoJsonModel> {
 
     private static final Logger logger = LogManager.getLogger(SensLogTelemetryFetcher.class);
 
@@ -33,11 +36,16 @@ public class SensLogTelemetryFetcher implements ConnectorFetcher<SensLogTelemetr
         OffsetDateTime fromTime, toTime;
     }
 
+    private static final ZoneOffset DEFAULT_TIME_OFFSET = ZoneOffset.UTC;
+
     private final SensLogTelemetryConfig config;
 
     private final HttpClient httpClient;
 
-    private CampaignInfo campaignInfo;
+    private OffsetDateTime startFrom;
+
+    private final Map<Long, SensLogTelemetrySession> multiEventSession;
+
 
     public SensLogTelemetryFetcher() {
         this(null, null);
@@ -46,12 +54,16 @@ public class SensLogTelemetryFetcher implements ConnectorFetcher<SensLogTelemetr
     public SensLogTelemetryFetcher(SensLogTelemetryConfig config, HttpClient httpClient) {
         this.config = config;
         this.httpClient = httpClient;
+        this.multiEventSession = new HashMap<>();
     }
 
     @Override
     public void init() throws Exception {
         assert config != null; assert httpClient != null;
 
+        startFrom = config.getStartAt().atOffset(DEFAULT_TIME_OFFSET);
+
+        /*
         HttpRequest request = HttpRequest.newBuilder().GET()
                 .url(URLBuilder.newBuilder(config.getBaseUrl(), String.format("campaigns/%d", config.getCampaignId()))
                         .addParam("zone", "UTC")
@@ -73,58 +85,100 @@ public class SensLogTelemetryFetcher implements ConnectorFetcher<SensLogTelemetr
         }
 
         logger.info("Initialized fetching telemetries from the campaign '{}'", campaignInfo.name);
+        */
+
     }
 
     @Override
-    public GeoJsonModel fetch(Optional<SensLogTelemetrySession> sessionOpt) {
-
-        final OffsetDateTime now = OffsetDateTime.ofInstant(Instant.now(), ZoneOffset.UTC);
-
-        SensLogTelemetrySession session = sessionOpt.orElse(null);
-        assert session != null;
-
-        if (session.hasNotNext()) {
-            if (session.getFromTime() == null) {
-                if (config.getStartAt() != null) {
-                    session.setFromTime(OffsetDateTime.of(config.getStartAt(), ZoneOffset.UTC));
+    public GeoJsonModel fetch(Optional<VoidSession> sessionOpt) {
+
+        final OffsetDateTime now = OffsetDateTime.ofInstant(Instant.now(), DEFAULT_TIME_OFFSET);
+        {
+            for (long unitId : config.getAllowedStations()) {
+                logger.info("Events for Unit {} from {}.", unitId, startFrom.format(ISO_OFFSET_DATE_TIME));
+                HttpRequest request = HttpRequest.newBuilder().GET()
+                        .url(URLBuilder.newBuilder(config.getBaseUrl(), String.format("/entities/%d/units/%d/actions/%d/events",
+                                        config.getEntityId(), unitId, config.getActionId()))
+                                .addParam("from", startFrom.format(ISO_OFFSET_DATE_TIME))
+                                .addParam("zone", "UTC")
+                                .addParam("navigationLinks", "false")
+                                .build())
+                        .header(ACCEPT, APPLICATION_JSON)
+                        .header(AUTHORIZATION, "Bearer " + config.getBearerToken())
+                        .build();
+
+                HttpResponse response = httpClient.send(request);
+
+                if (response.isOk()) {
+                    Type eventInfoListType = new TypeToken<Collection<EventInfo>>(){}.getType();
+                    List<EventInfo> eventInfoList = BasicJson.jsonToObject(response.getBody(), eventInfoListType);
+                    for (EventInfo eventInfo : eventInfoList) {
+                        if (multiEventSession.containsKey(eventInfo.id)) {
+                            EventInfo sessionEvent = multiEventSession.get(eventInfo.id).getEventInfo();
+                            sessionEvent.fromTime = eventInfo.fromTime;
+                            sessionEvent.toTime = eventInfo.toTime;
+                            sessionEvent.status = eventInfo.status;
+                        } else {
+                            logger.info("Events for Unit {} from {}.", unitId, startFrom.format(ISO_OFFSET_DATE_TIME));
+                            HttpResponse orderStatusRes = httpClient.send(HttpRequest.newBuilder().GET()
+                                    .url(URLBuilder.newBuilder(config.getBaseUrl(), "/integration/tracking/status")
+                                            .addParam("unit_id", unitId)
+                                            .build())
+                                    .header(ACCEPT, APPLICATION_JSON)
+                                    .header(AUTHORIZATION, "Bearer " + config.getBearerToken())
+                                    .build());
+
+                            if (orderStatusRes.isOk()) {
+                                Map<?, ?> orderStatusMap = BasicJson.jsonToObject(orderStatusRes.getBody(), Map.class);
+                                if (orderStatusMap.containsKey("orderId")) {
+                                    eventInfo.orderId = ((Double) orderStatusMap.get("orderId")).longValue();
+                                    multiEventSession.put(eventInfo.id, new SensLogTelemetrySession(eventInfo, startFrom, config.getInterval()));
+                                }
+                            } else {
+                                logger.error(orderStatusRes.getBody());
+                            }
+                        }
+                    }
                 } else {
-                    session.setFromTime(campaignInfo.fromTime);
+                    logger.error(response.getBody());
                 }
-                session.setToTime(session.getFromTime().plusHours(config.getInterval()));
             }
+        }
+        startFrom = now;
 
-            if (session.getToTime().isAfter(now)) {
-                logger.info("To early for the new data, the next interval is in {} minutes.", Duration.between(now, session.getToTime()).toMinutes());
-                return GeoJsonModel.empty();
-            }
+        if (multiEventSession.isEmpty()) {
+            return GeoJsonModel.empty();
         }
 
-        HttpRequest request;
-        if (config.getAllowedStations().isEmpty()) {
+        JsonArray featureCollections = new JsonArray();
+        OffsetDateTime globalTimestampFirst = OffsetDateTime.MAX;
+        OffsetDateTime globalTimestampLast = OffsetDateTime.MIN;
 
-            request = HttpRequest.newBuilder().GET()
-                    .url(URLBuilder.newBuilder(config.getBaseUrl(), String.format("campaigns/%d/units/observations", config.getCampaignId()))
-                            .addParam("limit", config.getLimit())
-                            .addParam("format", "geojson")
-                            .addParam("from", session.getFromTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
-                            .addParam("to", session.getToTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
-                            .addParam("zone", "UTC")
-                            .addParam("offset", session.getOffset())
-                            .addParam("navigationLinks", "false")
-                            .build())
-                    .header(ACCEPT, APPLICATION_GEO_JSON)
-                    .header(AUTHORIZATION, "Bearer " + config.getBearerToken())
-                    .build();
-        } else {
+        for (SensLogTelemetrySession session : multiEventSession.values()) {
+            EventInfo event = session.getEventInfo();
+
+            if (session.hasNotNext()) {
+                if (session.getEventInfo().toTime != null) {
+                    session.setToTime(session.getEventInfo().toTime);
+                } else if (session.getToTime().isAfter(now)) {
+                    session.setToTime(now);
+                }
 
-            long unitId = config.getAllowedStations().iterator().next();
+//                if (session.getToTime().isAfter(now)) {
+//                    logger.info("To early for the new data, the next interval is in {} minutes.", Duration.between(now, session.getToTime()).toMinutes());
+//                    return GeoJsonModel.empty();
+//                }
+            }
 
-            request = HttpRequest.newBuilder().GET()
-                    .url(URLBuilder.newBuilder(config.getBaseUrl(), String.format("campaigns/%d/units/%d/observations", config.getCampaignId(), unitId))
+            String fromParam = session.getFromTime().format(ISO_OFFSET_DATE_TIME);
+            String toParam = session.getToTime().format(ISO_OFFSET_DATE_TIME);
+            logger.info("Getting observations within: {} - {}.", fromParam, toParam);
+            HttpRequest request = HttpRequest.newBuilder().GET()
+                    .url(URLBuilder.newBuilder(config.getBaseUrl(), String.format("/events/%d/observations", event.id))
                             .addParam("limit", config.getLimit())
                             .addParam("format", "geojson")
-                            .addParam("from", session.getFromTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
-                            .addParam("to", session.getToTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
+                            .addParam("from", fromParam)
+                            .addParam("to", toParam)
                             .addParam("zone", "UTC")
                             .addParam("offset", session.getOffset())
                             .addParam("navigationLinks", "false")
@@ -132,42 +186,64 @@ public class SensLogTelemetryFetcher implements ConnectorFetcher<SensLogTelemetr
                     .header(ACCEPT, APPLICATION_GEO_JSON)
                     .header(AUTHORIZATION, "Bearer " + config.getBearerToken())
                     .build();
-        }
 
-        HttpResponse response = httpClient.send(request);
+            HttpResponse response = httpClient.send(request);
 
-        if (response.isError()) {
-            logger.error(response.getBody());
-            return GeoJsonModel.empty();
-        }
+            if (response.isError()) {
+                logger.error(response.getBody());
+                continue;
+            }
 
-        JsonObject geoJson = BasicJson.jsonToObject(response.getBody(), JsonObject.class);
+            JsonObject geoJson = BasicJson.jsonToObject(response.getBody(), JsonObject.class);
 
-        JsonObject metadata = geoJson.remove("metadata").getAsJsonObject();
-        int geoSize = metadata.get("size").getAsInt();
-        session.setOffset(geoSize);
-        session.hasNext(metadata.get("hasNext").getAsBoolean());
+            JsonObject metadata = geoJson.getAsJsonObject("metadata");
+            int geoSize = metadata.get("size").getAsInt();
+            session.setOffset(geoSize);
+            session.hasNext(metadata.get("hasNext").getAsBoolean());
 
-        if (session.hasNotNext()) {
-            session.setFromTime(session.getToTime());
-            session.setToTime(session.getToTime().plusHours(config.getInterval()));
-            session.setOffset(0);
-        }
+            if (session.hasNotNext()) {
+                session.setFromTime(session.getToTime());
+                session.setToTime(session.getToTime().plusHours(config.getInterval()));
+                session.setOffset(0);
+            }
 
-        JsonArray features = geoJson.getAsJsonArray("features");
-        if (features.size() != geoSize) {
-            logger.warn("Retrieved metadata size of features differs to " + geoSize + " from " + features.size());
-            geoSize = features.size();
-        }
+            JsonArray features = geoJson.getAsJsonArray("features");
+            if (features.size() != geoSize) {
+                logger.warn("Retrieved metadata size of features differs to " + geoSize + " from " + features.size());
+                geoSize = features.size();
+            }
 
-        if (geoSize <= 0) {
-            logger.warn("Retrieved zero data within the interval of " + session.getFromTime() + " to " + session.getToTime());
-            return GeoJsonModel.empty();
+            if (geoSize <= 0) {
+                logger.warn("Retrieved zero data within the interval of " + session.getFromTime() + " to " + session.getToTime());
+                return GeoJsonModel.empty();
+            }
+
+            OffsetDateTime locTmFirst = OffsetDateTime.parse(features.get(0).getAsJsonObject().getAsJsonObject("properties").get("timestamp").getAsString());
+            OffsetDateTime locTmLast = OffsetDateTime.parse(features.get(geoSize - 1).getAsJsonObject().getAsJsonObject("properties").get("timestamp").getAsString());
+
+            features.asList().forEach(feature -> feature.getAsJsonObject().get("properties").getAsJsonObject()
+                    .addProperty("orderId", Long.toString(event.orderId))
+            );
+
+            featureCollections.addAll(features);
+            if (locTmFirst.isBefore(globalTimestampFirst)) {
+                globalTimestampFirst = locTmFirst;
+            }
+            if (locTmLast.isAfter(globalTimestampLast)) {
+                globalTimestampLast = locTmLast;
+            }
+
+            if (event.status.equals(EventInfo.EventStatus.FINISHED) && session.hasNotNext()) {
+                multiEventSession.remove(event.id);
+            } else {
+                event.status = EventInfo.EventStatus.FINISHED;
+            }
         }
 
-        OffsetDateTime fTimestampFirst = OffsetDateTime.parse(features.get(0).getAsJsonObject().getAsJsonObject("properties").get("timestamp").getAsString());
-        OffsetDateTime fTimestampLast = OffsetDateTime.parse(features.get(geoSize-1).getAsJsonObject().getAsJsonObject("properties").get("timestamp").getAsString());
+        JsonObject geoJson = new JsonObject();
+        geoJson.addProperty("type", "FeatureCollection");
+        geoJson.add("features", featureCollections);
 
-        return new GeoJsonModel(geoJson, fTimestampFirst, fTimestampLast);
+        return new GeoJsonModel(geoJson, globalTimestampFirst, globalTimestampLast);
     }
 }

+ 0 - 25
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetryProxySession.java

@@ -1,25 +0,0 @@
-package cz.senslog.fetch.senslog.telemetry;
-
-import cz.senslog.connector.fetch.api.ConnectorFetcher;
-import cz.senslog.connector.fetch.api.FetchProxySession;
-import cz.senslog.connector.model.api.GeoJsonModel;
-import cz.senslog.connector.model.api.ProxySessionModel;
-
-import java.util.Optional;
-
-public class SensLogTelemetryProxySession extends FetchProxySession<SensLogTelemetrySession, GeoJsonModel> {
-
-    protected SensLogTelemetryProxySession(ConnectorFetcher<SensLogTelemetrySession, GeoJsonModel> instance) {
-        super(instance);
-    }
-
-    @Override
-    protected SensLogTelemetrySession preProcessing(Optional<SensLogTelemetrySession> previousSession) {
-        return previousSession.filter(ProxySessionModel::isActive).orElse(new SensLogTelemetrySession(true));
-    }
-
-    @Override
-    protected void postProcessing(Optional<GeoJsonModel> model, Optional<SensLogTelemetrySession> session) {
-
-    }
-}

+ 11 - 6
connector-fetch-senslog-telemetry/src/main/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetrySession.java

@@ -1,21 +1,26 @@
-package cz.senslog.fetch.senslog.telemetry;
+package cz.senslog.connector.fetch.senslog.telemetry;
 
 import cz.senslog.connector.model.api.ProxySessionModel;
 
 import java.time.OffsetDateTime;
 
-public class SensLogTelemetrySession extends ProxySessionModel {
+public class SensLogTelemetrySession {
 
+    private final EventInfo eventInfo;
     private OffsetDateTime fromTime, toTime;
     private int offset;
     private boolean hasNext;
 
-    public SensLogTelemetrySession(boolean isActive) {
-        super(isActive);
+    public SensLogTelemetrySession(EventInfo eventInfo, OffsetDateTime fromTime, int interval) {
+        this.eventInfo = eventInfo;
         this.offset = 0;
         this.hasNext = false;
-        this.fromTime = null;
-        this.toTime = null;
+        this.fromTime = fromTime;
+        this.toTime = fromTime.plusHours(interval);
+    }
+
+    public EventInfo getEventInfo() {
+        return eventInfo;
     }
 
     public int getOffset() {

+ 1 - 1
connector-fetch-senslog-telemetry/src/main/resources/META-INF/services/cz.senslog.connector.fetch.api.ConnectorFetchProvider

@@ -1 +1 @@
-cz.senslog.fetch.senslog.telemetry.ConnectorFetchSenslogTelemetryProvider
+cz.senslog.connector.fetch.senslog.telemetry.ConnectorFetchSenslogTelemetryProvider

+ 20 - 10
connector-fetch-senslog-telemetry/src/test/java/cz/senslog/connector/fetch/senslog/telemetry/SensLogTelemetryFetcherTest.java

@@ -1,4 +1,4 @@
-package cz.senslog.fetch.senslog.telemetry;
+package cz.senslog.connector.fetch.senslog.telemetry;
 
 import cz.senslog.connector.fetch.api.ExecutableFetcher;
 import cz.senslog.connector.model.api.GeoJsonModel;
@@ -6,28 +6,38 @@ import cz.senslog.connector.model.config.DefaultConfig;
 import cz.senslog.connector.tools.http.HttpClient;
 import org.junit.jupiter.api.Test;
 
+import java.util.Arrays;
 import java.util.Collections;
 
-import static org.junit.jupiter.api.Assertions.*;
-
 class SensLogTelemetryFetcherTest {
 
     @Test
     void onlineTest() throws Exception {
 
         DefaultConfig defaultConfig = new DefaultConfig("", null);
-        defaultConfig.setProperty("baseUrl", "https://theros.wirelessinfo.cz/");
-        defaultConfig.setProperty("startAt", "2024-06-23T20:00:00+00:00");
+//        defaultConfig.setProperty("startAt", "now");
+        defaultConfig.setProperty("startAt", "2024-12-01T10:09:00+00:00"); // 2024-12-01T10:09:34.589Z
         defaultConfig.setProperty("bearerToken", "#123");
+        defaultConfig.setProperty("limit", 100);
+        defaultConfig.setProperty("interval", 1);
+
+//        defaultConfig.setProperty("baseUrl", "http://127.0.0.1:8085");
+//        defaultConfig.setProperty("campaignId", 3);
+//        defaultConfig.setProperty("actionId", 9);
+//        defaultConfig.setProperty("entityId", 3);
+//        defaultConfig.setProperty("allowedStations", Arrays.asList(1000L));
+
+
+        defaultConfig.setProperty("baseUrl", "https://theros.wirelessinfo.cz/");
         defaultConfig.setProperty("campaignId", 4);
-        defaultConfig.setProperty("limit", 200);
-        defaultConfig.setProperty("interval", 12);
-        defaultConfig.setProperty("allowedStations", Collections.singletonList(1305167563014004L));
+        defaultConfig.setProperty("actionId", 1);
+        defaultConfig.setProperty("entityId", 1);
+        defaultConfig.setProperty("allowedStations", Arrays.asList(1305167563014004L, 1305167563048283L));
+
 
         SensLogTelemetryConfig config = new SensLogTelemetryConfig(defaultConfig);
         SensLogTelemetryFetcher fetcher = new SensLogTelemetryFetcher(config, HttpClient.newHttpSSLClient());
-        SensLogTelemetryProxySession proxySession = new SensLogTelemetryProxySession(fetcher);
-        ExecutableFetcher<GeoJsonModel> executor = ExecutableFetcher.createWithProxySession(proxySession);
+        ExecutableFetcher<GeoJsonModel> executor = ExecutableFetcher.create(fetcher);
 
         fetcher.init();
         for (int i = 0; i < 10; i++) {

+ 1 - 0
connector-fetch-senslog-v1/src/main/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetcher.java

@@ -22,6 +22,7 @@ import java.util.stream.Stream;
 
 import static cz.senslog.connector.tools.json.BasicJson.jsonToObject;
 import static java.time.OffsetDateTime.parse;
+import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
 import static java.time.format.DateTimeFormatter.ofPattern;
 
 public class SenslogFetcher implements ConnectorFetcher<SensLogSession, SenslogV1Model> {

+ 4 - 3
connector-fetch-senslog-v1/src/test/java/cz/senslog/connector/fetch/senslog/v1/SenslogFetcherTest.java

@@ -26,7 +26,8 @@ class SenslogFetcherTest {
         defaultConfig.setProperty("timeZone", "Europe/Prague");
         defaultConfig.setProperty("baseUrl", "http://sensor.lesprojekt.cz/senslog15");
         defaultConfig.setProperty("user", "osek");
-        defaultConfig.setProperty("interval", 20);
+        defaultConfig.setProperty("delayStartDate", 30);
+        defaultConfig.setProperty("interval", 10);
 
         Map<String, String> auth = new HashMap<>();
         auth.put("username", "osek");
@@ -48,8 +49,8 @@ class SenslogFetcherTest {
         AnalyticsModel a1 = converter.convert(fetcher.fetch(Optional.of(session)));
         System.out.println(BasicJson.objectToJson(a1.getObservations()));
 
-        AnalyticsModel a2 = converter.convert(fetcher.fetch(Optional.of(session)));
-        System.out.println(BasicJson.objectToJson(a2.getObservations()));
+//        AnalyticsModel a2 = converter.convert(fetcher.fetch(Optional.of(session)));
+//        System.out.println(BasicJson.objectToJson(a2.getObservations()));
     }
 
 }

+ 26 - 7
connector-push-telemetry/pom.xml

@@ -2,19 +2,38 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
     <parent>
-        <groupId>cz.senslog</groupId>
         <artifactId>connector-period</artifactId>
+        <groupId>cz.senslog</groupId>
         <version>1.0-SNAPSHOT</version>
     </parent>
+    <modelVersion>4.0.0</modelVersion>
 
     <artifactId>connector-push-telemetry</artifactId>
+    <name>push-telemetry</name>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>cz.senslog</groupId>
+            <artifactId>connector-push-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cz.senslog</groupId>
+            <artifactId>connector-model</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+
+    </dependencies>
 
-    <properties>
-        <maven.compiler.source>17</maven.compiler.source>
-        <maven.compiler.target>17</maven.compiler.target>
-        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    </properties>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
 
 </project>

+ 28 - 0
connector-push-telemetry/src/main/java/cz/senslog/connector/push/telemetry/TelemetryConfig.java

@@ -0,0 +1,28 @@
+package cz.senslog.connector.push.telemetry;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+
+public class TelemetryConfig {
+
+    private final String infoUrl;
+    private final String stopDeliveryUrl;
+    private final String updateLocationsUrl;
+
+    TelemetryConfig(DefaultConfig defaultConfig) {
+        this.infoUrl = defaultConfig.getStringProperty("infoUrl");
+        this.stopDeliveryUrl = defaultConfig.getStringProperty("stopDeliveryUrl");
+        this.updateLocationsUrl = defaultConfig.getStringProperty("updateLocationsUrl");
+    }
+
+    public String getInfoUrl() {
+        return infoUrl;
+    }
+
+    public String getStopDeliveryUrl() {
+        return stopDeliveryUrl;
+    }
+
+    public String getUpdateLocationsUrl() {
+        return updateLocationsUrl;
+    }
+}

+ 31 - 0
connector-push-telemetry/src/main/java/cz/senslog/connector/push/telemetry/TelemetryPushProvider.java

@@ -0,0 +1,31 @@
+package cz.senslog.connector.push.telemetry;
+
+import cz.senslog.connector.model.config.DefaultConfig;
+import cz.senslog.connector.model.telemetry.TelemetryModel;
+import cz.senslog.connector.push.api.ConnectorPushProvider;
+import cz.senslog.connector.push.api.ConnectorPusher;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import static cz.senslog.connector.tools.http.HttpClient.newHttpSSLClient;
+
+public class TelemetryPushProvider implements ConnectorPushProvider {
+
+    private static final Logger logger = LogManager.getLogger(TelemetryPushProvider.class);
+
+    @Override
+    public ConnectorPusher<TelemetryModel> createPusher(DefaultConfig defaultConfig) {
+
+        logger.info("Initialization a new push provider {}.", TelemetryPusher.class);
+
+        logger.debug("Creating a new configuration.");
+        TelemetryConfig config = new TelemetryConfig(defaultConfig);
+        logger.info("Configuration for {} was created successfully.", TelemetryPusher.class);
+
+        logger.debug("Creating a new instance of {}.", TelemetryPusher.class);
+        TelemetryPusher pusher = new TelemetryPusher(config, newHttpSSLClient());
+        logger.info("Pusher for {} was created successfully.", TelemetryPusher.class);
+
+        return pusher;
+    }
+}

+ 54 - 0
connector-push-telemetry/src/main/java/cz/senslog/connector/push/telemetry/TelemetryPusher.java

@@ -0,0 +1,54 @@
+package cz.senslog.connector.push.telemetry;
+
+import cz.senslog.connector.model.telemetry.TelemetryModel;
+import cz.senslog.connector.push.api.ConnectorPusher;
+import cz.senslog.connector.tools.http.HttpClient;
+import cz.senslog.connector.tools.http.HttpRequest;
+import cz.senslog.connector.tools.http.HttpResponse;
+import cz.senslog.connector.tools.http.URLBuilder;
+import cz.senslog.connector.tools.json.BasicJson;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Map;
+
+import static java.lang.String.format;
+
+public class TelemetryPusher implements ConnectorPusher<TelemetryModel> {
+
+    private static final Logger logger = LogManager.getLogger(TelemetryPusher.class);
+
+
+    private final TelemetryConfig config;
+
+    private final HttpClient httpClient;
+
+    public TelemetryPusher(final TelemetryConfig config, final HttpClient httpClient) {
+        this.config = config;
+        this.httpClient = httpClient;
+    }
+
+    @Override
+    public void init() throws Exception {
+        HttpRequest request = HttpRequest.newBuilder().GET()
+                .url(URLBuilder.newBuilder(config.getInfoUrl()).build())
+                .build();
+
+        HttpResponse response = httpClient.send(request);
+
+        if (response.isError()) {
+            throw logger.throwing(new Exception(format(
+                    "Can not get information about the telemetry instance. %s", response.getBody()
+            )));
+        }
+
+        String responseBody = response.getBody();
+        Map<?, ?> infoJson = BasicJson.jsonToObject(responseBody, Map.class);
+        logger.info(format("Successfully connected to: %s", infoJson.get("name")));
+    }
+
+    @Override
+    public void push(TelemetryModel model) {
+        logger.info(format("Pushing: %s", model.getBody()));
+    }
+}

+ 1 - 0
connector-push-telemetry/src/main/resources/META-INF/services/cz.senslog.connector.push.api.ConnectorPushProvider

@@ -0,0 +1 @@
+cz.senslog.connector.push.telemetry.TelemetryPushProvider

+ 2 - 5
connector-push-theros/src/main/java/cz/senslog/connector/push/theros/TherosPusher.java

@@ -48,10 +48,7 @@ public class TherosPusher implements ConnectorPusher<GeoJsonModel> {
             logger.warn("Nothing to push."); return;
         }
 
-        List<JsonElement> geoFeatures = model.getGeoJson().getAsJsonArray("features").asList();
-        geoFeatures.forEach(feature -> feature.getAsJsonObject().get("properties").getAsJsonObject()
-                        .addProperty("lotNumber", "X1234345")
-                );
+        int featuresToPush = model.getGeoJson().getAsJsonArray("features").size();
 
         AuthorizationToken token = authService.getAuthorizationToken();
         HttpRequest request = HttpRequest.newBuilder().POST()
@@ -66,7 +63,7 @@ public class TherosPusher implements ConnectorPusher<GeoJsonModel> {
         if (response.isOk()) {
             JsonObject geoJson = BasicJson.jsonToObject(response.getBody(), JsonObject.class);
             int uploadedFeatures = geoJson.get("features").getAsJsonArray().size();
-            logger.info("Send telemetries {}/{} from {} to {}.", geoFeatures.size(), uploadedFeatures, model.getFrom(), model.getTo());
+            logger.info("Send telemetries {}/{} from {} to {}.", featuresToPush, uploadedFeatures, model.getFrom(), model.getTo());
         } else {
             logger.error("Can not send telemetries from {} to {}.", model.getFrom(), model.getTo());
             logger.error(response.getBody());