diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b055b853f4..180bb28806 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -95,18 +95,18 @@ jobs: sudo apt install -y protobuf-compiler - name: Build - run: mvn -Dgpg.skip=true --batch-mode install + run: mvn -Dgpg.skip=true -Dmaven.javadoc.skip=true --batch-mode install - name: Publish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: mvn -Dgpg.skip=true --batch-mode -DskipTests deploy + run: mvn -Dgpg.skip=true -Dmaven.javadoc.skip=true --batch-mode -DskipTests deploy if: ${{ github.repository == 'ControlSystemStudio/phoebus' && (github.ref == 'refs/heads/master' || github.ref == 'refs/tags/*') }} - name: Coveralls report env: COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }} run: | - mvn install jacoco:report coveralls:report -DrepoToken=$COVERALLS_TOKEN + mvn install -Dmaven.javadoc.skip=true jacoco:report coveralls:report -DrepoToken=$COVERALLS_TOKEN - name: Download Coverity Build Tool env: @@ -120,7 +120,7 @@ jobs: TOKEN: ${{ secrets.COVERITY_TOKEN }} run: | export PATH=`pwd`/cov-analysis-linux64/bin:$PATH - cov-build --dir cov-int mvn -Dgpg.skip=true -DskipTests install + cov-build --dir cov-int mvn -Dgpg.skip=true -Dmaven.javadoc.skip=true -DskipTests install - name: Submit static analysis results env: TOKEN: ${{ secrets.COVERITY_TOKEN }} diff --git a/.github/workflows/nightly_linux.yml b/.github/workflows/nightly_linux.yml index 5207e60e33..224868d743 100644 --- a/.github/workflows/nightly_linux.yml +++ b/.github/workflows/nightly_linux.yml @@ -19,7 +19,7 @@ jobs: java-version: 11 - name: Build - run: mvn -Djavafx.platform=linux -DskipTests -T6 verify + run: mvn -Djavafx.platform=linux -Dmaven.javadoc.skip=true -DskipTests -T6 verify - name: Tag Repo uses: richardsimko/update-tag@v1 @@ -32,18 +32,19 @@ jobs: uses: mknejp/delete-release-assets@v1 with: token: ${{ github.token }} - tag: Commander-nightly-build # This may also be of the form 'refs/tags/staging' + tag: Commander-nightly-build-* # This may also be of the form 'refs/tags/staging' assets: '*nightly-linux-x86_64.zip' fail-if-no-release: false fail-if-no-assets: false - name: Upload binaries to release - uses: svenstaro/upload-release-action@2.2.1 + uses: svenstaro/upload-release-action@2.7.0 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: phoebus-product/target/product-4.6.10-SNAPSHOT.zip asset_name: Commander_${{github.sha}}-SNAPSHOT-nightly-linux-x86_64.zip - tag: Commander-nightly-build + tag: Commander-nightly-build-${{github.sha}} + target_commit: ${{github.sha}} overwrite: true body: "Latest and greatest nightly build. This is NOT a stable version." diff --git a/.github/workflows/nightly_mac.yml b/.github/workflows/nightly_mac.yml index 6ae2231c0c..c8c400d71c 100644 --- a/.github/workflows/nightly_mac.yml +++ b/.github/workflows/nightly_mac.yml @@ -15,24 +15,25 @@ jobs: java-version: 11 - name: Build - run: mvn -Djavafx.platform=mac -DskipTests -T6 verify + run: mvn -Djavafx.platform=mac -Dmaven.javadoc.skip=true -DskipTests -T6 verify - name: Delete old release assets uses: mknejp/delete-release-assets@v1 with: token: ${{ github.token }} - tag: Commander-nightly-build # This may also be of the form 'refs/tags/staging' + tag: Commander-nightly-build-* # This may also be of the form 'refs/tags/staging' assets: '*nightly-mac-x86_64.zip' fail-if-no-release: false fail-if-no-assets: false - name: Upload binaries to release - uses: svenstaro/upload-release-action@2.2.1 + uses: svenstaro/upload-release-action@2.7.0 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: phoebus-product/target/product-4.6.10-SNAPSHOT.zip asset_name: Commander_${{github.sha}}-SNAPSHOT-nightly-mac-x86_64.zip - tag: Commander-nightly-build + tag: Commander-nightly-build-${{github.sha}} + target_commit: ${{github.sha}} overwrite: true body: "Latest and greatest nightly build. This is NOT a stable version." diff --git a/.github/workflows/nightly_windows.yml b/.github/workflows/nightly_windows.yml index d8ed6cb3ec..179d2a39da 100644 --- a/.github/workflows/nightly_windows.yml +++ b/.github/workflows/nightly_windows.yml @@ -15,24 +15,25 @@ jobs: java-version: 11 - name: Build - run: mvn -Djavafx.platform=win -DskipTests -T6 verify + run: mvn -Djavafx.platform=win -Dmaven.javadoc.skip=true -DskipTests -T6 verify - name: Delete old release assets uses: mknejp/delete-release-assets@v1 with: token: ${{ github.token }} - tag: Commander-nightly-build # This may also be of the form 'refs/tags/staging' + tag: Commander-nightly-build-* # This may also be of the form 'refs/tags/staging' assets: '*nightly-windows-x86_64.zip' fail-if-no-release: false fail-if-no-assets: false - name: Upload binaries to release - uses: svenstaro/upload-release-action@2.2.1 + uses: svenstaro/upload-release-action@2.7.0 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: phoebus-product/target/product-4.6.10-SNAPSHOT.zip asset_name: Commander_${{github.sha}}-SNAPSHOT-nightly-windows-x86_64.zip - tag: Commander-nightly-build + tag: Commander-nightly-build-${{github.sha}} + target_commit: ${{github.sha}} overwrite: true body: "Latest and greatest nightly build. This is NOT a stable version." diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..ad4e5d18d2 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +build: + mvn -DskipTests install -T6 diff --git a/app/commander/app-commander-command-options/.classpath b/app/commander/app-commander-command-options/.classpath index 234db15be4..b010aef042 100644 --- a/app/commander/app-commander-command-options/.classpath +++ b/app/commander/app-commander-command-options/.classpath @@ -9,6 +9,7 @@ + @@ -28,5 +29,12 @@ + + + + + + + diff --git a/app/commander/app-commander-command-options/pom.xml b/app/commander/app-commander-command-options/pom.xml index 71cd951a2d..11649e2454 100644 --- a/app/commander/app-commander-command-options/pom.xml +++ b/app/commander/app-commander-command-options/pom.xml @@ -5,7 +5,7 @@ com.windhoverlabs app-commander - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT app-commander-command-options @@ -50,7 +50,7 @@ com.windhoverlabs commander-core - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT junit diff --git a/app/commander/app-commander-connections/.classpath b/app/commander/app-commander-connections/.classpath index 234db15be4..b010aef042 100644 --- a/app/commander/app-commander-connections/.classpath +++ b/app/commander/app-commander-connections/.classpath @@ -9,6 +9,7 @@ + @@ -28,5 +29,12 @@ + + + + + + + diff --git a/app/commander/app-commander-connections/pom.xml b/app/commander/app-commander-connections/pom.xml index 8f5816f564..987a5bc4aa 100644 --- a/app/commander/app-commander-connections/pom.xml +++ b/app/commander/app-commander-connections/pom.xml @@ -3,7 +3,7 @@ com.windhoverlabs app-commander - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT app-commander-connections @@ -48,13 +48,13 @@ com.windhoverlabs commander-core - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT com.windhoverlabs commander-core - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT test-jar test diff --git a/app/commander/app-commander-display-model/.classpath b/app/commander/app-commander-display-model/.classpath index 234db15be4..b010aef042 100644 --- a/app/commander/app-commander-display-model/.classpath +++ b/app/commander/app-commander-display-model/.classpath @@ -9,6 +9,7 @@ + @@ -28,5 +29,12 @@ + + + + + + + diff --git a/app/commander/app-commander-display-model/pom.xml b/app/commander/app-commander-display-model/pom.xml index 4c8a4fb0cf..9c76c3e477 100644 --- a/app/commander/app-commander-display-model/pom.xml +++ b/app/commander/app-commander-display-model/pom.xml @@ -3,7 +3,7 @@ com.windhoverlabs app-commander - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT app-commander-display-model diff --git a/app/commander/app-commander-display-representation-javafx/.classpath b/app/commander/app-commander-display-representation-javafx/.classpath index 234db15be4..b010aef042 100644 --- a/app/commander/app-commander-display-representation-javafx/.classpath +++ b/app/commander/app-commander-display-representation-javafx/.classpath @@ -9,6 +9,7 @@ + @@ -28,5 +29,12 @@ + + + + + + + diff --git a/app/commander/app-commander-display-representation-javafx/pom.xml b/app/commander/app-commander-display-representation-javafx/pom.xml index 1095485227..d511c166c9 100644 --- a/app/commander/app-commander-display-representation-javafx/pom.xml +++ b/app/commander/app-commander-display-representation-javafx/pom.xml @@ -3,13 +3,13 @@ com.windhoverlabs app-commander - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT com.windhoverlabs app-commander-display-model - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT app-commander-display-representation-javafx diff --git a/app/commander/app-commander-display-runtime/.classpath b/app/commander/app-commander-display-runtime/.classpath index 234db15be4..b010aef042 100644 --- a/app/commander/app-commander-display-runtime/.classpath +++ b/app/commander/app-commander-display-runtime/.classpath @@ -9,6 +9,7 @@ + @@ -28,5 +29,12 @@ + + + + + + + diff --git a/app/commander/app-commander-display-runtime/pom.xml b/app/commander/app-commander-display-runtime/pom.xml index ce9e7cfe08..8470877bbd 100644 --- a/app/commander/app-commander-display-runtime/pom.xml +++ b/app/commander/app-commander-display-runtime/pom.xml @@ -3,13 +3,13 @@ com.windhoverlabs app-commander - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT com.windhoverlabs app-commander-display-model - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT app-commander-display-runtime diff --git a/app/commander/app-commander-events/.classpath b/app/commander/app-commander-events/.classpath index 234db15be4..b010aef042 100644 --- a/app/commander/app-commander-events/.classpath +++ b/app/commander/app-commander-events/.classpath @@ -9,6 +9,7 @@ + @@ -28,5 +29,12 @@ + + + + + + + diff --git a/app/commander/app-commander-events/pom.xml b/app/commander/app-commander-events/pom.xml index 66a62a24cb..8814396c0d 100644 --- a/app/commander/app-commander-events/pom.xml +++ b/app/commander/app-commander-events/pom.xml @@ -5,7 +5,7 @@ com.windhoverlabs app-commander - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT app-commander-events @@ -50,7 +50,7 @@ com.windhoverlabs commander-core - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT junit diff --git a/app/commander/app-commander-links/Makefile b/app/commander/app-commander-links/Makefile new file mode 100644 index 0000000000..b1173ee1fa --- /dev/null +++ b/app/commander/app-commander-links/Makefile @@ -0,0 +1,2 @@ +format: + mvn com.coveo:fmt-maven-plugin:format diff --git a/app/commander/app-commander-links/pom.xml b/app/commander/app-commander-links/pom.xml new file mode 100644 index 0000000000..295f646dc5 --- /dev/null +++ b/app/commander/app-commander-links/pom.xml @@ -0,0 +1,92 @@ + + 4.0.0 + + com.windhoverlabs + app-commander + 0.2.6-SNAPSHOT + + app-commander-links + + + + sphinx + + + + org.apache.maven.plugins + maven-site-plugin + 3.7.1 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.0.0 + + + + kr.motd.maven + sphinx-maven-plugin + 2.10.0 + + ${basedir}/docs/source + ${basedir}/docs/build/html + + + + package + + generate + + + + + + + + + + + com.windhoverlabs + commander-core + 0.2.6-SNAPSHOT + + + junit + junit + ${junit.version} + test + + + org.hamcrest + hamcrest-all + 1.3 + test + + + org.testfx + testfx-core + 4.0.13-alpha + test + + + org.testfx + testfx-junit + 4.0.13-alpha + test + + + org.w3c + dom + 2.3.0-jaxb-1.0.6 + + + + org.yamcs + yamcs-api + + 5.7.9 + + + \ No newline at end of file diff --git a/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerApp.java b/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerApp.java new file mode 100644 index 0000000000..46fdc2216f --- /dev/null +++ b/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerApp.java @@ -0,0 +1,56 @@ +package com.windhoverlabs.yamcs.applications.links; + +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.phoebus.framework.preferences.AnnotatedPreferences; +import org.phoebus.framework.preferences.Preference; +import org.phoebus.framework.spi.AppDescriptor; +import org.phoebus.framework.spi.AppInstance; + +@SuppressWarnings("nls") +public class LinksViewerApp implements AppDescriptor { + + public static final String Name = "Links"; + + public static final String DisplayName = Messages.DisplayName; + + public static final Logger log = Logger.getLogger(LinksViewerApp.class.getPackageName()); + + @Preference public static String css_path; + + static { + AnnotatedPreferences.initialize(LinksViewerApp.class, "/eventviewer_preferences.properties"); + } + + @Override + public String getName() { + return Name; + } + + public static String getCSSPath() { + String path = css_path.trim(); + if (path.isEmpty()) { + return LinksViewerInstance.class.getResource("/events_style.css").toExternalForm(); + } else { + return Path.of(path).toUri().toString(); + } + } + + @Override + public AppInstance create() { + + if (LinksViewerInstance.INSTANCE == null) { + try { + LinksViewerInstance.INSTANCE = new LinksViewerInstance(this); + } catch (Exception ex) { + Logger.getLogger(LinksViewerApp.class.getPackageName()) + .log(Level.WARNING, "Cannot create Error Log", ex); + return null; + } + } else { + LinksViewerInstance.INSTANCE.raise(); + } + return LinksViewerInstance.INSTANCE; + } +} diff --git a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExportController.java b/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerController.java similarity index 51% rename from app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExportController.java rename to app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerController.java index 6d98054599..e330e6ed84 100644 --- a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExportController.java +++ b/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerController.java @@ -1,4 +1,4 @@ -package com.windhoverlabs.yamcs.applications.events; +package com.windhoverlabs.yamcs.applications.links; import com.windhoverlabs.pv.yamcs.YamcsAware; import com.windhoverlabs.yamcs.core.CMDR_Event; @@ -7,12 +7,15 @@ import java.text.DecimalFormat; import java.time.Instant; import java.util.ArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Logger; import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.css.SimpleStyleableStringProperty; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.TableCell; @@ -21,15 +24,29 @@ import javafx.scene.control.ToggleButton; import javafx.scene.layout.GridPane; import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; import org.yamcs.protobuf.Event.EventSeverity; -public class ParameterExportController { - public static final Logger log = Logger.getLogger(ParameterExportController.class.getPackageName()); +public class LinksViewerController { + public static final Logger log = Logger.getLogger(LinksViewerController.class.getPackageName()); - private final TableView tableView = new TableView(); + private final TableView tableView = + new TableView(); - TableColumn messageCol = new TableColumn("Message"); + TableColumn inCount = + new TableColumn("In"); + TableColumn annotationCol = new TableColumn(); + TableColumn generationTimeCol = + new TableColumn("Generation Time"); + TableColumn receptionTimeCol = + new TableColumn("Reception Time"); + TableColumn nameCol = + new TableColumn("Name"); TableColumn typeCol = new TableColumn("Type"); + TableColumn sourceCol = new TableColumn("Source"); + TableColumn instanceCol = new TableColumn("Instance"); + + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private ObservableList data = FXCollections.observableArrayList(new ArrayList()); @@ -51,15 +68,29 @@ public Node getRootPane() { @FXML public void initialize() { - tableView.setId("eventsTable"); - messageCol.setCellValueFactory( - (event) -> { - return new SimpleStringProperty(event.getValue().getMessage()); + tableView.setId("LinksTable"); + + // scheduler.scheduleAtFixedRate( + // () -> { + // // this.outOfSync = this.logEventCount != this.streamEventCount; + //// tableView.refresh(); + // }, + // 30, + // 30, + // TimeUnit.SECONDS); + // tableView.getStylesheets().add(LinksViewerApp.getCSSPath()); + nameCol.setCellValueFactory( + (link) -> { + SimpleStyleableStringProperty s = new javafx.css.SimpleStyleableStringProperty(null); + if (link != null && link.getValue() != null) { + s.set(link.getValue().getName()); + } + return s; }); - messageCol.setCellFactory( + nameCol.setCellFactory( column -> { - return new TableCell() { + return new TableCell() { @Override protected void updateItem(String item, boolean empty) { super.updateItem(item, empty); // This is mandatory @@ -69,43 +100,75 @@ protected void updateItem(String item, boolean empty) { setStyle(""); } else { // If the cell is not empty // We get here all the info of the event of this row - CMDR_Event event = getTableView().getItems().get(getIndex()); - switch (event.getSeverity()) { - case CRITICAL: - this.getStyleClass().add("critical"); - break; - case DISTRESS: - this.getStyleClass().add("distress"); - break; - case ERROR: - this.getStyleClass().add("error"); - break; - case INFO: - this.getStyleClass().add("info"); - break; - case SEVERE: - this.getStyleClass().add("severe"); - break; - case WARNING: - this.getStyleClass().add("warning"); - break; - case WATCH: - this.getStyleClass().add("watch"); - break; - default: - setTextFill(Color.BLACK); - break; - } + // CMDR_Event event = getTableView().getItems().get(getIndex()); + // switch (event.getSeverity()) { + // case CRITICAL: + // this.getStyleClass().add("critical"); + // break; + // case DISTRESS: + // this.getStyleClass().add("distress"); + // break; + // case ERROR: + // this.getStyleClass().add("error"); + // break; + // case INFO: + // this.getStyleClass().add("info"); + // break; + // case SEVERE: + // this.getStyleClass().add("severe"); + // break; + // case WARNING: + // this.getStyleClass().add("warning"); + // break; + // case WATCH: + // this.getStyleClass().add("watch"); + // break; + // default: + // setTextFill(Color.BLACK); + // break; + // } setText(item); // Put the String data in the cell + Circle circle = new Circle(); + circle.setCenterX(100.0f); + circle.setCenterY(100.0f); + circle.setRadius(10.0f); + var activeColor = Color.RED; + if (YamcsObjectManager.getDefaultInstance() != null + && YamcsObjectManager.getDefaultInstance().getLinksMap().get(item) != null) { + if (YamcsObjectManager.getDefaultInstance().isLinkActive(item)) { + activeColor = Color.LIGHTGREEN; + } else { + // activeColor = activeColor.darker(); + activeColor = Color.BLUE; + } + } + + circle.setFill(activeColor); + this.setGraphic(circle); } } }; }); - tableView - .getColumns() - .addAll( - messageCol, - typeCol); + inCount.setCellValueFactory( + (link) -> { + if (link != null && link.getValue() != null) { + return new SimpleStringProperty(Long.toString(link.getValue().getDataInCount())); + } else { + return new SimpleStringProperty(""); + } + }); + // tableView + // .getColumns() + // .addAll( + // nameCol, + // generationTimeCol, + // receptionTimeCol, + // severityCol, + // typeCol, + // sourceCol, + // instanceCol); + + tableView.getColumns().addAll(nameCol, inCount); yamcsListener = new YamcsAware() { @@ -124,7 +187,7 @@ public void onChanged(Change c) { }); } }); - tableView.setItems(YamcsObjectManager.getDefaultInstance().getEvents()); + tableView.setItems(YamcsObjectManager.getDefaultInstance().getLinks()); tableView.refresh(); } @@ -144,7 +207,7 @@ public void onChanged(Change c) { }); } }); - tableView.setItems(YamcsObjectManager.getDefaultInstance().getEvents()); + tableView.setItems(YamcsObjectManager.getDefaultInstance().getLinks()); tableView.refresh(); } } @@ -165,10 +228,14 @@ public void onChanged(Change c) { }); } }); - tableView.setItems(YamcsObjectManager.getDefaultInstance().getEvents()); + tableView.setItems(YamcsObjectManager.getDefaultInstance().getLinks()); tableView.refresh(); } } + + // public void updateLink(String link) { + // tableView.refresh(); + // } }; YamcsObjectManager.addYamcsListener(yamcsListener); @@ -181,7 +248,7 @@ public void onChanged(Change c) { gridPane.add(tableView, 0, 1); } - public ParameterExportController() { + public LinksViewerController() { System.out.println("EventViewerController constructor$$$$$$$$$$$$$$"); } diff --git a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExportViewerInstance.java b/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerInstance.java similarity index 77% rename from app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExportViewerInstance.java rename to app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerInstance.java index 727a6b5781..ca53682356 100644 --- a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExportViewerInstance.java +++ b/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerInstance.java @@ -1,4 +1,4 @@ -package com.windhoverlabs.yamcs.applications.events; +package com.windhoverlabs.yamcs.applications.links; import java.io.FileNotFoundException; import java.io.IOException; @@ -20,32 +20,28 @@ /** @author lgomez */ @SuppressWarnings("nls") -public class ParameterExportViewerInstance implements AppInstance { - private static final String YAMCS_EVENTS_MEMENTO_FILENAME = "yamcs_events_memento"; +public class LinksViewerInstance implements AppInstance { /** Logger for all file browser code */ - public static final Logger logger = Logger.getLogger(ParameterExportViewerInstance.class.getPackageName()); + public static final Logger logger = Logger.getLogger(LinksViewerInstance.class.getPackageName()); - /** Memento tags */ - private static final String YAMCS_EVENTS = "yamcs_events", YAMCS_EVENT_MESSAGE = "message"; - - static ParameterExportViewerInstance INSTANCE; + static LinksViewerInstance INSTANCE; private FXMLLoader loader; - private ParameterExportController eventInstanceController = null; + private LinksViewerController eventInstanceController = null; private final AppDescriptor app; private DockItem tab = null; - public ParameterExportViewerInstance(AppDescriptor app) { + public LinksViewerInstance(AppDescriptor app) { this.app = app; Node content = null; ResourceBundle resourceBundle = NLS.getMessages(Messages.class); FXMLLoader loader = new FXMLLoader(); loader.setResources(resourceBundle); - loader.setLocation(this.getClass().getResource("EventView.fxml")); + loader.setLocation(this.getClass().getResource("LinksView.fxml")); try { content = loader.load(); @@ -99,7 +95,7 @@ public void raise() { tab.select(); } - public ParameterExportController getController() { + public LinksViewerController getController() { return eventInstanceController; } diff --git a/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerMenuEntry.java b/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerMenuEntry.java new file mode 100644 index 0000000000..699063dd5b --- /dev/null +++ b/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/LinksViewerMenuEntry.java @@ -0,0 +1,30 @@ +package com.windhoverlabs.yamcs.applications.links; + +import javafx.scene.image.Image; +import org.phoebus.framework.workbench.ApplicationService; +import org.phoebus.ui.javafx.ImageCache; +import org.phoebus.ui.spi.MenuEntry; + +@SuppressWarnings("nls") +public class LinksViewerMenuEntry implements MenuEntry { + @Override + public String getName() { + return LinksViewerApp.Name; + } + + @Override + public String getMenuPath() { + return Messages.MenuPath; + } + + @Override + public Image getIcon() { + return ImageCache.getImage(LinksViewerApp.class, "/icons/filebrowser.png"); + } + + @Override + public Void call() throws Exception { + ApplicationService.createInstance(LinksViewerApp.Name); + return null; + } +} diff --git a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/Messages.java b/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/Messages.java similarity index 98% rename from app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/Messages.java rename to app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/Messages.java index 2f6721422e..6956956b4c 100644 --- a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/Messages.java +++ b/app/commander/app-commander-links/src/main/java/com/windhoverlabs/yamcs/applications/links/Messages.java @@ -5,7 +5,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ -package com.windhoverlabs.yamcs.applications.events; +package com.windhoverlabs.yamcs.applications.links; import org.phoebus.framework.nls.NLS; diff --git a/app/commander/app-commander-links/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor b/app/commander/app-commander-links/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor new file mode 100644 index 0000000000..db03c858da --- /dev/null +++ b/app/commander/app-commander-links/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor @@ -0,0 +1 @@ +com.windhoverlabs.yamcs.applications.links.LinksViewerApp \ No newline at end of file diff --git a/app/commander/app-commander-links/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry b/app/commander/app-commander-links/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry new file mode 100644 index 0000000000..c27b194d41 --- /dev/null +++ b/app/commander/app-commander-links/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry @@ -0,0 +1 @@ +com.windhoverlabs.yamcs.applications.links.LinksViewerMenuEntry \ No newline at end of file diff --git a/app/commander/app-commander-links/src/main/resources/META-INF/services/org.phoebus.ui.spi.ToolbarEntry b/app/commander/app-commander-links/src/main/resources/META-INF/services/org.phoebus.ui.spi.ToolbarEntry new file mode 100644 index 0000000000..c2e8342eb3 --- /dev/null +++ b/app/commander/app-commander-links/src/main/resources/META-INF/services/org.phoebus.ui.spi.ToolbarEntry @@ -0,0 +1,2 @@ +#TODO:Add Toolbar Entries +#org.phoebus.applications.commander.connections.FileBrowserToolbarEntry \ No newline at end of file diff --git a/app/commander/app-commander-parameter-export/src/main/resources/com/windhoverlabs/yamcs/applications/events/EventView.fxml b/app/commander/app-commander-links/src/main/resources/com/windhoverlabs/yamcs/applications/links/LinksView.fxml similarity index 93% rename from app/commander/app-commander-parameter-export/src/main/resources/com/windhoverlabs/yamcs/applications/events/EventView.fxml rename to app/commander/app-commander-links/src/main/resources/com/windhoverlabs/yamcs/applications/links/LinksView.fxml index f2d47c3714..049a56c95e 100644 --- a/app/commander/app-commander-parameter-export/src/main/resources/com/windhoverlabs/yamcs/applications/events/EventView.fxml +++ b/app/commander/app-commander-links/src/main/resources/com/windhoverlabs/yamcs/applications/links/LinksView.fxml @@ -6,7 +6,7 @@ - + diff --git a/app/commander/app-commander-links/src/main/resources/com/windhoverlabs/yamcs/applications/links/messages.properties b/app/commander/app-commander-links/src/main/resources/com/windhoverlabs/yamcs/applications/links/messages.properties new file mode 100644 index 0000000000..49d9f9b898 --- /dev/null +++ b/app/commander/app-commander-links/src/main/resources/com/windhoverlabs/yamcs/applications/links/messages.properties @@ -0,0 +1,47 @@ +BaseDirectorySelTT=Select a File Browser path +BaseDirectoryTT=Enter File Browser path +BrowserRootTitle=Select File Browser Path +ColName=Name +ColSize=Size +ColTime=Time +CopyPathClp=Copy Path to Clipboard +CreateDirectoryErr=Cannot create new folder +CreateDirectoryHdr=Enter name for new folder under +Delete=Delete +DeleteJobName=Delete +DeletePromptHeader=Delete +DeletePromptTitle=Delete +DisplayName=Links +Duplicate=Duplicate +DuplicateAlert1=File +DuplicateAlert2=already exists +DuplicateJobName=Rename +DuplicatePrefix=Copy_of_ +DuplicatePromptHeader=Enter name for duplicated file: +HomeButtonTT=Revert to default directory +LookupJobName=File Lookup +MenuPath=Yamcs +MoveOrCopyAlert=Failed to move or copy\n{0}\nto\n{1} +MoveOrCopyAlertTitle=Move or Copy Error +MoveOrCopyJobName=Move/copy file +NewFolder=New Folder +NewFolderAlert=Folder\n{0}\nalready exists +Open=Open +OpenAlert1=Cannot open\n +OpenAlert2=,\nsee log for details +OpenWith=Open With... +Paste=Paste Files from Clipboard +PropDlgBytes=bytes +PropDlgDate=Date: +PropDlgExecutable=Executable +PropDlgPath=Path: +PropDlgPermissions=Permissions: +PropDlgSize=Size: +PropDlgTitle=File Properties +PropDlgWritable=Writeable +Properties=Properties +Refresh=Refresh +Rename=Rename +RenameHdr=Enter new name: +RenameJobName=Rename +SetBaseDirectory=Set as File Browser Path diff --git a/app/commander/app-commander-links/src/main/resources/eventviewer_preferences.properties b/app/commander/app-commander-links/src/main/resources/eventviewer_preferences.properties new file mode 100644 index 0000000000..71f0987453 --- /dev/null +++ b/app/commander/app-commander-links/src/main/resources/eventviewer_preferences.properties @@ -0,0 +1,8 @@ +# -------------------------------------------- +# Package com.windhoverlabs.commander.applications.events +# -------------------------------------------- + +#You may specify the CSS file used to style events here or inside of a custom settings.ini file. +#If no path is specified, then the file at app-commander-events/src/main/resources/events_style.css +#is used instead. +#css_path=$(phoebus.install)/some_path \ No newline at end of file diff --git a/app/commander/app-commander-links/src/main/resources/icons/activate.png b/app/commander/app-commander-links/src/main/resources/icons/activate.png new file mode 100644 index 0000000000..a2b0e676cf Binary files /dev/null and b/app/commander/app-commander-links/src/main/resources/icons/activate.png differ diff --git a/app/commander/app-commander-links/src/main/resources/icons/add_server_connection.png b/app/commander/app-commander-links/src/main/resources/icons/add_server_connection.png new file mode 100644 index 0000000000..851ea63f2e Binary files /dev/null and b/app/commander/app-commander-links/src/main/resources/icons/add_server_connection.png differ diff --git a/app/commander/app-commander-links/src/main/resources/icons/delete.png b/app/commander/app-commander-links/src/main/resources/icons/delete.png new file mode 100644 index 0000000000..5f00385852 Binary files /dev/null and b/app/commander/app-commander-links/src/main/resources/icons/delete.png differ diff --git a/app/commander/app-commander-links/src/main/resources/icons/fb_home.png b/app/commander/app-commander-links/src/main/resources/icons/fb_home.png new file mode 100644 index 0000000000..9a67fd58e6 Binary files /dev/null and b/app/commander/app-commander-links/src/main/resources/icons/fb_home.png differ diff --git a/app/commander/app-commander-links/src/main/resources/icons/filebrowser.png b/app/commander/app-commander-links/src/main/resources/icons/filebrowser.png new file mode 100644 index 0000000000..8e521645d8 Binary files /dev/null and b/app/commander/app-commander-links/src/main/resources/icons/filebrowser.png differ diff --git a/app/commander/app-commander-links/src/main/resources/icons/filebrowser@2x.png b/app/commander/app-commander-links/src/main/resources/icons/filebrowser@2x.png new file mode 100644 index 0000000000..1d274ab9be Binary files /dev/null and b/app/commander/app-commander-links/src/main/resources/icons/filebrowser@2x.png differ diff --git a/app/commander/app-commander-links/src/main/resources/links_style.css b/app/commander/app-commander-links/src/main/resources/links_style.css new file mode 100644 index 0000000000..fd0ee92c03 --- /dev/null +++ b/app/commander/app-commander-links/src/main/resources/links_style.css @@ -0,0 +1,28 @@ +.info { + -fx-text-fill: black; +} + +.warning { + -fx-text-fill: red; +} + +.error { + -fx-text-fill: red; +} + +.watch { + -fx-text-fill: black; +} + +.distress { + -fx-text-fill: black; +} + +.critical { + -fx-text-fill: black; +} + +.severe { + -fx-text-fill: black; +} + diff --git a/app/commander/app-commander-mission-planner/.classpath b/app/commander/app-commander-mission-planner/.classpath index 234db15be4..b010aef042 100644 --- a/app/commander/app-commander-mission-planner/.classpath +++ b/app/commander/app-commander-mission-planner/.classpath @@ -9,6 +9,7 @@ + @@ -28,5 +29,12 @@ + + + + + + + diff --git a/app/commander/app-commander-mission-planner/pom.xml b/app/commander/app-commander-mission-planner/pom.xml index a0239fd439..4f64d5e9c6 100644 --- a/app/commander/app-commander-mission-planner/pom.xml +++ b/app/commander/app-commander-mission-planner/pom.xml @@ -3,7 +3,7 @@ com.windhoverlabs app-commander - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT app-commander-mission-planner diff --git a/app/commander/app-commander-parameter-export/Makefile b/app/commander/app-commander-parameter-export/Makefile new file mode 100644 index 0000000000..b1173ee1fa --- /dev/null +++ b/app/commander/app-commander-parameter-export/Makefile @@ -0,0 +1,2 @@ +format: + mvn com.coveo:fmt-maven-plugin:format diff --git a/app/commander/app-commander-parameter-export/pom.xml b/app/commander/app-commander-parameter-export/pom.xml index 460bfecb91..c25d89bdb0 100644 --- a/app/commander/app-commander-parameter-export/pom.xml +++ b/app/commander/app-commander-parameter-export/pom.xml @@ -5,7 +5,7 @@ com.windhoverlabs app-commander - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT app-commander-parameter-export @@ -47,10 +47,17 @@ + + + org.apache.commons + commons-csv + 1.10.0 + + com.windhoverlabs commander-core - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT junit diff --git a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ExportCSVJob.java b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ExportCSVJob.java new file mode 100644 index 0000000000..bd57af93f7 --- /dev/null +++ b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ExportCSVJob.java @@ -0,0 +1,427 @@ +/******************************************************************************* + * Copyright (c) 2010-2018 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ +package com.windhoverlabs.yamcs.applications.parameter; + +import com.windhoverlabs.yamcs.core.YamcsObjectManager; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.csstudio.trends.databrowser3.Activator; +import org.phoebus.archive.reader.ArchiveReader; +import org.phoebus.framework.jobs.JobMonitor; +import org.phoebus.framework.jobs.JobRunnable; +import org.yamcs.client.Helpers; +import org.yamcs.client.Page; +import org.yamcs.protobuf.Pvalue.ParameterValue; + +/** + * Base for Eclipse Job for exporting data from Model to file + * + * @author Kay Kasemir + */ +@SuppressWarnings("nls") +public class ExportCSVJob implements JobRunnable { + + class CountedParameterValue { + private ParameterValue pv; + int count; + + public CountedParameterValue(ParameterValue pv, int count) { + this.pv = pv; + this.count = count; + } + } + + public static final int PROGRESS_UPDATE_LINES = 1000; + public Instant start, end; + public String filename; + public Consumer error_handler; + public ArrayList parameters = new ArrayList(); + + public AtomicInteger jobBarrier = new AtomicInteger(0); + + public HashMap> timeStampToParameters = + new HashMap>(); + /** Active readers, used to cancel and close them */ + private final CopyOnWriteArrayList archive_readers = + new CopyOnWriteArrayList(); + + public final boolean unixTimeStamp; + private CancellationPoll cancel_poll; + + /** + * Thread that polls a progress monitor and cancels active archive readers if the user requests + * the export job to end via the progress monitor + */ + class CancellationPoll implements Runnable { + + private final JobMonitor monitor; + volatile boolean exit = false; + + public CancellationPoll(final JobMonitor monitor) { + this.monitor = monitor; + } + + @Override + public void run() { + while (!exit) { + if (monitor.isCanceled()) { + for (ArchiveReader reader : archive_readers) reader.cancel(); + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Ignore + } + } + } + } + + /** + * @param comment Comment prefix ('#' for most ASCII, '%' for Matlab, ...) + * @param model Model with data + * @param start Start time + * @param end End time + * @param source Where to get samples + * @param optimize_parameter Used by optimized source + * @param filename Name of file to create or null if performExport + * handles the file + * @param error_handler Callback for errors + * @param unixTimeStamp If true, time stamps are UNIX style, i.e. ms since EPOCH. + * Defaults to false. + */ + public ExportCSVJob( + final Instant start, + final Instant end, + final String filename, + final Consumer error_handler, + final boolean unixTimeStamp, + ArrayList parameters) { + this.start = start; + this.end = end; + this.filename = filename; + this.error_handler = error_handler; + this.unixTimeStamp = unixTimeStamp; + this.parameters = parameters; + } + + /** Job's main routine {@inheritDoc} */ + @Override + public final void run(final JobMonitor monitor) { + monitor.beginTask("Data Export", 100); + + try { + BufferedWriter writer; + if (filename != null) { + writer = Files.newBufferedWriter(Paths.get(filename)); + // printExportzInfo(out); + } else writer = null; + // Start thread that checks monitor to cancels readers when + // user tries to abort the export job + cancel_poll = new CancellationPoll(monitor); + final Future done = Activator.thread_pool.submit(cancel_poll); + performExport(monitor, writer); + // ask thread to exit + // cancel_poll.exit = true; + // if (writer != null) writer.close(); + // Wait for poller to quit + done.get(); + } catch (final Exception ex) { + error_handler.accept(ex); + } + } + + /** + * Perform the data export + * + * @param out PrintStream for output + * @throws Exception on error + */ + private void performExport(final JobMonitor monitor, BufferedWriter writer) { + monitor.worked(0); + YamcsObjectManager.getDefaultInstance() + .getParameters( + YamcsObjectManager.getDefaultServer().getYamcsClient(), + this.parameters, + start, + end, + (pages) -> { + handlePages(pages); + writeToCSV(writer); + monitor.worked(100); + cancel_poll.exit = true; + }); + monitor.worked(10); + } + + private void writeToCSV(BufferedWriter writer) { + CSVPrinter csvPrinter = null; + ArrayList columnHeaders = new ArrayList(); + try { + columnHeaders.add("Time"); + columnHeaders.add("RelativeTime_MS"); + + for (String p : this.parameters) { + var nameParts = p.split("/"); + var name = nameParts[nameParts.length - 1]; + columnHeaders.add(name); + columnHeaders.add(name + "_Count"); + } + + csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT); + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + cancel_poll.exit = true; + return; + } + try { + csvPrinter.printRecord(columnHeaders); + + List sortedTimeStamps = new ArrayList(timeStampToParameters.keySet()); + Collections.sort(sortedTimeStamps); + + long deltaCount = 0; + Instant timeZero = sortedTimeStamps.get(0); + ArrayList recordZero = new ArrayList(); + recordZero.add(timeZero.toString()); + recordZero.add(Long.toString(deltaCount)); + HashMap> zeroParamToCountMap = + new HashMap>(); + for (var p : this.parameters) { + var nameParts = p.split("/"); + var name = nameParts[nameParts.length - 1]; + zeroParamToCountMap + .computeIfAbsent(timeZero, (instant) -> new HashMap()) + .put(name, 0); + } + resolvePvsForRecord(timeZero, recordZero, zeroParamToCountMap, null); + + csvPrinter.printRecord(recordZero); + + HashMap> paramToCountMap = + new HashMap>(); + + HashMap> paramToLatestValMap = + new HashMap>(); + paramToCountMap.put(timeZero, zeroParamToCountMap.entrySet().iterator().next().getValue()); + var latestValueForParam = new HashMap(); + for (int i = 1; i < sortedTimeStamps.size(); i++) { + var currentCountMap = + paramToCountMap.computeIfAbsent( + sortedTimeStamps.get(i), (item) -> new HashMap()); + var currentLatestValMap = + paramToLatestValMap.computeIfAbsent( + sortedTimeStamps.get(i), (item) -> new HashMap()); + for (var p : this.parameters) { + var nameParts = p.split("/"); + var name = nameParts[nameParts.length - 1]; + currentCountMap.put(name, 0); + var currentParam = timeStampToParameters.get(sortedTimeStamps.get(i)).get(name); + if (currentParam.pv != null) { + latestValueForParam.put(name, currentParam.pv); + int prevCount = paramToCountMap.get(sortedTimeStamps.get(i - 1)).get(name); + currentCountMap.put(name, prevCount + 1); + } else { + int prevCount = paramToCountMap.get(sortedTimeStamps.get(i - 1)).get(name); + currentCountMap.put(name, prevCount); + } + + if (latestValueForParam.containsKey(name)) + ; + { + currentLatestValMap.put(name, latestValueForParam.get(name)); + } + } + } + + for (int i = 1; i < sortedTimeStamps.size(); i++) { + ArrayList record = new ArrayList(); + + Duration zeroDelta = Duration.between(timeZero, sortedTimeStamps.get(i)); + deltaCount = zeroDelta.toMillis(); + record.add(sortedTimeStamps.get(i).toString()); + record.add(Long.toString(deltaCount)); + resolvePvsForRecord(sortedTimeStamps.get(i), record, paramToCountMap, paramToLatestValMap); + csvPrinter.printRecord(record); + } + + } catch (IOException e) { + e.printStackTrace(); + } + + try { + csvPrinter.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Blocks until we are done processing all pages from YAMCS + * + * @param pages + */ + private void handlePages(ArrayList> pages) { + pages.parallelStream() + .forEach( + page -> { + page.iterator() + .forEachRemaining( + pv -> { + constructTimeToParamsMap(pv); + }); + while (page.hasNextPage()) { + try { + try { + page = page.getNextPage().get(1, TimeUnit.MINUTES); + } catch (TimeoutException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } catch (InterruptedException | ExecutionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + page.iterator() + .forEachRemaining( + pv -> { + constructTimeToParamsMap(pv); + }); + } + }); + + while (jobBarrier.get() < pages.size()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + private void resolvePvsForRecord( + Instant currentTimeStamp, + ArrayList record, + HashMap> currentCountMap, + HashMap> latestValueMap) { + for (var p : this.parameters) { + var nameParts = p.split("/"); + var name = nameParts[nameParts.length - 1]; + CountedParameterValue currentCountedP = timeStampToParameters.get(currentTimeStamp).get(name); + if (currentCountedP.pv != null) { + resolvePV(record, currentCountedP); + record.add(Integer.toString(currentCountMap.get(currentTimeStamp).get(name))); + } else { + if (latestValueMap == null || latestValueMap.get(currentTimeStamp).get(name) == null) { + record.add("N/A"); + } else { + CountedParameterValue latestCountedP = + new CountedParameterValue(latestValueMap.get(currentTimeStamp).get(name), 0); + resolvePV(record, latestCountedP); + } + record.add(Integer.toString(currentCountMap.get(currentTimeStamp).get(name))); + } + } + } + + private void resolvePV(ArrayList record, CountedParameterValue countedP) { + switch (countedP.pv.getEngValue().getType()) { + case AGGREGATE: + record.add(countedP.pv.getEngValue().getType().toString()); + break; + case ARRAY: + record.add(countedP.pv.getEngValue().getType().toString()); + break; + case BINARY: + record.add(countedP.pv.getEngValue().getType().toString()); + break; + case BOOLEAN: + record.add(Boolean.toString(countedP.pv.getEngValue().getBooleanValue())); + break; + case DOUBLE: + record.add(Double.toString(countedP.pv.getEngValue().getDoubleValue())); + break; + case ENUMERATED: + record.add(countedP.pv.getEngValue().getType().toString()); + break; + case FLOAT: + record.add(Float.toString(countedP.pv.getEngValue().getFloatValue())); + break; + case NONE: + record.add(countedP.pv.getEngValue().getType().toString()); + break; + case SINT32: + record.add(Integer.toString(countedP.pv.getEngValue().getSint32Value())); + break; + case SINT64: + record.add(Long.toString(countedP.pv.getEngValue().getSint64Value())); + break; + case STRING: + record.add(countedP.pv.getEngValue().getStringValue()); + break; + case TIMESTAMP: + record.add(countedP.pv.getEngValue().getType().toString()); + break; + case UINT32: + record.add(Integer.toString(countedP.pv.getEngValue().getUint32Value())); + break; + case UINT64: + record.add(Long.toString(countedP.pv.getEngValue().getUint64Value())); + break; + default: + record.add("Something_ELSE"); + break; + } + } + + private synchronized void constructTimeToParamsMap(ParameterValue pv) { + Instant pvGenerationTime = Helpers.toInstant(pv.getGenerationTime()); + + timeStampToParameters.computeIfAbsent( + pvGenerationTime, + p -> { + return new HashMap(); + }); + String pvNameKey = pv.getId().getName(); + + for (String parameterName : this.parameters) { + var nameParts = parameterName.split("/"); + var countedParams = + timeStampToParameters + .get(pvGenerationTime) + .computeIfAbsent( + nameParts[nameParts.length - 1], + p -> { + return new CountedParameterValue(null, 0); + }); + } + timeStampToParameters.get(pvGenerationTime).put(pvNameKey, new CountedParameterValue(pv, 0)); + + jobBarrier.getAndAdd(1); + } +} diff --git a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ExportView.java b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ExportView.java new file mode 100644 index 0000000000..8b504d6181 --- /dev/null +++ b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ExportView.java @@ -0,0 +1,533 @@ +/******************************************************************************* + * Copyright (c) 2010-2018 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ +package com.windhoverlabs.yamcs.applications.parameter; + +import java.io.File; +import java.text.MessageFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.function.BiConsumer; +import javafx.application.Platform; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.css.PseudoClass; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; +import javafx.scene.control.TextField; +import javafx.scene.control.TitledPane; +import javafx.scene.control.ToggleGroup; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import org.csstudio.trends.databrowser3.Activator; +import org.csstudio.trends.databrowser3.Messages; +import org.csstudio.trends.databrowser3.export.Source; +import org.csstudio.trends.databrowser3.model.Model; +import org.csstudio.trends.databrowser3.ui.TimeRangePopover; +import org.phoebus.archive.vtype.Style; +import org.phoebus.framework.jobs.JobManager; +import org.phoebus.framework.persistence.Memento; +import org.phoebus.ui.dialog.DialogHelper; +import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog; +import org.phoebus.ui.dialog.PopOver; +import org.phoebus.ui.dialog.SaveAsDialog; +import org.phoebus.ui.time.TimeRelativeIntervalPane; +import org.phoebus.util.time.TimeRelativeInterval; + +/** + * Panel for exporting data into files + * + * @author Kay Kasemir + */ +@SuppressWarnings("nls") +public class ExportView extends VBox { + private static final String TAG_SOURCE = "source", + TAG_OPTCOUNT = "optcount", + TAG_LININT = "linint", + TAG_TYPE = "type", + TAG_FORMAT = "format", + TAG_DIGITS = "digits", + TAG_FILE = "file"; + + private final TextField start = new TextField(); + + private final String utcRegex = "d{4}-d{2}-d{2}Td{2}:d{2}:d{2}.d{3}+d{2}:d{2}"; + + public String getStart() { + return start.getText(); + } + + private final TextField end = new TextField(); + + public String getEnd() { + return end.getText(); + } + + void setEnd(String time) { + end.setText(time); + } + + void setStart(String time) { + start.setText(time); + } + + private final ToggleGroup sources = new ToggleGroup(), + table_types = new ToggleGroup(), + formats = new ToggleGroup(); + private final TextField optimize = new TextField(Messages.ExportDefaultOptimization), + linear = new TextField(Messages.ExportDefaultLinearInterpolation), + format_digits = new TextField(Messages.ExportDefaultDigits), + filename = new TextField(); + private final RadioButton source_raw = new RadioButton(Source.RAW_ARCHIVE.toString()), + type_matlab = new RadioButton(Messages.ExportTypeMatlab); + + private final CheckBox useUnixTimeStamp = new CheckBox(Messages.UseUnixTimeStamp); + private SimpleBooleanProperty unixTimeStamp = new SimpleBooleanProperty(false); + + private Model model = new org.csstudio.trends.databrowser3.model.Model(); + + // UnaryOperator numberValidationFormatter = change -> { + // if(change.getText().matches("\\d+")){ + // return change; //if change is a number + // } else { + // change.setText(""); //else make no change + // change.setRange( //don't remove any selected text either. + // change.getRangeStart(), + // change.getRangeStart() + // ); + // return change; + // } + // }; + + private ArrayList parameters = new ArrayList(); + + public ArrayList getParameters() { + return parameters; + } + + public void setParameters(ArrayList parameters) { + this.parameters = parameters; + } + + /** @param model Model from which to export */ + public ExportView() { + // * Samples To Export * + // Start: ___start_______________________________________________________________ [select] + // End : ___end_________________________________________________________________ [x] Use + // start/end time of Plot + // Source: ( ) Plot (*) Raw Archived Data ( ) Averaged Archived Data __time__ ( ) Linear + // __linear__ + + configureValidators(); + GridPane grid = new GridPane(); + grid.setHgap(5); + grid.setVgap(5); + grid.setPadding(new Insets(5)); + + grid.add(new Label(Messages.StartTimeLbl), 0, 0); + start.setPromptText("2023-08-20T04:30:44.424Z"); + // start.va + // \\d{4}-[0-1]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\+\\d{4} + GridPane.setHgrow(start, Priority.ALWAYS); + grid.add(start, 1, 0); + + final Button sel_times = new Button(Messages.StartEndDialogBtn); + sel_times.setTooltip(new Tooltip(Messages.StartEndDialogTT)); + grid.add(sel_times, 2, 0); + + grid.add(new Label(Messages.EndTimeLbl), 0, 1); + // end.setTooltip(new Tooltip(Messages.EndTimeTT)); + end.setPromptText("2023-08-20T04:35:44.424Z"); + GridPane.setHgrow(end, Priority.ALWAYS); + grid.add(end, 1, 1); + + BiConsumer closeCallback = + (timePane, popOver) -> { + popOver.hide(); + }; + BiConsumer applyCallback = + (timePane, popOver) -> { + final String[] range = Model.getTimerangeText(timePane.getInterval()); + start.setText(range[0]); + end.setText(range[1]); + popOver.hide(); + }; + final TimeRangePopover popover = + TimeRangePopover.withDefaultTimePane(model, closeCallback, applyCallback); + sel_times.setOnAction( + event -> { + popover.show((Region) event.getSource()); + }); + + grid.add(new Label(Messages.ExportGroupSource), 0, 2); + + // Order of source_* radio buttons must match the corresponding Source.* ordinal + final RadioButton source_plot = new RadioButton(Source.PLOT.toString()); + source_plot.setTooltip(new Tooltip(Messages.ExportSource_PlotTT)); + source_plot.setToggleGroup(sources); + + source_raw.setTooltip(new Tooltip(Messages.ExportSource_RawArchiveTT)); + source_raw.setToggleGroup(sources); + + final RadioButton source_opt = new RadioButton(Source.OPTIMIZED_ARCHIVE.toString()); + source_opt.setTooltip(new Tooltip(Messages.ExportSource_OptimizedArchiveTT)); + source_opt.setToggleGroup(sources); + + optimize.setPrefColumnCount(6); + optimize.setTooltip(new Tooltip(Messages.ExportOptimizationTT)); + optimize.disableProperty().bind(source_opt.selectedProperty().not()); + + final RadioButton source_lin = new RadioButton(Source.LINEAR_INTERPOLATION.toString()); + source_lin.setTooltip(new Tooltip(Messages.ExportSource_LinearTT)); + source_lin.setToggleGroup(sources); + + linear.setPrefColumnCount(8); + linear.setTooltip(new Tooltip(Messages.ExportDefaultLinearInterpolationTT)); + linear.disableProperty().bind(source_lin.selectedProperty().not()); + + final HBox source_options = + new HBox( + 5, source_plot, source_raw, source_opt, optimize, source_lin, linear, useUnixTimeStamp); + source_options.setAlignment(Pos.CENTER_LEFT); + grid.add(source_options, 1, 2, 2, 1); + + source_raw.setSelected(true); + source_options.setDisable(true); + + final TitledPane source = new TitledPane(Messages.ExportGroupSource, grid); + source.setCollapsible(false); + + // * Format * + // (*) Spreadsheet ( ) Matlab + // [x] Tabular [x] ... with min/max column [x] ... with Severity/Status + // (*) Default format ( ) decimal notation ( ) exponential notation _digits_ fractional digits + grid = new GridPane(); + grid.setHgap(5); + grid.setVgap(5); + grid.setPadding(new Insets(5)); + + final RadioButton type_spreadsheet = new RadioButton(Messages.ExportTypeSpreadsheet); + type_spreadsheet.setTooltip(new Tooltip(Messages.ExportTypeSpreadsheetTT)); + type_spreadsheet.setToggleGroup(table_types); + grid.add(type_spreadsheet, 0, 0); + + type_matlab.setTooltip(new Tooltip(Messages.ExportTypeMatlabTT)); + type_matlab.setToggleGroup(table_types); + grid.add(type_matlab, 1, 0); + + type_spreadsheet.setSelected(true); + + final RadioButton format_default = new RadioButton(Messages.Format_Default); + format_default.setTooltip(new Tooltip(Messages.ExportFormat_DefaultTT)); + format_default.setToggleGroup(formats); + grid.add(format_default, 0, 2); + + final RadioButton format_decimal = new RadioButton(Messages.Format_Decimal); + format_decimal.setTooltip(new Tooltip(Messages.ExportFormat_DecimalTT)); + format_decimal.setToggleGroup(formats); + grid.add(format_decimal, 1, 2); + + final RadioButton format_expo = new RadioButton(Messages.Format_Exponential); + format_expo.setTooltip(new Tooltip(Messages.ExportFormat_ExponentialTT)); + format_expo.setToggleGroup(formats); + grid.add(format_expo, 2, 2); + + format_digits.setPrefColumnCount(3); + format_digits.setTooltip(new Tooltip(Messages.ExportDigitsTT)); + format_digits.disableProperty().bind(format_default.selectedProperty()); + grid.add(format_digits, 3, 2); + + // Formatting only applies to spreadsheet + format_default.disableProperty().bind(type_matlab.selectedProperty()); + format_decimal.disableProperty().bind(type_matlab.selectedProperty()); + format_expo.disableProperty().bind(type_matlab.selectedProperty()); + format_digits.disableProperty().bind(type_matlab.selectedProperty()); + + grid.add(new Label(Messages.ExportDigits), 4, 2); + + format_default.setSelected(true); + + final TitledPane format = new TitledPane(Messages.ExportGroupFormat, grid); + format.setCollapsible(false); + format.setDisable(true); + + // * Output * + // Filename: ______________ [Browse] [Export] + filename.setPromptText(Messages.ExportDefaultFilename); + filename.setTooltip(new Tooltip(Messages.ExportFilenameTT)); + + final Button sel_filename = new Button(Messages.ExportBrowse); + sel_filename.setTooltip(new Tooltip(Messages.ExportBrowseTT)); + sel_filename.setOnAction(event -> selectFilename()); + + final Button export = new Button(Messages.ExportStartExport, Activator.getIcon("export")); + export.setOnAction(event -> startExportJob()); + + final HBox outputs = + new HBox(5, new Label(Messages.ExportFilename), filename, sel_filename, export); + outputs.setAlignment(Pos.CENTER_LEFT); + HBox.setHgrow(filename, Priority.ALWAYS); + + final TitledPane output = new TitledPane(Messages.ExportGroupOutput, outputs); + output.setCollapsible(false); + + getChildren().setAll(source, format, output); + + // Enter in filename suggests to next start export + filename.setOnAction(event -> export.requestFocus()); + + useUnixTimeStamp.selectedProperty().bindBidirectional(unixTimeStamp); + } + + void configureValidators() { + start + .textProperty() + .addListener( + event -> { + System.out.println("Changed:" + !start.getText().matches(utcRegex)); + ; + start.pseudoClassStateChanged( + PseudoClass.getPseudoClass("error"), + !start.getText().isEmpty() && !start.getText().matches(utcRegex)); + }); + } + + /** @return true if the min/max (error) column option should be enabled */ + private boolean minMaxAllowed() { + return !type_matlab.isSelected() && !source_raw.isSelected(); + } + + private void selectFilename() { + File file = new File(filename.getText().trim()); + file = new SaveAsDialog().promptForFile(getScene().getWindow(), Messages.Export, file, null); + if (file != null) filename.setText(file.getAbsolutePath()); + } + + private void startExportJob() { + try { + // Determine start/end time + TimeRelativeInterval range = null; + // final Object s = TimeParser.parseInstantOrTemporalAmount(start.getText()); + // final Object e = TimeParser.parseInstantOrTemporalAmount(end.getText()); + // if (s instanceof Instant) { + // if (e instanceof Instant) range = TimeRelativeInterval.of((Instant) s, (Instant) e); + // else if (e instanceof TemporalAmount) + // range = TimeRelativeInterval.of((Instant) s, (TemporalAmount) e); + // } else if (s instanceof TemporalAmount) { + // if (e instanceof Instant) range = TimeRelativeInterval.of((TemporalAmount) s, + // (Instant) e); + // else if (e instanceof TemporalAmount) + // range = TimeRelativeInterval.of((TemporalAmount) s, (TemporalAmount) e); + // } + // + // if (range == null) throw new Exception("Invalid start..end time range"); + // + // // Determine source: Plot, archive, ... + // final int src_index = sources.getToggles().indexOf(sources.getSelectedToggle()); + // if (src_index < 0 || src_index >= Source.values().length) + // throw new Exception("Invalid sample source"); + // final Source source = Source.values()[src_index]; + // int optimize_parameter = -1; + // if (source == Source.OPTIMIZED_ARCHIVE) { + // try { + // optimize_parameter = Integer.parseInt(optimize.getText().trim()); + // } catch (Exception ex) { + // ExceptionDetailsErrorDialog.openError( + // optimize, + // Messages.Error, + // Messages.ExportOptimizeCountError, + // new Exception(optimize.getText())); + // Platform.runLater(optimize::requestFocus); + // return; + // } + // } else if (source == Source.LINEAR_INTERPOLATION) { + // try { + // optimize_parameter = (int) (SecondsParser.parseSeconds(linear.getText().trim()) + + // 0.5); + // if (optimize_parameter < 1) optimize_parameter = 1; + // } catch (Exception ex) { + // ExceptionDetailsErrorDialog.openError( + // linear, + // Messages.Error, + // Messages.ExportLinearIntervalError, + // new Exception(linear.getText())); + // Platform.runLater(linear::requestFocus); + // return; + // } + // } + + // Get remaining export parameters + final String filename = this.filename.getText().trim(); + if (filename.isEmpty()) { + ExceptionDetailsErrorDialog.openError( + this.filename, + Messages.Error, + Messages.ExportEnterFilenameError, + new Exception(filename)); + Platform.runLater(this.filename::requestFocus); + return; + } + if (new File(filename).exists()) { + final Alert dialog = new Alert(AlertType.CONFIRMATION); + dialog.setTitle(Messages.ExportFileExists); + dialog.setHeaderText(MessageFormat.format(Messages.ExportFileExistsFmt, filename)); + DialogHelper.positionDialog(dialog, this.filename, -200, -200); + if (dialog.showAndWait().orElse(ButtonType.CANCEL) != ButtonType.OK) { + Platform.runLater(this.filename::requestFocus); + return; + } + } + + // Construct appropriate export job + ExportCSVJob export; + // TimeInterval start_end = range.toAbsoluteInterval(); + // if (type_matlab.isSelected()) { // Matlab file export + // if (filename.endsWith(".m")) + // export = + // new MatlabScriptExportJob( + // model, + // start_end.getStart(), + // start_end.getEnd(), + // source, + // optimize_parameter, + // filename, + // this::handleError, + // unixTimeStamp.get()); + // else if (filename.endsWith(".mat")) + // export = + // new MatlabFileExportJob( + // model, + // start_end.getStart(), + // start_end.getEnd(), + // source, + // optimize_parameter, + // filename, + // this::handleError, + // unixTimeStamp.get()); + // else { + // ExceptionDetailsErrorDialog.openError( + // this.filename, + // Messages.Error, + // Messages.ExportMatlabFilenameError, + // new Exception(filename)); + // Platform.runLater(this.filename::requestFocus); + // return; + // } + // } else + { // Spreadsheet file export + Style style = Style.Default; + + // Default, decimal, exponential + final int fmt = formats.getToggles().indexOf(formats.getSelectedToggle()); + if (fmt == 1) style = Style.Decimal; + else if (fmt == 2) style = Style.Exponential; + int precision = 0; + if (style != Style.Default) { + try { + precision = Integer.parseInt(format_digits.getText().trim()); + } catch (Exception ex) { + ExceptionDetailsErrorDialog.openError( + format_digits, + Messages.Error, + Messages.ExportDigitsError, + new Exception(format_digits.getText())); + Platform.runLater(format_digits::requestFocus); + return; + } + } + // ValueFormatter formatter = new ValueWithInfoFormatter(style, + // precision); + + export = + new ExportCSVJob( + Instant.parse(start.getText()), + Instant.parse(end.getText()), + filename, + this::handleError, + unixTimeStamp.get(), + parameters); + + // final ValueFormatter formatter; + // if (sev_stat.isSelected()) formatter = new ValueWithInfoFormatter(style, + // precision); + // else formatter = new ValueFormatter(style, precision); + // formatter.useMinMaxColumn(minMaxAllowed() && min_max_col.isSelected()); + // if (tabular.isSelected()) + // export = + // new SpreadsheetExportJob( + // model, + // start_end.getStart(), + // start_end.getEnd(), + // source, + // optimize_parameter, + // formatter, + // filename, + // this::handleError, + // unixTimeStamp.get()); + // else + // export = + // new PlainExportJob( + // model, + // start_end.getStart(), + // start_end.getEnd(), + // source, + // optimize_parameter, + // formatter, + // filename, + // this::handleError, + // unixTimeStamp.get()); + } + + JobManager.schedule(filename, export); + } catch (Exception ex) { + handleError(ex); + } + } + + private void handleError(final Exception ex) { + ExceptionDetailsErrorDialog.openError(this, Messages.Error, "Export error", ex); + } + + /** @param memento Where to save current state */ + public void save(final Memento memento) { + memento.setNumber(TAG_SOURCE, sources.getToggles().indexOf(sources.getSelectedToggle())); + memento.setString(TAG_OPTCOUNT, optimize.getText()); + memento.setString(TAG_LININT, linear.getText()); + memento.setNumber(TAG_TYPE, table_types.getToggles().indexOf(table_types.getSelectedToggle())); + memento.setNumber(TAG_FORMAT, formats.getToggles().indexOf(formats.getSelectedToggle())); + memento.setString(TAG_DIGITS, format_digits.getText()); + memento.setString(TAG_FILE, filename.getText()); + } + + /** @param memento From where to restore saved state */ + public void restore(final Memento memento) { + memento + .getNumber(TAG_SOURCE) + .ifPresent(index -> sources.selectToggle(sources.getToggles().get(index.intValue()))); + memento.getString(TAG_OPTCOUNT).ifPresent(optimize::setText); + memento.getString(TAG_LININT).ifPresent(linear::setText); + memento + .getNumber(TAG_TYPE) + .ifPresent( + index -> table_types.selectToggle(table_types.getToggles().get(index.intValue()))); + memento + .getNumber(TAG_FORMAT) + .ifPresent(index -> formats.selectToggle(formats.getToggles().get(index.intValue()))); + memento.getString(TAG_DIGITS).ifPresent(format_digits::setText); + memento.getString(TAG_FILE).ifPresent(filename::setText); + } +} diff --git a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/Messages.java b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/Messages.java new file mode 100644 index 0000000000..c9ec408a36 --- /dev/null +++ b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/Messages.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2010 Oak Ridge National Laboratory. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + ******************************************************************************/ +package com.windhoverlabs.yamcs.applications.parameter; + +import org.phoebus.framework.nls.NLS; + +/** + * Eclipse string externalization + * + * @author Kay Kasemir + * @author Pavel Charvat + */ +public class Messages { + // --- + // --- Keep alphabetically sorted and 'in sync' with messages.properties! + // --- + public static String BaseDirectorySelTT; + public static String BaseDirectoryTT; + public static String BrowserRootTitle; + public static String ColName; + public static String ColSize; + public static String ColTime; + public static String CopyPathClp; + public static String CreateDirectoryErr; + public static String CreateDirectoryHdr; + public static String Delete; + public static String DeleteJobName; + public static String DeletePromptHeader; + public static String DeletePromptTitle; + public static String DisplayName; + public static String Duplicate; + public static String DuplicateAlert1; + public static String DuplicateAlert2; + public static String DuplicateJobName; + public static String DuplicatePrefix; + public static String DuplicatePromptHeader; + public static String HomeButtonTT; + public static String LookupJobName; + public static String MenuPath; + public static String MoveOrCopyAlert; + public static String MoveOrCopyAlertTitle; + public static String MoveOrCopyJobName; + public static String NewFolder; + public static String NewFolderAlert; + public static String Open; + public static String OpenAlert1; + public static String OpenAlert2; + public static String OpenWith; + public static String Paste; + public static String PropDlgBytes; + public static String PropDlgDate; + public static String PropDlgExecutable; + public static String PropDlgPath; + public static String PropDlgPermissions; + public static String PropDlgSize; + public static String PropDlgTitle; + public static String PropDlgWritable; + public static String Properties; + public static String Refresh; + public static String Rename; + public static String RenameHdr; + public static String RenameJobName; + public static String SetBaseDirectory; + // --- + // --- Keep alphabetically sorted and 'in sync' with messages.properties! + // --- + + static { + NLS.initializeMessages(Messages.class); + } + + private Messages() { + // Prevent instantiation + } +} diff --git a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExport.java b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExport.java similarity index 94% rename from app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExport.java rename to app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExport.java index 2a38b2e64f..59450ed9c1 100644 --- a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExport.java +++ b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExport.java @@ -1,6 +1,5 @@ -package com.windhoverlabs.yamcs.applications.events; +package com.windhoverlabs.yamcs.applications.parameter; -import java.nio.file.Path; import java.util.logging.Level; import java.util.logging.Logger; import org.phoebus.framework.preferences.AnnotatedPreferences; @@ -22,7 +21,7 @@ public class ParameterExport implements AppDescriptor { static { AnnotatedPreferences.initialize(ParameterExport.class, "/eventviewer_preferences.properties"); } - + @Override public String getName() { return Name; diff --git a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExportController.java b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExportController.java new file mode 100644 index 0000000000..1986f1d867 --- /dev/null +++ b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExportController.java @@ -0,0 +1,235 @@ +package com.windhoverlabs.yamcs.applications.parameter; + +import com.windhoverlabs.pv.yamcs.YamcsAware; +import com.windhoverlabs.yamcs.core.CMDR_Event; +import com.windhoverlabs.yamcs.core.YamcsObjectManager; +import com.windhoverlabs.yamcs.core.YamcsServer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.logging.Logger; +import javafx.application.Platform; +import javafx.beans.Observable; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.geometry.Orientation; +import javafx.scene.Node; +import javafx.scene.control.SplitPane; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.scene.control.cell.CheckBoxTableCell; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.GridPane; +import javafx.util.Callback; +import org.csstudio.trends.databrowser3.Activator; +import org.csstudio.trends.databrowser3.Messages; +import org.phoebus.framework.autocomplete.PVProposalService; +import org.phoebus.framework.autocomplete.Proposal; +import org.phoebus.framework.autocomplete.ProposalService; + +public class ParameterExportController { + class ExportPV { + private final SimpleBooleanProperty export = new SimpleBooleanProperty(); + private final SimpleStringProperty pv = new SimpleStringProperty(); + + ExportPV(String pv, boolean export) { + this.pv.set(pv); + this.export.set(export); + } + + public final SimpleStringProperty pvProperty() { + return pv; + } + + public final SimpleBooleanProperty exportProperty() { + return export; + } + } + + public static final Logger log = + Logger.getLogger(ParameterExportController.class.getPackageName()); + + private final TableView tableView = new TableView(); + + TableColumn exportColumn = new TableColumn("export"); + TableColumn pvColumn = new TableColumn("pv"); + + private ObservableList data = + FXCollections.observableArrayList(new ArrayList()); + private static final int dataSize = 10_023; + + private ProposalService proposalService = PVProposalService.INSTANCE; + + // TODO:Eventually these will be in spinner nodes. These are the event filters. + private String currentServer = "sitl"; + private String currentInstance = "yamcs-cfs"; + + YamcsAware yamcsListener = null; + + @FXML private GridPane gridPane; + + @FXML private TextField pvTextField; + + private ObservableList proposalList = FXCollections.observableArrayList(); + private HashSet exportSet = new HashSet(); + + private ExportView paramExportView = new ExportView(); + + public ExportView getParamExportView() { + return paramExportView; + } + + private Tab exportTab; + @FXML private TabPane exportTabPane; + @FXML private SplitPane mainSplit; + + public Node getRootPane() { + return mainSplit; + } + + @FXML + public void initialize() { + tableView.setId("paramExportTable"); + + pvColumn.setCellValueFactory(new PropertyValueFactory<>("pv")); + pvColumn.setCellValueFactory(cellData -> cellData.getValue().pvProperty()); + + exportColumn.prefWidthProperty().bind(tableView.widthProperty().multiply(0.3)); + pvColumn.prefWidthProperty().bind(tableView.widthProperty().multiply(0.7)); + exportColumn.minWidthProperty().bind(tableView.widthProperty().multiply(0.3)); + pvColumn.minWidthProperty().bind(tableView.widthProperty().multiply(0.7)); + exportColumn.setCellValueFactory(cellData -> cellData.getValue().exportProperty()); + exportColumn.setCellFactory(tc -> new CheckBoxTableCell<>()); + + this.proposalList = + FXCollections.observableArrayList( + new Callback() { + + @Override + public Observable[] call(ExportPV param) { + return new Observable[] {param.exportProperty()}; + } + }); + + this.proposalList.addListener( + new ListChangeListener() { + + @Override + public void onChanged(ListChangeListener.Change c) { + while (c.next()) { + if (c.wasUpdated()) { + ExportPV item = proposalList.get(c.getFrom()); + if (item.exportProperty().get()) { + exportSet.add(item.pvProperty().get()); + } else { + exportSet.remove(item.pvProperty().get()); + } + paramExportView.getParameters().clear(); + exportSet.forEach( + p -> { + paramExportView.getParameters().add(p); + }); + } + } + } + }); + // tableView.setColumnResizePolicy(null); + tableView.getColumns().addAll(pvColumn, exportColumn); + + yamcsListener = + new YamcsAware() { + public void changeDefaultInstance() { + tableView.refresh(); + } + + public void onYamcsConnected() { + if (YamcsObjectManager.getDefaultInstance() != null) { + YamcsObjectManager.getDefaultInstance() + .getEvents() + .addListener( + new ListChangeListener() { + @Override + public void onChanged(Change c) { + Platform.runLater(() -> {}); + } + }); + tableView.refresh(); + } + } + + public void onInstancesReady(YamcsServer s) { + if (s.getDefaultInstance() != null) { + s.getDefaultInstance() + .getEvents() + .addListener( + new ListChangeListener() { + @Override + public void onChanged(Change c) { + Platform.runLater(() -> {}); + } + }); + tableView.refresh(); + } + } + }; + + YamcsObjectManager.addYamcsListener(yamcsListener); + pvTextField.setOnKeyTyped( + e -> { + String text = pvTextField.getText(); + + proposalService.lookup( + text, + (name, priority, proposals) -> + handleLookupResult(pvTextField, text, name, priority, proposals)); + if (e.getCode() == KeyCode.ENTER) { + proposalService.addToHistory(text); + } + }); + tableView.setItems(proposalList); + tableView.setEditable(true); + gridPane.add(tableView, 0, 1); + createExportTab(); + mainSplit.setOrientation(Orientation.VERTICAL); + mainSplit.setDividerPositions(0.8); + } + + private void handleLookupResult( + final javafx.scene.control.TextInputControl field, + final String text, + final String name, + final int priority, + final List proposals) { + synchronized (proposalList) { + proposalList.clear(); + exportSet.forEach( + item -> { + proposalList.add(new ExportPV(item, true)); + }); + for (Proposal p : proposals) { + proposalList.add(new ExportPV(p.getValue(), false)); + } + } + } + + public ParameterExportController() {} + + public void unInit() { + YamcsObjectManager.removeListener(yamcsListener); + } + + private void createExportTab() { + exportTab = new Tab(Messages.Export, paramExportView); + exportTab.setClosable(false); + exportTab.setGraphic(Activator.getIcon("export")); + exportTabPane.getTabs().add(exportTab); + } +} diff --git a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExportMenuEntry.java b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExportMenuEntry.java similarity index 92% rename from app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExportMenuEntry.java rename to app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExportMenuEntry.java index b3d6dff603..8729fe5735 100644 --- a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/events/ParameterExportMenuEntry.java +++ b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExportMenuEntry.java @@ -1,4 +1,4 @@ -package com.windhoverlabs.yamcs.applications.events; +package com.windhoverlabs.yamcs.applications.parameter; import javafx.scene.image.Image; import org.phoebus.framework.workbench.ApplicationService; diff --git a/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExportViewerInstance.java b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExportViewerInstance.java new file mode 100644 index 0000000000..af2b43e086 --- /dev/null +++ b/app/commander/app-commander-parameter-export/src/main/java/com/windhoverlabs/yamcs/applications/parameter/ParameterExportViewerInstance.java @@ -0,0 +1,172 @@ +package com.windhoverlabs.yamcs.applications.parameter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import org.phoebus.framework.nls.NLS; +import org.phoebus.framework.persistence.Memento; +import org.phoebus.framework.persistence.MementoTree; +import org.phoebus.framework.persistence.XMLMementoTree; +import org.phoebus.framework.spi.AppDescriptor; +import org.phoebus.framework.spi.AppInstance; +import org.phoebus.framework.workbench.Locations; +import org.phoebus.ui.docking.DockItem; +import org.phoebus.ui.docking.DockPane; + +/** @author lgomez */ +@SuppressWarnings("nls") +public class ParameterExportViewerInstance implements AppInstance { + private static final String YAMCS_PARAMETER_EXPORT_MEMENTO_FILENAME = + "yamcs_parameter_export_memento"; + + /** Logger for all file browser code */ + public static final Logger logger = + Logger.getLogger(ParameterExportViewerInstance.class.getPackageName()); + + /** Memento tags */ + private static final String EXPORT_START = "yamcs_export_start", EXPORT_END = "yamcs_export_end"; + + static ParameterExportViewerInstance INSTANCE; + + private FXMLLoader loader; + + private ParameterExportController parameterExportInstanceController = null; + + private final AppDescriptor app; + + private DockItem tab = null; + + public ParameterExportViewerInstance(AppDescriptor app) { + this.app = app; + Node content = null; + ResourceBundle resourceBundle = NLS.getMessages(Messages.class); + FXMLLoader loader = new FXMLLoader(); + loader.setResources(resourceBundle); + loader.setLocation(this.getClass().getResource("ExportView.fxml")); + + try { + content = loader.load(); + parameterExportInstanceController = loader.getController(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + tab = new DockItem(this, content); + + // start + // .getScene() + // .getStylesheets() + // .add(ExportView.class.getResource("/text-field-red-border.css").toExternalForm()); + // + + parameterExportInstanceController.getParamExportView().configureValidators(); + + DockPane.getActiveDockPane().addTab(tab); + tab.addCloseCheck( + () -> { + INSTANCE = null; + parameterExportInstanceController.unInit(); + return CompletableFuture.completedFuture(true); + }); + } + + @Override + public AppDescriptor getAppDescriptor() { + return app; + } + + @Override + public void restore(final Memento memento) { + + try { + final XMLMementoTree csvExporterMementoTree = + XMLMementoTree.read( + new FileInputStream( + new File(Locations.user(), YAMCS_PARAMETER_EXPORT_MEMENTO_FILENAME))); + Memento csvExporterMemento = + csvExporterMementoTree.getChild(YAMCS_PARAMETER_EXPORT_MEMENTO_FILENAME); + parameterExportInstanceController + .getParamExportView() + .setStart(csvExporterMemento.getString(EXPORT_START).orElse("")); + parameterExportInstanceController + .getParamExportView() + .setEnd(csvExporterMemento.getString(EXPORT_END).orElse("")); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Override + public void save(final Memento memento) { + // TODO:Implement memento pattern + try { + } catch (Exception e) { + logger.warning("Error saving Events connections...:" + e.toString()); + } + logger.info("Saving Yamcs Events..."); + + // Save yamcs connections + try { + createParameterExportMemento(); + } catch (Exception ex) { + logger.log(Level.WARNING, "Error writing saved state to " + "", ex); + } + } + + private void createParameterExportMemento() throws Exception, FileNotFoundException { + + logger.info("Saving CSV Exporter state..."); + final XMLMementoTree csvExporterMemento = XMLMementoTree.create(); + MementoTree exportData = + csvExporterMemento.createChild(YAMCS_PARAMETER_EXPORT_MEMENTO_FILENAME); + + boolean saveMemento = false; + + if (isViewerValid()) { + exportData.setString( + EXPORT_START, parameterExportInstanceController.getParamExportView().getStart()); + exportData.setString( + EXPORT_END, parameterExportInstanceController.getParamExportView().getEnd()); + saveMemento = true; + } + if (saveMemento) { + csvExporterMemento.write( + new FileOutputStream( + new File(Locations.user(), YAMCS_PARAMETER_EXPORT_MEMENTO_FILENAME))); + } else { + logger.info("Ignoring invalid fields. Only the last valid state will be saved."); + } + } + + private boolean isViewerValid() { + return (!parameterExportInstanceController.getParamExportView().getStart().isBlank() + && !parameterExportInstanceController.getParamExportView().getStart().isEmpty()) + && (!parameterExportInstanceController.getParamExportView().getEnd().isBlank() + && !parameterExportInstanceController.getParamExportView().getEnd().isEmpty()); + } + + public void raise() { + tab.select(); + } + + public ParameterExportController getController() { + return parameterExportInstanceController; + } + + private static ObservableList restoreEvents() { + // TODO:Implement memento pattern + return FXCollections.observableArrayList(new ArrayList()); + } +} diff --git a/app/commander/app-commander-parameter-export/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor b/app/commander/app-commander-parameter-export/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor index c40ad0a963..2b3d6dcced 100644 --- a/app/commander/app-commander-parameter-export/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor +++ b/app/commander/app-commander-parameter-export/src/main/resources/META-INF/services/org.phoebus.framework.spi.AppDescriptor @@ -1 +1 @@ -com.windhoverlabs.yamcs.applications.events.EventViewerApp \ No newline at end of file +com.windhoverlabs.yamcs.applications.parameter.ParameterExport \ No newline at end of file diff --git a/app/commander/app-commander-parameter-export/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry b/app/commander/app-commander-parameter-export/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry index 35dd5af730..7e645953f3 100644 --- a/app/commander/app-commander-parameter-export/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry +++ b/app/commander/app-commander-parameter-export/src/main/resources/META-INF/services/org.phoebus.ui.spi.MenuEntry @@ -1 +1 @@ -com.windhoverlabs.yamcs.applications.events.EventViewerMenuEntry \ No newline at end of file +com.windhoverlabs.yamcs.applications.parameter.ParameterExportMenuEntry \ No newline at end of file diff --git a/app/commander/app-commander-parameter-export/src/main/resources/com/windhoverlabs/yamcs/applications/parameter/ExportView.fxml b/app/commander/app-commander-parameter-export/src/main/resources/com/windhoverlabs/yamcs/applications/parameter/ExportView.fxml new file mode 100644 index 0000000000..180a2e758b --- /dev/null +++ b/app/commander/app-commander-parameter-export/src/main/resources/com/windhoverlabs/yamcs/applications/parameter/ExportView.fxml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/commander/app-commander-parameter-export/src/main/resources/com/windhoverlabs/yamcs/applications/events/messages.properties b/app/commander/app-commander-parameter-export/src/main/resources/com/windhoverlabs/yamcs/applications/parameter/messages.properties similarity index 100% rename from app/commander/app-commander-parameter-export/src/main/resources/com/windhoverlabs/yamcs/applications/events/messages.properties rename to app/commander/app-commander-parameter-export/src/main/resources/com/windhoverlabs/yamcs/applications/parameter/messages.properties diff --git a/app/commander/app-commander-parameter-export/src/main/resources/text-field-red-border.css b/app/commander/app-commander-parameter-export/src/main/resources/text-field-red-border.css new file mode 100644 index 0000000000..b29e4bd085 --- /dev/null +++ b/app/commander/app-commander-parameter-export/src/main/resources/text-field-red-border.css @@ -0,0 +1,4 @@ +.error { + -fx-text-box-border: red ; + -fx-focus-color: red ; +} diff --git a/app/commander/pom.xml b/app/commander/pom.xml index 696bb6af86..6b6249d703 100644 --- a/app/commander/pom.xml +++ b/app/commander/pom.xml @@ -171,7 +171,9 @@ app-commander-display-runtime app-commander-mission-planner app-commander-command-options + app-commander-parameter-export + app-commander-links - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT com.windhoverlabs diff --git a/core/commander-core/Makefile b/core/commander-core/Makefile new file mode 100644 index 0000000000..b1173ee1fa --- /dev/null +++ b/core/commander-core/Makefile @@ -0,0 +1,2 @@ +format: + mvn com.coveo:fmt-maven-plugin:format diff --git a/core/commander-core/pom.xml b/core/commander-core/pom.xml index 4516072341..98c3f4cdd6 100644 --- a/core/commander-core/pom.xml +++ b/core/commander-core/pom.xml @@ -7,7 +7,7 @@ com.windhoverlabs commander-core - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT diff --git a/core/commander-core/src/main/java/com/windhoverlabs/pv/yamcs/YamcsAware.java b/core/commander-core/src/main/java/com/windhoverlabs/pv/yamcs/YamcsAware.java index 8ae5fa024f..eceb250459 100644 --- a/core/commander-core/src/main/java/com/windhoverlabs/pv/yamcs/YamcsAware.java +++ b/core/commander-core/src/main/java/com/windhoverlabs/pv/yamcs/YamcsAware.java @@ -56,4 +56,6 @@ default void changeProcessorInfo(ProcessorInfo processor) {} default void updateTime(Instant time) {} default void updateClearance(boolean enabled, SignificanceLevelType level) {} + + default void updateLink(String link) {} } diff --git a/core/commander-core/src/main/java/com/windhoverlabs/pv/yamcs/autocomplete/YamcsPVProposalProvider.java b/core/commander-core/src/main/java/com/windhoverlabs/pv/yamcs/autocomplete/YamcsPVProposalProvider.java index 61f79a63e0..c19d5964f4 100644 --- a/core/commander-core/src/main/java/com/windhoverlabs/pv/yamcs/autocomplete/YamcsPVProposalProvider.java +++ b/core/commander-core/src/main/java/com/windhoverlabs/pv/yamcs/autocomplete/YamcsPVProposalProvider.java @@ -44,7 +44,8 @@ public List lookup(final String text) { // } // content = AutoCompleteHelper.trimWildcards(content); - // content = content.replaceAll("\\[[0-9]+\\]", "[]"); // Ignore specific index into array + // content = content.replaceAll("\\[[0-9]+\\]", "[]"); // Ignore specific index into + // array // var namePattern = AutoCompleteHelper.convertToPattern(content); // namePattern = Pattern.compile(namePattern.pattern(), Pattern.CASE_INSENSITIVE); @@ -96,45 +97,15 @@ private void scanTypeForPvCandidates( } } if (type.hasArrayInfo()) { - var entryPvName = basePvName + "[]"; + var entryPvName = new StringBuilder(basePvName + "[]"); var entryType = type.getArrayInfo().getType(); - scanTypeForPvCandidates(entryPvName, entryType, pvCandidates); + for (int i = 0; i < type.getArrayInfo().getDimensionsList().get(0).getFixedValue(); i++) { + // System.out.println("arrayItem-->" + entryPvName); + entryPvName = new StringBuilder(basePvName + "[]"); + entryPvName.insert(basePvName.length() + 1, i); + System.out.println("array info for " + entryPvName); + pvCandidates.add(entryPvName.toString()); + } } } - - // { - // if (!text.startsWith("loc://")) return generic; - // - // final List result = new ArrayList<>(); - // // final List split = LocProposal.splitNameTypeAndInitialValues(text); - // final List split = new ArrayList(); - // - // // Use the entered name, but add "loc://". - // // Default to just "loc://name" - // String name = split.get(0).trim(); - // if (name.isEmpty()) name = "loc://name"; - // else if (!name.startsWith("loc://")) name = "loc://" + name; - // - // // Use the entered type, or default to "VType" - // String type = split.get(1); - // if (type != null) { - // result.add(new LocProposal(name, "VDouble", "number")); - // result.add(new LocProposal(name, "VLong", "number")); - // result.add(new LocProposal(name, "VString", "\"string\"")); - // result.add(new LocProposal(name, "VEnum", "index", "\"Label 1\"", "\"Label 2\", ...")); - // result.add(new LocProposal(name, "VDoubleArray", "number", "number, ...")); - // result.add(new LocProposal(name, "VStringArray", "\"string\"", "\"string\", ...")); - // result.add(new LocProposal(name, "VTable")); - // } else { - // result.add(new LocProposal(name, "VType", "number")); - // result.add(new LocProposal(name, "VType", "\"string\"")); - // } - // return result; - // } - - // @Override - // public AutoCompleteResult listResult(ContentDescriptor desc, int limit) { - // - // } - } diff --git a/core/commander-core/src/main/java/com/windhoverlabs/yamcs/core/CMDR_YamcsInstance.java b/core/commander-core/src/main/java/com/windhoverlabs/yamcs/core/CMDR_YamcsInstance.java index f09b8e6e7e..c950f65fff 100644 --- a/core/commander-core/src/main/java/com/windhoverlabs/yamcs/core/CMDR_YamcsInstance.java +++ b/core/commander-core/src/main/java/com/windhoverlabs/yamcs/core/CMDR_YamcsInstance.java @@ -2,14 +2,18 @@ import com.windhoverlabs.pv.yamcs.YamcsPV; import com.windhoverlabs.pv.yamcs.YamcsSubscriptionService; +import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; import java.util.logging.Logger; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.yamcs.client.EventSubscription; +import org.yamcs.client.LinkSubscription; import org.yamcs.client.Page; import org.yamcs.client.YamcsClient; import org.yamcs.client.archive.ArchiveClient; @@ -19,7 +23,10 @@ import org.yamcs.protobuf.GetServerInfoResponse; import org.yamcs.protobuf.GetServerInfoResponse.CommandOptionInfo; import org.yamcs.protobuf.Mdb.ParameterInfo; +import org.yamcs.protobuf.Pvalue.ParameterValue; import org.yamcs.protobuf.SubscribeEventsRequest; +import org.yamcs.protobuf.links.LinkInfo; +import org.yamcs.protobuf.links.SubscribeLinksRequest; // import org.yamcs.protobuf.Event; @@ -29,11 +36,42 @@ public class CMDR_YamcsInstance extends YamcsObject> { private ProcessorClient yamcsProcessor = null; private YamcsSubscriptionService paramSubscriptionService; private EventSubscription eventSubscription; + private LinkSubscription linkSubscription; private MissionDatabase missionDatabase; // private EventSubscription eventSubscription; private ArchiveClient yamcsArchiveClient; private CMDR_YamcsInstanceState instanceState; + // TODO:Not sure if we want to have this on every instance and their server...just want it to work + // for now. + // Useful for "special" command link arguments such as cop1Bypass + private HashMap extraCommandArgs = + new HashMap(); + + private ObservableList optionsList = FXCollections.observableArrayList(); + private ObservableList events = FXCollections.observableArrayList(); + private ObservableList links = FXCollections.observableArrayList(); + + private HashMap linksMap = new HashMap(); + + public HashMap getLinksMap() { + return linksMap; + } + + private HashMap activeInLinks = new HashMap(); + + private HashMap LastUpdateLinks = new HashMap(); + + public HashMap getActiveInLinks() { + return activeInLinks; + } + + private HashMap activeOutLinks = new HashMap(); + + public ObservableList getLinks() { + return links; + } + public CMDR_YamcsInstanceState getInstanceState() { return instanceState; } @@ -60,13 +98,6 @@ public void setValue(String newValue) { this.value = newValue; } } - // TODO:Not sure if we want to have this on every instance and their server...just want it to work - // for now. - // Useful for "special" command link arguments such as cop1Bypass - private HashMap extraCommandArgs = - new HashMap(); - - private ObservableList optionsList = FXCollections.observableArrayList(); public ObservableList getOptionsList() { return optionsList; @@ -80,8 +111,6 @@ public ArchiveClient getYamcsArchiveClient() { return yamcsArchiveClient; } - private ObservableList events = FXCollections.observableArrayList(); - public ObservableList getEvents() { return events; } @@ -121,6 +150,47 @@ protected void initYamcsSubscriptionService( yamcsClient.createParameterSubscription(), serverName, this.getName(), procesor); } + protected void initLinkSubscription(YamcsClient yamcsClient, String serverName) { + linkSubscription = yamcsClient.createLinkSubscription(); + linkSubscription.addMessageListener( + linkEvent -> { + switch (linkEvent.getType()) { + case REGISTERED: + case UPDATED: + { + var link = linkEvent.getLinkInfo(); + LinkInfo linkFromList = null; + + LastUpdateLinks.put(link.getName(), Instant.now()); + + linksMap.put(link.getName(), link); + + boolean linkExistsInlList = false; + + for (var l : links) { + if (l != null) { + if (l.getName().equals(link.getName())) { + linkFromList = l; + linkExistsInlList = true; + } + } + } + + if (linkExistsInlList) { + links.remove(linkFromList); + } + links.add(linksMap.get(link.getName())); + } + + break; + case UNREGISTERED: + // TODO but not currently sent by Yamcs + } + }); + + linkSubscription.sendMessage(SubscribeLinksRequest.newBuilder().setInstance(getName()).build()); + } + protected void initEventSubscription(YamcsClient yamcsClient, String serverName) { eventSubscription = yamcsClient.createEventSubscription(); eventSubscription.addMessageListener( @@ -145,12 +215,10 @@ protected void initEventSubscription(YamcsClient yamcsClient, String serverName) } private MissionDatabase loadMissionDatabase(YamcsClient client) { - // monitor.subTask("Loading mission database"); var missionDatabase = new MissionDatabase(); var mdbClient = client.createMissionDatabaseClient(getName()); try { - // log.fine("Fetching available parameters"); var page = mdbClient.listParameters(ListOptions.limit(500)).get(); page.iterator().forEachRemaining(missionDatabase::addParameter); while (page.hasNextPage()) { @@ -158,16 +226,12 @@ private MissionDatabase loadMissionDatabase(YamcsClient client) { page.iterator().forEachRemaining(missionDatabase::addParameter); } - // log.fine("Fetching available commands"); var commandPage = mdbClient.listCommands(ListOptions.limit(200)).get(); commandPage.iterator().forEachRemaining(missionDatabase::addCommand); while (commandPage.hasNextPage()) { commandPage = commandPage.getNextPage().get(); commandPage.iterator().forEachRemaining(missionDatabase::addCommand); } - // log.info(String.format("Loaded %d parameters and %d commands", - // missionDatabase.getParameterCount(), - // missionDatabase.getCommandCount())); } catch (Exception e) { e.printStackTrace(); // throw new Exception("Failed to load mission database", e); @@ -228,6 +292,7 @@ public void activate(YamcsClient yamcsClient, String serverName) { initProcessorClient(yamcsClient); initYamcsSubscriptionService(yamcsClient, serverName, "realtime"); initEventSubscription(yamcsClient, serverName); + initLinkSubscription(yamcsClient, serverName); initMDBParameterRDequest(yamcsClient, serverName); missionDatabase = loadMissionDatabase(yamcsClient); @@ -316,4 +381,29 @@ public void switchProcessor(YamcsClient yamcsClient, String serverName, String p paramSubscriptionService.destroy(); initYamcsSubscriptionService(yamcsClient, serverName, processorName); } + + public void getParameters( + YamcsClient yamcsClient, + List parameters, + Instant start, + Instant end, + Consumer>> consumer) { + + // this.getYamcsArchiveClient().streamValues(parameters, consumer, start, end); + ArrayList> pages = new ArrayList>(); + for (var p : parameters) { + try { + pages.add(this.getYamcsArchiveClient().listValues(p, start, end).get()); + } catch (InterruptedException | ExecutionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + consumer.accept(pages); + } + + public boolean isLinkActive(String linkName) { + return Duration.between(Instant.now(), LastUpdateLinks.get(linkName)).toMillis() < 1000; + } } diff --git a/phoebus-product/pom.xml b/phoebus-product/pom.xml index 95959c43d1..9b42f82b53 100644 --- a/phoebus-product/pom.xml +++ b/phoebus-product/pom.xml @@ -22,41 +22,51 @@ app-filebrowser 4.6.10-SNAPSHOT + + com.windhoverlabs + app-commander-parameter-export + 0.2.6-SNAPSHOT + com.windhoverlabs app-commander-connections - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT com.windhoverlabs app-commander-events - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT + + + com.windhoverlabs + app-commander-links + 0.2.6-SNAPSHOT com.windhoverlabs app-commander-command-options - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT com.windhoverlabs commander-core - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT com.windhoverlabs app-commander-display-model - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT com.windhoverlabs app-commander-display-representation-javafx - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT com.windhoverlabs app-commander-display-runtime - 0.2.4-SNAPSHOT + 0.2.6-SNAPSHOT org.phoebus diff --git a/pom.xml b/pom.xml index 0f1b87b0af..7f92c8de3c 100644 --- a/pom.xml +++ b/pom.xml @@ -126,6 +126,7 @@ public + false @@ -375,7 +376,7 @@ none 11 - + false