diff --git a/plugins-dev/caravel-env/plugins.lst b/plugins-dev/caravel-env/plugins.lst new file mode 100644 index 0000000000..6298e57ec8 --- /dev/null +++ b/plugins-dev/caravel-env/plugins.lst @@ -0,0 +1 @@ +pt.lsts.neptus.plugins.caravelenv.CaravelDataPlotter diff --git a/plugins-dev/caravel-env/src/java/pt/lsts/neptus/plugins/caravelenv/CaravelDataPlotter.java b/plugins-dev/caravel-env/src/java/pt/lsts/neptus/plugins/caravelenv/CaravelDataPlotter.java new file mode 100644 index 0000000000..bce18d2400 --- /dev/null +++ b/plugins-dev/caravel-env/src/java/pt/lsts/neptus/plugins/caravelenv/CaravelDataPlotter.java @@ -0,0 +1,1009 @@ +/* + * Copyright (c) 2004-2025 Universidade do Porto - Faculdade de Engenharia + * Laboratório de Sistemas e Tecnologia Subaquática (LSTS) + * All rights reserved. + * Rua Dr. Roberto Frias s/n, sala I203, 4200-465 Porto, Portugal + * + * This file is part of Neptus, Command and Control Framework. + * + * Commercial Licence Usage + * Licencees holding valid commercial Neptus licences may use this file + * in accordance with the commercial licence agreement provided with the + * Software or, alternatively, in accordance with the terms contained in a + * written agreement between you and Universidade do Porto. For licensing + * terms, conditions, and further information contact lsts@fe.up.pt. + * + * Modified European Union Public Licence - EUPL v.1.1 Usage + * Alternatively, this file may be used under the terms of the Modified EUPL, + * Version 1.1 only (the "Licence"), appearing in the file LICENSE.md + * included in the packaging of this file. You may not use this work + * except in compliance with the Licence. Unless required by applicable + * law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the Licence for the specific + * language governing permissions and limitations at + * https://github.com/LSTS/neptus/blob/develop/LICENSE.md + * and http://ec.europa.eu/idabc/eupl.html. + * + * For more information please see . + * + * Author: Paulo Dias + * 5 June 2024 + */ +package pt.lsts.neptus.plugins.caravelenv; + +import com.google.common.eventbus.Subscribe; +import org.apache.commons.lang3.tuple.Triple; +import pt.lsts.imc.AirSaturation; +import pt.lsts.imc.Chlorophyll; +import pt.lsts.imc.CurrentProfile; +import pt.lsts.imc.CurrentProfileCell; +import pt.lsts.imc.DissolvedOrganicMatter; +import pt.lsts.imc.DissolvedOxygen; +import pt.lsts.imc.IMCMessage; +import pt.lsts.imc.Salinity; +import pt.lsts.imc.Temperature; +import pt.lsts.imc.Turbidity; +import pt.lsts.neptus.NeptusLog; +import pt.lsts.neptus.colormap.ColorBarPainterUtil; +import pt.lsts.neptus.colormap.ColorMap; +import pt.lsts.neptus.colormap.ColorMapFactory; +import pt.lsts.neptus.colormap.ColorMapUtils; +import pt.lsts.neptus.comm.manager.imc.EntitiesResolver; +import pt.lsts.neptus.comm.manager.imc.ImcSystem; +import pt.lsts.neptus.comm.manager.imc.ImcSystemsHolder; +import pt.lsts.neptus.console.ConsoleLayer; +import pt.lsts.neptus.console.plugins.MainVehicleChangeListener; +import pt.lsts.neptus.messages.listener.Periodic; +import pt.lsts.neptus.plugins.NeptusProperty; +import pt.lsts.neptus.plugins.PluginDescription; +import pt.lsts.neptus.renderer2d.OffScreenLayerImageControl; +import pt.lsts.neptus.renderer2d.StateRenderer2D; +import pt.lsts.neptus.types.coord.LocationType; +import pt.lsts.neptus.util.AngleUtils; +import pt.lsts.neptus.util.conf.PreferencesListener; +import pt.lsts.neptus.util.coord.MapTileRendererCalculator; +import scala.runtime.StringFormat; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.RenderingHints; +import java.awt.Transparency; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Vector; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import static com.sun.tools.doclint.Entity.gt; + +/** + * @author pdias + * + */ +@PluginDescription(name = "Caravel Data Plotter", description = "Plotter for Caravel underway data.") +public class CaravelDataPlotter extends ConsoleLayer implements PreferencesListener, MainVehicleChangeListener { + + final static int ARROW_RADIUS = 12; + final static Path2D.Double arrow = new Path2D.Double(); + static { + arrow.moveTo(-5, 6); + arrow.lineTo(0, -6); + arrow.lineTo(5, 6); + arrow.lineTo(0, 3); + arrow.lineTo(-5, 6); + arrow.closePath(); + } + + @NeptusProperty(name = "Show temperature", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Temperature") + public boolean showTemp = false; + @NeptusProperty(name = "Show salinity", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Salinity") + public boolean showSal = true; + @NeptusProperty(name = "Show turbidity", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Turbidity") + public boolean showTurbidity = false; + @NeptusProperty(name = "Show chlorophyll", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Chlorophyll") + public boolean showChlorophyll = false; + @NeptusProperty(name = "Show dissolved organic matter", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Dissolved Organic Matter") + public boolean showDissolvedOrganicMatter = false; + @NeptusProperty(name = "Show dissolved oxygen", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Dissolved Oxygen") + public boolean showDissolvedOxygen = false; + @NeptusProperty(name = "Show air saturation", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Air Saturation") + public boolean showAirSaturation = false; + @NeptusProperty(name = "Show water current", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Water Current") + public boolean showWaterCurrent = false; + + @NeptusProperty(name = "Valid temperature data sources", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Temperature") + public String validTempDataSources = "CTD, Daemon"; + @NeptusProperty(name = "Valid salinity data sources", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Salinity") + public String validSalDataSources = "CTD, Daemon"; + @NeptusProperty(name = "Valid turbidity data sources", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Turbidity") + public String validTurbidityDataSources = "Fluorometers, Daemon"; + @NeptusProperty(name = "Valid chlorophyll data sources", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Chlorophyll") + public String validChlorophyllDataSources = "Fluorometers, Daemon"; + @NeptusProperty(name = "Valid dissolved organic matter data sources", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Dissolved Organic Matter") + public String validDissolvedOrganicMatterDataSources = "Fluorometers, Daemon"; + @NeptusProperty(name = "Valid dissolved oxygen data sources", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Dissolved Oxygen") + public String validDissolvedOxygenDataSources = "Dissolved Oxygen, Daemon"; + @NeptusProperty(name = "Valid air saturation data sources", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Air Saturation") + public String validAirSaturationDataSources = "Dissolved Oxygen, Daemon"; + @NeptusProperty(name = "Valid water current data sources", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Water Current") + public String validWaterCurrentDataSources = "ADCP, Daemon"; + + @NeptusProperty(name = "Min temperature", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Temperature") + public double minTemp = 15; + @NeptusProperty(name = "Max temperature", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Temperature") + public double maxTemp = 35; + + @NeptusProperty(name = "Temperature color map", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Temperature") + public ColorMap colormapTemp = ColorMapFactory.createJetColorMap(); + + @NeptusProperty(name = "Min salinity", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Salinity") + public double minSal = 33; + @NeptusProperty(name = "Max salinity", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Salinity") + public double maxSal = 36; + + @NeptusProperty(name = "Salinity color map", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Salinity") + public ColorMap colormapSal = ColorMapFactory.createJetColorMap(); + + @NeptusProperty(name = "Min turbidity", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Turbidity") + public double minTurbidity = 0; + @NeptusProperty(name = "Max turbidity", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Turbidity") + public double maxTurbidity = 30; + + @NeptusProperty(name = "Turbidity color map", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Turbidity") + public ColorMap colormapTurbidity = ColorMapFactory.createJetColorMap(); + + @NeptusProperty(name = "Min chlorophyll", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Chlorophyll") + public double minChlorophyll = 0; + @NeptusProperty(name = "Max chlorophyll", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Chlorophyll") + public double maxChlorophyll = 2; + + @NeptusProperty(name = "Chlorophyll color map", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Chlorophyll") + public ColorMap colormapChlorophyll = ColorMapFactory.createJetColorMap(); + + @NeptusProperty(name = "Min dissolved organic matter", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Dissolved Organic Matter") + public double minDissolvedOrganicMatter = 0; + @NeptusProperty(name = "Max dissolved organic matter", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Dissolved Organic Matter") + public double maxDissolvedOrganicMatter = 10; + + @NeptusProperty(name = "Dissolved Organic Matter color map", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Dissolved Organic Matter") + public ColorMap colormapDissolvedOrganicMatter = ColorMapFactory.createJetColorMap(); + + @NeptusProperty(name = "Min dissolved oxygen", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Dissolved Oxygen") + public double minDissolvedOxygen = 0; + @NeptusProperty(name = "Max dissolved oxygen", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Dissolved Oxygen") + public double maxDissolvedOxygen = 300; + + @NeptusProperty(name = "Dissolved Oxygen color map", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Dissolved Oxygen") + public ColorMap colormapDissolvedOxygen = ColorMapFactory.createJetColorMap(); + + @NeptusProperty(name = "Min air saturation", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Air Saturation") + public double minAirSaturation = 0; + @NeptusProperty(name = "Max air saturation", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Air Saturation") + public double maxAirSaturation = 100; + + @NeptusProperty(name = "Air Saturation color map", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Air Saturation") + public ColorMap colormapAirSaturation = ColorMapFactory.createJetColorMap(); + + @NeptusProperty(name = "Min water current", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Water Current") + public double minWaterCurrent = -2; + @NeptusProperty(name = "Max water current", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Water Current") + public double maxWaterCurrent = 2; + + @NeptusProperty(name = "Water Current color map", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Water Current") + public ColorMap colormapWaterCurrent = ColorMapFactory.createJetColorMap(); + @NeptusProperty(name = "Water Current Depth", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Water Current", + description = "The layer to show the water current data (m).") + public double waterCurrentDepth = 2.0; + @NeptusProperty(name = "Water Current Depth Window (m)", userLevel = NeptusProperty.LEVEL.REGULAR, category = "Water Current", + description = "Window is +- this value from the waterCurrentDepth.") + public short waterCurrentDepthWindow = 2; + + @NeptusProperty(name = "Max samples", userLevel = NeptusProperty.LEVEL.REGULAR) + public int maxSamples = 35000; + + @NeptusProperty(name = "Clamp to fit", userLevel = NeptusProperty.LEVEL.REGULAR) + public boolean clampToFit = false; + + private OffScreenLayerImageControl offScreenSalinity = new OffScreenLayerImageControl(); + private OffScreenLayerImageControl offScreenTemperature = new OffScreenLayerImageControl(); + private OffScreenLayerImageControl offScreenTurbidity = new OffScreenLayerImageControl(); + private OffScreenLayerImageControl offScreenChlorophyll = new OffScreenLayerImageControl(); + private OffScreenLayerImageControl offScreenDissolvedOrganicMatter = new OffScreenLayerImageControl(); + private OffScreenLayerImageControl offScreenDissolvedOxygen = new OffScreenLayerImageControl(); + private OffScreenLayerImageControl offScreenAirSaturation = new OffScreenLayerImageControl(); + private OffScreenLayerImageControl offScreenWaterCurrent = new OffScreenLayerImageControl(); + + private Thread painterThread = null; + private AtomicBoolean abortIndicator = null; + + private List pointsTemp = Collections.synchronizedList(new LinkedList<>()); + private List pointsSalinity = Collections.synchronizedList(new LinkedList<>()); + private List pointsTurbidity = Collections.synchronizedList(new LinkedList<>()); + private List pointsChlorophyll = Collections.synchronizedList(new LinkedList<>()); + private List pointsDissolvedOrganicMatter = Collections.synchronizedList(new LinkedList<>()); + private List pointsDissolvedOxygen = Collections.synchronizedList(new LinkedList<>()); + private List pointsAirSaturation = Collections.synchronizedList(new LinkedList<>()); + private List pointsWaterCurrent = Collections.synchronizedList(new LinkedList<>()); + + private AtomicInteger trigger = new AtomicInteger(0); + private int triggerCount = 0; + + @Override + public void mainVehicleChange(String id) { + pointsTemp.clear(); + pointsSalinity.clear(); + pointsTurbidity.clear(); + pointsChlorophyll.clear(); + pointsDissolvedOrganicMatter.clear(); + pointsDissolvedOxygen.clear(); + pointsAirSaturation.clear(); + pointsWaterCurrent.clear(); + } + + private static class DataPoint { + LocationType location = null; + double value = Double.NaN; + } + + private static class DataPointPolar extends DataPoint { + double directionRads = Double.NaN; + double zDown = Double.NaN; + } + + private static class DataPointXY { + Point2D point = null; + double value = Double.NaN; + } + + private static class DataPointXYPolar extends DataPointXY { + double directionRads = Double.NaN; + double zDown = Double.NaN; + } + + /* (non-Javadoc) + * @see pt.lsts.neptus.console.ConsoleLayer#initLayer() + */ + @Override + public void initLayer() { + } + + /* (non-Javadoc) + * @see pt.lsts.neptus.console.ConsoleLayer#cleanLayer() + */ + @Override + public void cleanLayer() { + pointsTemp.clear(); + + } + + /* (non-Javadoc) + * @see pt.lsts.neptus.console.ConsoleLayer#userControlsOpacity() + */ + @Override + public boolean userControlsOpacity() { + return false; + } + +// /* (non-Javadoc) +// * @see pt.lsts.neptus.util.nmea.NmeaListener#nmeaSentence(java.lang.String) +// */ +// @Override +// public void nmeaSentence(String sentence) { +// if (sentence.startsWith("t1")) { +// LocationType loc = MyState.getLocation().getNewAbsoluteLatLonDepth(); +// +// DataPoint pt = new DataPoint(); +// pt.location = loc; +// +// String[] tks = sentence.split(","); +// for (String tk : tks) { +// try { +// String[] tts = tk.trim().split("="); +// if (tts.length < 2) +// continue; +// switch (tts[0].trim()) { +// case "t1": +// pt.temperature = Double.parseDouble(tts[1]); +// break; +// case "s": +// pt.salinity= Double.parseDouble(tts[1]); +// break; +// default: +// break; +// } +// } +// catch (NumberFormatException e) { +// e.printStackTrace(); +// } +// } +// +// try { +// Salinity msgSal = new Salinity(Double.valueOf(pt.salinity).floatValue()); +// msgSal.setSrc(GeneralPreferences.imcCcuId.intValue()); +// msgSal.setTimestampMillis(System.currentTimeMillis()); +// LsfMessageLogger.log(msgSal); +// Temperature msgTemp = new Temperature(Double.valueOf(pt.temperature).floatValue()); +// msgTemp.setSrc(GeneralPreferences.imcCcuId.intValue()); +// msgTemp.setTimestampMillis(System.currentTimeMillis()); +// LsfMessageLogger.log(msgTemp); +// } +// catch (Exception e) { +// e.printStackTrace(); +// } +// +// points.add(pt); +// if (points.size() > maxSamples) +// points.remove(0); +// trigger.incrementAndGet(); +// } +// } + + private LocationType getSystemLocation() { + // TODO FIXME + ImcSystem sys = ImcSystemsHolder.getSystemWithName(getConsole().getMainSystem()); + return sys == null ? null : sys.getLocation(); + } + + private double getSystemLocationDepth() { + // TODO FIXME + ImcSystem sys = ImcSystemsHolder.getSystemWithName(getConsole().getMainSystem()); + return sys == null ? Double.NaN : sys.getLocation().getDepth(); + } + + private void extractSensorMeasurementValueFromMessage(double value, List points, IMCMessage msg) { + LocationType loc = getSystemLocation(); + DataPoint pt = new DataPoint(); + pt.location = loc; + pt.value = value; + + points.add(pt); + if (points.size() > maxSamples) + points.remove(0); + trigger.incrementAndGet(); + } + + @Subscribe + public void on(Temperature msg) { + if (!getConsole().getMainSystem().equalsIgnoreCase(msg.getSourceName())) { + return; + } + String entity = EntitiesResolver.resolveName(msg.getSourceName(), (int) msg.getSrcEnt()); + if (entity != null && !validTempDataSources.contains(entity)) { + return; + } + extractSensorMeasurementValueFromMessage(msg.getValue(), pointsTemp, msg); + offScreenTemperature.triggerImageRebuild(); + } + + @Subscribe + public void on(Salinity msg) { + if (!getConsole().getMainSystem().equalsIgnoreCase(msg.getSourceName())) { + return; + } + String entity = EntitiesResolver.resolveName(msg.getSourceName(), (int) msg.getSrcEnt()); + if (entity != null && !validSalDataSources.contains(entity)) { + return; + } + extractSensorMeasurementValueFromMessage(msg.getValue(), pointsSalinity, msg); + offScreenSalinity.triggerImageRebuild(); + } + + @Subscribe + public void on(Turbidity msg) { + if (!getConsole().getMainSystem().equalsIgnoreCase(msg.getSourceName())) { + return; + } + String entity = EntitiesResolver.resolveName(msg.getSourceName(), (int) msg.getSrcEnt()); + if (entity != null && !validTurbidityDataSources.contains(entity)) { + return; + } + extractSensorMeasurementValueFromMessage(msg.getValue(), pointsTurbidity, msg); + offScreenTurbidity.triggerImageRebuild(); + } + + @Subscribe + public void on(Chlorophyll msg) { + if (!getConsole().getMainSystem().equalsIgnoreCase(msg.getSourceName())) { + return; + } + String entity = EntitiesResolver.resolveName(msg.getSourceName(), (int) msg.getSrcEnt()); + if (entity != null && !validChlorophyllDataSources.contains(entity)) { + return; + } + extractSensorMeasurementValueFromMessage(msg.getValue(), pointsChlorophyll, msg); + offScreenChlorophyll.triggerImageRebuild(); + } + + @Subscribe + public void on(DissolvedOrganicMatter msg) { + if (!getConsole().getMainSystem().equalsIgnoreCase(msg.getSourceName())) { + return; + } + String entity = EntitiesResolver.resolveName(msg.getSourceName(), (int) msg.getSrcEnt()); + if (entity != null && !validDissolvedOrganicMatterDataSources.contains(entity)) { + return; + } + extractSensorMeasurementValueFromMessage(msg.getValue(), pointsDissolvedOrganicMatter, msg); + offScreenDissolvedOrganicMatter.triggerImageRebuild(); + } + + @Subscribe + public void on(DissolvedOxygen msg) { + if (!getConsole().getMainSystem().equalsIgnoreCase(msg.getSourceName())) { + return; + } + String entity = EntitiesResolver.resolveName(msg.getSourceName(), (int) msg.getSrcEnt()); + if (entity != null && !validDissolvedOxygenDataSources.contains(entity)) { + return; + } + extractSensorMeasurementValueFromMessage(msg.getValue(), pointsDissolvedOxygen, msg); + offScreenDissolvedOxygen.triggerImageRebuild(); + } + + @Subscribe + public void on(AirSaturation msg) { + if (!getConsole().getMainSystem().equalsIgnoreCase(msg.getSourceName())) { + return; + } + String entity = EntitiesResolver.resolveName(msg.getSourceName(), (int) msg.getSrcEnt()); + if (entity != null && !validAirSaturationDataSources.contains(entity)) { + return; + } + extractSensorMeasurementValueFromMessage(msg.getValue(), pointsAirSaturation, msg); + offScreenAirSaturation.triggerImageRebuild(); + } + + @Subscribe + public void on(CurrentProfile msg) { + if (!getConsole().getMainSystem().equalsIgnoreCase(msg.getSourceName())) { + return; + } + String entity = EntitiesResolver.resolveName(msg.getSourceName(), (int) msg.getSrcEnt()); + if (entity != null && !validWaterCurrentDataSources.contains(entity)) { + return; + } + + LocationType loc = getSystemLocation(); + double depth = getSystemLocationDepth(); + + switch (msg.getCoordSys()) { + case CurrentProfile.UTF_BEAMS: + NeptusLog.pub().warn("BEAMS not supported yet."); + return; + case CurrentProfile.UTF_XYZ: // TODO FIXME, now it is the same as UTF_ENU + case CurrentProfile.UTF_NED: + // case CurrentProfile.UTF_ENU: // TODO FIXME + default: + break; + } + + short nBeams = msg.getNbeams(); + short nCells = msg.getNcells(); + + if (nBeams != 3 && nBeams != 4) { + NeptusLog.pub().warn("Only 3 or 4 beams supported."); + return; + } + + if (msg.getProfile().size() != nCells) { + NeptusLog.pub().warn("Number of cells does not match the profile size."); + return; + } + + Vector profile = msg.getProfile(); + for (int i = 0; i < profile.size(); i++) { + CurrentProfileCell cpcell = profile.get(i); + List points = pointsWaterCurrent; + + double measureDepth = cpcell.getCellPosition() + (depth < 0 ? 0 : depth); + + DataPointPolar pt = new DataPointPolar(); + pt.location = loc; + Triple valueSDQ = calcWaterSpeedAndDirectionFromBeams(nBeams, cpcell); + if (!Double.isNaN(valueSDQ.getLeft()) && !Double.isNaN(valueSDQ.getMiddle()) && !Double.isNaN(valueSDQ.getRight())) { + pt.value = valueSDQ.getLeft(); + pt.directionRads = valueSDQ.getMiddle(); + pt.zDown = measureDepth; + points.add(pt); + } + } + + if (pointsWaterCurrent.size() > maxSamples) + pointsWaterCurrent.remove(0); + trigger.incrementAndGet(); + } + + private Triple calcWaterSpeedAndDirectionFromBeams(short nBeams, CurrentProfileCell cpcell) { + double vel = Double.NaN; + double dirRads = Double.NaN; + double z = Double.NaN; + + if (nBeams == 3 || nBeams == 4) { + vel = Math.sqrt(Math.pow(cpcell.getBeams().get(0).getVel(), 2) + Math.pow(cpcell.getBeams().get(1).getVel(), 2)); + // East, North + dirRads = AngleUtils.nomalizeAngleRads2Pi(AngleUtils.calcAngle(0, 0, cpcell.getBeams().get(0).getVel(), cpcell.getBeams().get(1).getVel())); + // Up to Down + z = -1 * cpcell.getBeams().get(2).getVel(); + } + if (nBeams == 4) { + z = -1 * (cpcell.getBeams().get(2).getVel() + cpcell.getBeams().get(3).getVel()) / 2.0; + } + return Triple.of(vel, dirRads, z); + } + + @Periodic(value = 2_000) + private void update() { + triggerAllImagesRebuild(); + } + + /* (non-Javadoc) + * @see pt.lsts.neptus.console.ConsoleLayer#paint(java.awt.Graphics2D, pt.lsts.neptus.renderer2d.StateRenderer2D) + */ + @Override + public void paint(Graphics2D g, StateRenderer2D renderer) { + super.paint(g, renderer); + + // Wee need all tests to run to recreate the image cache and not just the first one that is true + boolean recreateImage = offScreenSalinity.paintPhaseStartTestRecreateImageAndRecreate(g, renderer); + recreateImage = offScreenTemperature.paintPhaseStartTestRecreateImageAndRecreate(g, renderer) || recreateImage; + recreateImage = offScreenTurbidity.paintPhaseStartTestRecreateImageAndRecreate(g, renderer) || recreateImage; + recreateImage = offScreenChlorophyll.paintPhaseStartTestRecreateImageAndRecreate(g, renderer) || recreateImage; + recreateImage = offScreenDissolvedOrganicMatter.paintPhaseStartTestRecreateImageAndRecreate(g, renderer) || recreateImage; + recreateImage = offScreenDissolvedOxygen.paintPhaseStartTestRecreateImageAndRecreate(g, renderer) || recreateImage; + recreateImage = offScreenAirSaturation.paintPhaseStartTestRecreateImageAndRecreate(g, renderer) || recreateImage; + recreateImage = offScreenWaterCurrent.paintPhaseStartTestRecreateImageAndRecreate(g, renderer) || recreateImage; + + int c = trigger.get(); + if (c != triggerCount) { + recreateImage = true; + triggerCount = c; + } + + if (recreateImage) { + if (painterThread != null) { + try { + abortIndicator.set(true); + painterThread.interrupt(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + final MapTileRendererCalculator rendererCalculator = new MapTileRendererCalculator(renderer); + abortIndicator = new AtomicBoolean(); + painterThread = new Thread(new Runnable() { + @Override + public void run() { + Graphics2D g2Temp = offScreenTemperature.getImageGraphics(); + Graphics2D g2Salinity = offScreenSalinity.getImageGraphics(); + Graphics2D g2Turbidity = offScreenTurbidity.getImageGraphics(); + Graphics2D g2Chlorophyll = offScreenChlorophyll.getImageGraphics(); + Graphics2D g2DissolvedOrganicMatter = offScreenDissolvedOrganicMatter.getImageGraphics(); + Graphics2D g2DissolvedOxygen = offScreenDissolvedOxygen.getImageGraphics(); + Graphics2D g2AirSaturation = offScreenAirSaturation.getImageGraphics(); + Graphics2D g2WaterCurrent = offScreenWaterCurrent.getImageGraphics(); + + try { + List ptsTemp = new ArrayList<>(); + List ptsSalinity = new ArrayList<>(); + List ptsTurbidity = new ArrayList<>(); + List ptsChlorophyll = new ArrayList<>(); + List ptsDissolvedOrganicMatter = new ArrayList<>(); + List ptsDissolvedOxygen = new ArrayList<>(); + List ptsAirSaturation = new ArrayList<>(); + List ptsWaterCurrent = new ArrayList<>(); + + if (showTemp) { + ptsTemp = transformDataPointsToXY(pointsTemp, rendererCalculator); + } + if (showSal) { + ptsSalinity = transformDataPointsToXY(pointsSalinity, rendererCalculator); + } + if (showTurbidity) { + ptsTurbidity = transformDataPointsToXY(pointsTurbidity, rendererCalculator); + } + if (showChlorophyll) { + ptsChlorophyll = transformDataPointsToXY(pointsChlorophyll, rendererCalculator); + } + if (showDissolvedOrganicMatter) { + ptsDissolvedOrganicMatter = transformDataPointsToXY(pointsDissolvedOrganicMatter, rendererCalculator); + } + if (showDissolvedOxygen) { + ptsDissolvedOxygen = transformDataPointsToXY(pointsDissolvedOxygen, rendererCalculator); + } + if (showAirSaturation) { + ptsAirSaturation = transformDataPointsToXY(pointsAirSaturation, rendererCalculator); + } + if (showWaterCurrent) { + ptsWaterCurrent = transformCurrentDataPointsToXYPolar(pointsWaterCurrent, waterCurrentDepth, waterCurrentDepthWindow, rendererCalculator); + } + + if (showTemp && !abortIndicator.get()) { + try { + recreateSensorDataCacheImage(ptsTemp, colormapTemp, minTemp, maxTemp, offScreenTemperature, g2Temp, rendererCalculator); + } + catch (Exception e) { + e.printStackTrace(); + offScreenTemperature.triggerImageRebuild(); + } + } + + if (showSal && !abortIndicator.get()) { + try { + recreateSensorDataCacheImage(ptsSalinity, colormapSal, minSal, maxSal, offScreenSalinity, g2Salinity, rendererCalculator); + } + catch (Exception e) { + e.printStackTrace(); + offScreenSalinity.triggerImageRebuild(); + } + } + + if (showTurbidity && !abortIndicator.get()) { + try { + recreateSensorDataCacheImage(ptsTurbidity, colormapTurbidity, minTurbidity, maxTurbidity, offScreenTurbidity, g2Turbidity, rendererCalculator); + } + catch (Exception e) { + e.printStackTrace(); + offScreenTurbidity.triggerImageRebuild(); + } + } + + if (showChlorophyll && !abortIndicator.get()) { + try { + recreateSensorDataCacheImage(ptsChlorophyll, colormapChlorophyll, minChlorophyll, maxChlorophyll, offScreenChlorophyll, g2Chlorophyll, rendererCalculator); + } + catch (Exception e) { + e.printStackTrace(); + offScreenChlorophyll.triggerImageRebuild(); + } + } + + if (showDissolvedOrganicMatter && !abortIndicator.get()) { + try { + recreateSensorDataCacheImage(ptsDissolvedOrganicMatter, colormapDissolvedOrganicMatter, minDissolvedOrganicMatter, maxDissolvedOrganicMatter, offScreenDissolvedOrganicMatter, g2DissolvedOrganicMatter, rendererCalculator); + } + catch (Exception e) { + e.printStackTrace(); + offScreenDissolvedOrganicMatter.triggerImageRebuild(); + } + } + + if (showDissolvedOxygen && !abortIndicator.get()) { + try { + recreateSensorDataCacheImage(ptsDissolvedOxygen, colormapDissolvedOxygen, minDissolvedOxygen, maxDissolvedOxygen, offScreenDissolvedOxygen, g2DissolvedOxygen, rendererCalculator); + } + catch (Exception e) { + e.printStackTrace(); + offScreenDissolvedOxygen.triggerImageRebuild(); + } + } + + if (showAirSaturation && !abortIndicator.get()) { + try { + recreateSensorDataCacheImage(ptsAirSaturation, colormapAirSaturation, minAirSaturation, maxAirSaturation, offScreenAirSaturation, g2AirSaturation, rendererCalculator); + } + catch (Exception e) { + e.printStackTrace(); + offScreenAirSaturation.triggerImageRebuild(); + } + } + + if (showWaterCurrent && !abortIndicator.get()) { + try { + recreateSensorDataCacheImage(Collections.unmodifiableList(ptsWaterCurrent), colormapWaterCurrent, minWaterCurrent, + maxWaterCurrent, offScreenWaterCurrent, g2WaterCurrent, rendererCalculator); + } + catch (Exception e) { + e.printStackTrace(); + offScreenWaterCurrent.triggerImageRebuild(); + } + } + + g2Temp.dispose(); + g2Salinity.dispose(); + g2Turbidity.dispose(); + g2Chlorophyll.dispose(); + g2DissolvedOrganicMatter.dispose(); + g2DissolvedOxygen.dispose(); + g2AirSaturation.dispose(); + g2WaterCurrent.dispose(); + } + catch (Exception | Error e) { + e.printStackTrace(); + triggerAllImagesRebuild(); + } + + renderer.invalidate(); + renderer.repaint(200); + } + }, CaravelDataPlotter.class.getSimpleName() + ":: Painter"); + painterThread.setDaemon(true); + painterThread.start(); + } + offScreenTemperature.paintPhaseEndFinishImageRecreateAndPaintImageCacheToRendererNoGraphicDispose(g, renderer); + offScreenSalinity.paintPhaseEndFinishImageRecreateAndPaintImageCacheToRendererNoGraphicDispose(g, renderer); + offScreenTurbidity.paintPhaseEndFinishImageRecreateAndPaintImageCacheToRendererNoGraphicDispose(g, renderer); + offScreenChlorophyll.paintPhaseEndFinishImageRecreateAndPaintImageCacheToRendererNoGraphicDispose(g, renderer); + offScreenDissolvedOrganicMatter.paintPhaseEndFinishImageRecreateAndPaintImageCacheToRendererNoGraphicDispose(g, renderer); + offScreenDissolvedOxygen.paintPhaseEndFinishImageRecreateAndPaintImageCacheToRendererNoGraphicDispose(g, renderer); + offScreenAirSaturation.paintPhaseEndFinishImageRecreateAndPaintImageCacheToRendererNoGraphicDispose(g, renderer); + offScreenWaterCurrent.paintPhaseEndFinishImageRecreateAndPaintImageCacheToRendererNoGraphicDispose(g, renderer); + + paintColorbars(g, renderer); + } + + private void triggerAllImagesRebuild() { + offScreenTemperature.triggerImageRebuild(); + offScreenSalinity.triggerImageRebuild(); + offScreenTurbidity.triggerImageRebuild(); + offScreenChlorophyll.triggerImageRebuild(); + offScreenDissolvedOrganicMatter.triggerImageRebuild(); + offScreenDissolvedOxygen.triggerImageRebuild(); + offScreenAirSaturation.triggerImageRebuild(); + offScreenWaterCurrent.triggerImageRebuild(); + } + + private void recreateSensorDataCacheImage(List pts, ColorMap colormap, double minVal, + double maxVal, OffScreenLayerImageControl offScreenImageControl, Graphics2D g2, + MapTileRendererCalculator rendererCalculator) { + double fullImgWidth = rendererCalculator.getSize().getWidth() + offScreenImageControl.getOffScreenBufferPixel() * 2.; + double fullImgHeight = rendererCalculator.getSize().getHeight() + offScreenImageControl.getOffScreenBufferPixel() * 2.; + + double xMin = fullImgWidth; + double yMin = fullImgHeight; + xMin = 8; + yMin = 8; + + double cacheImgScaleX = 1. / xMin; + double cacheImgScaleY = 1. / yMin; + + double cacheImgWidth = fullImgWidth; + double cacheImgHeight = fullImgHeight; + cacheImgWidth *= cacheImgScaleX; + cacheImgHeight *= cacheImgScaleY; + + BufferedImage cacheImg = createBufferedImage((int) cacheImgWidth, (int) cacheImgHeight, Transparency.TRANSLUCENT); + boolean useArrows = false; + if (!pts.isEmpty()) { + DataPointXY pt0 = pts.get(0); + if (pt0 instanceof DataPointXYPolar) { + useArrows = true; + } + } + pts.parallelStream().forEach(getDataPointXYSensorPainter(colormap, minVal, maxVal, offScreenImageControl, + cacheImg, cacheImgScaleX, cacheImgScaleY, rendererCalculator, useArrows)); + + Graphics2D gt = (Graphics2D) g2.create(); + try { + gt.translate(rendererCalculator.getWidth() / 2., rendererCalculator.getHeight() / 2.); + gt.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_DEFAULT); + gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_DEFAULT); + gt.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + gt.drawImage(cacheImg, -(int) (cacheImg.getWidth() / cacheImgScaleX / 2.), + -(int) (cacheImg.getHeight() / cacheImgScaleY / 2.), + (int) (cacheImg.getWidth() / cacheImgScaleX), + (int) (cacheImg.getHeight() / cacheImgScaleY), null, null); + } + catch (Exception e) { + NeptusLog.pub().trace(e); + } + if (gt != null) + gt.dispose(); + } + + private Consumer getDataPointXYSensorPainter(ColorMap colormap, double minVal, double maxVal, + OffScreenLayerImageControl offScreenImageControl, + BufferedImage cacheImg, double cacheImgScaleX, double cacheImgScaleY, + MapTileRendererCalculator rendererCalculator, boolean useArrows) { + return pt -> { + try { + if (abortIndicator.get()) + return; + + double v = (double) pt.value; + + if (clampToFit + && (Double.compare(v, minVal) < 0 || Double.compare(v, maxVal) > 0)) + return; + + Color color = colormap.getColor(ColorMapUtils.getColorIndexZeroToOneLog10(v, minVal, maxVal)); + if (useArrows && false) { + try { + DataPointXYPolar ptPloar = (DataPointXYPolar) pt; + Graphics2D g2 = (Graphics2D) offScreenImageControl.getImageGraphics(); + g2.setColor(color); + g2.translate((ptPloar.point.getX() + offScreenImageControl.getOffScreenBufferPixel()), + (ptPloar.point.getY() + offScreenImageControl.getOffScreenBufferPixel())); + double rot = ptPloar.directionRads + Math.PI / 2. - rendererCalculator.getRotation(); + g2.rotate(rot); + g2.fill(arrow); + g2.rotate(-rot); + } catch (Exception e) { + //NeptusLog.pub().trace(e); + } + } else { + cacheImg.setRGB((int) ((pt.point.getX() + offScreenImageControl.getOffScreenBufferPixel()) * cacheImgScaleX), + (int) ((pt.point.getY() + offScreenImageControl.getOffScreenBufferPixel()) * cacheImgScaleY), color.getRGB()); + } + } + catch (Exception e) { + NeptusLog.pub().trace(e); + } + }; + } + + private List transformDataPointsToXY(List points, MapTileRendererCalculator rendererCalculator) { + return points.stream().collect(ArrayList::new, + (r, p) -> { + if (abortIndicator.get()) { + return; + } + Point2D pxy = rendererCalculator.getScreenPosition(p.location); + DataPointXY dpxy = new DataPointXY(); + dpxy.point = pxy; + dpxy.value = p.value; + r.add(dpxy); + }, (r1, r2) -> { + for (DataPointXY d2 : r2) { + if (abortIndicator.get()) { + break; + } + boolean found = false; + for (DataPointXY d1 : r1) { + if (abortIndicator.get()) { + break; + } + if (d2.point.equals(d1.point)) { + d1.value = (d1.value + d2.value) / 2.; + found = true; + break; + } + } + if (!found) { + r1.add(d2); + } + } + }); + } + + private List transformCurrentDataPointsToXYPolar(List points, + double waterCurrentDepth, int waterCurrentDepthWindow, + MapTileRendererCalculator rendererCalculator) { + final double minDepth = waterCurrentDepth - waterCurrentDepthWindow; + final double maxDepth = waterCurrentDepth + waterCurrentDepthWindow; + return points.stream().filter(dp -> dp.zDown >= minDepth && dp.zDown <= maxDepth) + .collect(ArrayList::new, + (r, p) -> { + if (abortIndicator.get()) { + return; + } + Point2D pxy = rendererCalculator.getScreenPosition(p.location); + DataPointXYPolar dpxy = new DataPointXYPolar(); + dpxy.point = pxy; + dpxy.value = p.value; + dpxy.directionRads = p.directionRads; + dpxy.zDown = p.zDown; + r.add(dpxy); + }, (r1, r2) -> { + for (DataPointXYPolar d2 : r2) { + if (abortIndicator.get()) { + break; + } + boolean found = false; + for (DataPointXYPolar d1 : r1) { + if (abortIndicator.get()) { + break; + } + if (d2.point.equals(d1.point)) { + d1.value = (d1.value + d2.value) / 2.; + d1.directionRads = (d1.directionRads + d2.directionRads) / 2.; + d1.zDown = (d1.zDown + d2.zDown) / 2.; + found = true; + break; + } + } + if (!found) { + r1.add(d2); + } + } + }); + } + + private static BufferedImage createBufferedImage(int cacheImgWidth, int cacheImgHeight, int translucent) { + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice gs = ge.getDefaultScreenDevice(); + GraphicsConfiguration gc = gs.getDefaultConfiguration(); + return gc.createCompatibleImage((int) cacheImgWidth , (int) cacheImgHeight , Transparency.TRANSLUCENT); + } + + private void paintColorbars(Graphics2D go, StateRenderer2D renderer) { + int offsetHeight = 130 * 3; + int offsetWidth = 5; + int offsetDelta = 130; + + if (showTemp) { + Graphics2D gl = (Graphics2D) go.create(); + gl.translate(offsetWidth, offsetHeight); + ColorBarPainterUtil.paintColorBar(gl, colormapTemp, "Temperature", "ºC", minTemp, maxTemp); + gl.dispose(); + offsetHeight += offsetDelta; + } else if (showSal) { + Graphics2D gl = (Graphics2D) go.create(); + gl.translate(offsetWidth, offsetHeight); + ColorBarPainterUtil.paintColorBar(gl, colormapSal, "Salinity", "PSU", minSal, maxSal); + gl.dispose(); + offsetHeight += offsetDelta; + } else if (showTurbidity) { + Graphics2D gl = (Graphics2D) go.create(); + gl.translate(offsetWidth, offsetHeight); + ColorBarPainterUtil.paintColorBar(gl, colormapTurbidity, "Turbidity", "NTU", minTurbidity, maxTurbidity); + gl.dispose(); + offsetHeight += offsetDelta; + } else if (showChlorophyll) { + Graphics2D gl = (Graphics2D) go.create(); + gl.translate(offsetWidth, offsetHeight); + ColorBarPainterUtil.paintColorBar(gl, colormapChlorophyll, "Chlorophyll", "µg/L", minChlorophyll, maxChlorophyll); + gl.dispose(); + offsetHeight += offsetDelta; + } else if (showDissolvedOrganicMatter) { + Graphics2D gl = (Graphics2D) go.create(); + gl.translate(offsetWidth, offsetHeight); + ColorBarPainterUtil.paintColorBar(gl, colormapDissolvedOrganicMatter, "Dissolved Organic Matter", "PPB", minDissolvedOrganicMatter, maxDissolvedOrganicMatter); + gl.dispose(); + offsetHeight += offsetDelta; + } else if (showDissolvedOxygen) { + Graphics2D gl = (Graphics2D) go.create(); + gl.translate(offsetWidth, offsetHeight); + ColorBarPainterUtil.paintColorBar(gl, colormapDissolvedOxygen, "Dissolved Oxygen", "µM", minDissolvedOxygen, maxDissolvedOxygen); + gl.dispose(); + offsetHeight += offsetDelta; + } else if (showAirSaturation) { + Graphics2D gl = (Graphics2D) go.create(); + gl.translate(offsetWidth, offsetHeight); + ColorBarPainterUtil.paintColorBar(gl, colormapAirSaturation, "Air Saturation", "%", minAirSaturation, maxAirSaturation); + gl.dispose(); + offsetHeight += offsetDelta; + } else if (showWaterCurrent) { + Graphics2D gl = (Graphics2D) go.create(); + gl.translate(offsetWidth, offsetHeight); + String txtName = String.format("Water Current [%.2f ±%d]", waterCurrentDepth, waterCurrentDepthWindow); + ColorBarPainterUtil.paintColorBar(gl, colormapWaterCurrent, txtName, "m/s", minWaterCurrent, maxWaterCurrent); + gl.dispose(); + offsetHeight += offsetDelta; + } + } + + /** + * @param go + * @param renderer + */ + private void paintColorbars(Graphics2D go, StateRenderer2D renderer, ColorMap colormap, String name, + String unit, double minValue, double maxValue) { + Graphics2D gl = (Graphics2D) go.create(); + ColorBarPainterUtil.paintColorBar(gl, colormap, name, unit, minValue, maxValue); + gl.dispose(); + } + + /* (non-Javadoc) + * @see pt.lsts.neptus.util.conf.PreferencesListener#preferencesUpdated() + */ + @Override + public void preferencesUpdated() { + triggerAllImagesRebuild(); + getConsole().repaint(); + } +} \ No newline at end of file diff --git a/plugins-dev/oplimits/src/java/pt/lsts/neptus/plugins/oplimits/OperationLimitsSubPanel.java b/plugins-dev/oplimits/src/java/pt/lsts/neptus/plugins/oplimits/OperationLimitsSubPanel.java index a605006204..2f96774908 100644 --- a/plugins-dev/oplimits/src/java/pt/lsts/neptus/plugins/oplimits/OperationLimitsSubPanel.java +++ b/plugins-dev/oplimits/src/java/pt/lsts/neptus/plugins/oplimits/OperationLimitsSubPanel.java @@ -69,6 +69,7 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; +import javax.swing.SwingWorker; import javax.swing.border.EmptyBorder; import com.google.common.eventbus.Subscribe; @@ -251,10 +252,17 @@ public void actionPerformed(ActionEvent e) { synchronized (OperationLimitsSubPanel.this) { lastMD5 = msg.payloadMD5(); - boolean ret = send(msg); - if (ret) { - send(new GetOperationalLimits()); - } + SwingWorker worker = new SwingWorker() { + @Override + protected Boolean doInBackground() throws Exception { + boolean ret = send(msg); + if (ret) { + send(new GetOperationalLimits()); + } + return ret; + } + }; + worker.execute(); } } }; @@ -273,7 +281,13 @@ public void actionPerformed(ActionEvent e) { updateAction.putValue(AbstractAction.SMALL_ICON, ICON_UPDATE_REQUEST); } updateAction.putValue(AbstractAction.SHORT_DESCRIPTION, TEXT_REQUEST_RESPONSE_WAITING); - send(IMCDefinition.getInstance().create("GetOperationalLimits")); + SwingWorker worker = new SwingWorker() { + @Override + protected Boolean doInBackground() throws Exception { + return send(IMCDefinition.getInstance().create("GetOperationalLimits")); + } + }; + worker.execute(); } }; @@ -365,8 +379,7 @@ public boolean send(IMCMessage message) { if (userAproveRequest) { lastRequest = new Date(); } - sendViaIridium(destination, message); - return true; + return sendViaIridium(destination, message); } else { return false; } diff --git a/plugins-dev/remote-actions-extra/src/java/pt/lsts/neptus/plugins/remoteactionsextra/RemoteActionsExtra.java b/plugins-dev/remote-actions-extra/src/java/pt/lsts/neptus/plugins/remoteactionsextra/RemoteActionsExtra.java index 101a74496c..5a7ee3eaab 100644 --- a/plugins-dev/remote-actions-extra/src/java/pt/lsts/neptus/plugins/remoteactionsextra/RemoteActionsExtra.java +++ b/plugins-dev/remote-actions-extra/src/java/pt/lsts/neptus/plugins/remoteactionsextra/RemoteActionsExtra.java @@ -35,6 +35,7 @@ import com.google.common.eventbus.Subscribe; import net.miginfocom.swing.MigLayout; import pt.lsts.imc.EntityState; +import pt.lsts.imc.IMCMessage; import pt.lsts.imc.RemoteActions; import pt.lsts.imc.RemoteActionsRequest; import pt.lsts.imc.VehicleState; @@ -50,14 +51,19 @@ import pt.lsts.neptus.plugins.Popup; import pt.lsts.neptus.plugins.update.Periodic; import pt.lsts.neptus.util.MathMiscUtils; +import pt.lsts.neptus.util.PropertiesLoader; +import pt.lsts.neptus.util.conf.ConfigFetch; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.SwingConstants; import java.awt.event.KeyEvent; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -70,6 +76,7 @@ description = "This plugin listen for non motion related remote actions and displays its controls.") @Popup(name = "Remote Actions Extra", width = 300, height = 200, pos = Popup.POSITION.BOTTOM, accelerator = KeyEvent.VK_3) public class RemoteActionsExtra extends ConsolePanel implements MainVehicleChangeListener, ConfigurationListener { + public static final String REMOTE_ACTIONS_EXTRA_PROPERTIES_FILE = ".cache/db/remote-actions-extra.properties"; static final boolean DEFAULT_AXIS_DECIMAL_VAL = false; private static final int DECIMAL_HOUSES_FOR_DECIMAL_AXIS = 6; @@ -129,6 +136,44 @@ enum ActionTypeEnum { private final TakeControlMonitor takeControlMonitor; + private static PropertiesLoader properties = null; + static { + String propertiesFile = ConfigFetch.resolvePathBasedOnConfigFile(REMOTE_ACTIONS_EXTRA_PROPERTIES_FILE); + if (!new File(propertiesFile).exists()) { + String testFile = ConfigFetch.resolvePathBasedOnConfigFile("../" + REMOTE_ACTIONS_EXTRA_PROPERTIES_FILE); + if (new File(testFile).exists()) + propertiesFile = testFile; + } + new File(propertiesFile).getParentFile().mkdirs(); + properties = new PropertiesLoader(propertiesFile, PropertiesLoader.PROPERTIES); + + Enumeration it = properties.keys(); + while (it.hasMoreElements()) { + String key = it.nextElement().toString(); + String value = properties.getProperty(key); + setRemoteActionsExtra(key, value, false); + } + } + + private static void saveProperties() { + try { + properties.store("RemoteActionsExtra properties"); + } + catch (IOException e) { + NeptusLog.pub().error("saveProperties", e); + } + } + + private static void setRemoteActionsExtra(String system, String actionsStr, boolean save) { + if (save) { + String old = properties.getProperty(system); + if (old == null || !old.equals(actionsStr)) { + properties.setProperty(system, actionsStr); + saveProperties(); + } + } + } + @NeptusProperty(name = "OBS Entity Name", userLevel = NeptusProperty.LEVEL.ADVANCED, description = "Used to check the state of the OBS take control status.") public String obsEntityName = "OBS Broker"; @@ -145,6 +190,7 @@ public RemoteActionsExtra(ConsoleLayout console, boolean usedInsideAnotherConsol @Override public void initSubPanel() { + updateForMainSystems(); resetUIWithActions(); } @@ -165,64 +211,66 @@ private synchronized void resetUIWithActions() { removeAll(); setLayout(new MigLayout("insets 10px")); - if (extraActionsTypesMap.isEmpty()) { - add(new JLabel("No actions available", SwingConstants.CENTER), "dock center"); - invalidate(); - validate(); - repaint(100); - return; - } - - // Let us process the actions list - List> groupedActions = groupActionsBySimilarity(extraActionsTypesMap.keySet(), true); - groupedActions = processActions(groupedActions, 2, false); - - int grpIdx = 0; - for (List grp1 : groupedActions) { - grpIdx++; - String lastAct = grp1.get(grp1.size() - 1); - for (String action : grp1) { - String wrapLay = ""; - if (lastAct.equals(action)) { - wrapLay = "wrap"; - } + synchronized (extraActionsTypesMap) { + if (extraActionsTypesMap.isEmpty()) { + add(new JLabel("No actions available", SwingConstants.CENTER), "dock center"); + invalidate(); + validate(); + repaint(100); + return; + } - boolean provideLock = false; - if (curState.extraActionsLocksMap.containsKey(action)) { - Boolean v = curState.extraActionsLocksMap.get(action); - if (v != null && v) { - provideLock = true; + // Let us process the actions list + List> groupedActions = groupActionsBySimilarity(extraActionsTypesMap.keySet(), true); + groupedActions = processActions(groupedActions, 2, false); + + int grpIdx = 0; + for (List grp1 : groupedActions) { + grpIdx++; + String lastAct = grp1.get(grp1.size() - 1); + for (String action : grp1) { + String wrapLay = ""; + if (lastAct.equals(action)) { + wrapLay = "wrap"; } - } - switch (extraActionsTypesMap.get(action)) { - case BUTTON: - boolean isToProvideLock = provideLock || isActionForLock(action); - JButton button = isToProvideLock ? new HoldFillButton(action, 2000) : new JButton(action); - button.addActionListener(e -> { - curState.changeButtonActionValue(action, 1); - }); - String lay = "dock center, sg grp" + grpIdx; - lay += ", " + wrapLay; - add(button, lay); - if (isToProvideLock) { - extraLockableButtons.add(button); + boolean provideLock = false; + if (curState.extraActionsLocksMap.containsKey(action)) { + Boolean v = curState.extraActionsLocksMap.get(action); + if (v != null && v) { + provideLock = true; } - if ("Take Control".equalsIgnoreCase(action)) { - takeControlMonitor.setButton(button); - takeControlMonitor.askedControl(); - } - break; - case AXIS: - // TODO - case SLIDER: - // TODO - case HALF_SLIDER: - // TODO - break; - } - } - } + } + + switch (extraActionsTypesMap.get(action)) { + case BUTTON: + boolean isToProvideLock = provideLock || isActionForLock(action); + JButton button = isToProvideLock ? new HoldFillButton(action, 2000) : new JButton(action); + button.addActionListener(e -> { + curState.changeButtonActionValue(action, 1); + }); + String lay = "dock center, sg grp" + grpIdx; + lay += ", " + wrapLay; + add(button, lay); + if (isToProvideLock) { + extraLockableButtons.add(button); + } + if ("Take Control".equalsIgnoreCase(action)) { + takeControlMonitor.setButton(button); + takeControlMonitor.askedControl(); + } + break; + case AXIS: + // TODO + case SLIDER: + // TODO + case HALF_SLIDER: + // TODO + break; + } + } + } + } invalidate(); validate(); @@ -232,9 +280,22 @@ private synchronized void resetUIWithActions() { @Subscribe public void on(ConsoleEventMainSystemChange evt) { configureActions("", DEFAULT_AXIS_DECIMAL_VAL, false); + updateForMainSystems(); takeControlMonitor.on(evt); } + private void updateForMainSystems() { + String actionsString = ""; + try { + if (properties.containsKey(getConsole().getMainSystem())) { + actionsString = (String) properties.get(getConsole().getMainSystem()); + } + } catch (Exception e) { + NeptusLog.pub().error(e.getMessage()); + } + configureActions(actionsString, DEFAULT_AXIS_DECIMAL_VAL, false); + } + @Subscribe public void on(RemoteActionsRequest msg) { if (!msg.getSourceName().equals(getMainVehicleId())) { @@ -243,7 +304,8 @@ public void on(RemoteActionsRequest msg) { if (msg.getOp() != RemoteActionsRequest.OP.REPORT) return; - configureActions(msg.getAsString("actions"), DEFAULT_AXIS_DECIMAL_VAL, false); + setRemoteActionsExtra(msg.getSourceName(), IMCMessage.encodeTupleList(msg.getActions()), true); + configureActions(msg.getActions(), DEFAULT_AXIS_DECIMAL_VAL, false); } @Subscribe diff --git a/plugins-dev/sunfish/src/java/pt/lsts/neptus/plugins/sunfish/iridium/feedback/IridiumStatus.java b/plugins-dev/sunfish/src/java/pt/lsts/neptus/plugins/sunfish/iridium/feedback/IridiumStatus.java index f3eae25dc6..a4d59c6c93 100644 --- a/plugins-dev/sunfish/src/java/pt/lsts/neptus/plugins/sunfish/iridium/feedback/IridiumStatus.java +++ b/plugins-dev/sunfish/src/java/pt/lsts/neptus/plugins/sunfish/iridium/feedback/IridiumStatus.java @@ -187,6 +187,16 @@ public void run() { @Override public int compare(String sdf1, String sdf2) { + if (sdf1 == null && sdf2 == null) { + return 0; + } + else if (sdf1 == null) { + return -1; + } + else if (sdf2 == null) { + return 1; + } + SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS dd-MM-yyyy 'Z'"); sdf1 = sdf1.replaceAll("V ", ""); sdf2 = sdf2.replaceAll("V ", ""); diff --git a/plugins-dev/sunfish/src/java/pt/lsts/neptus/plugins/sunfish/iridium/feedback/IridiumStatusTableModel.java b/plugins-dev/sunfish/src/java/pt/lsts/neptus/plugins/sunfish/iridium/feedback/IridiumStatusTableModel.java index c49df4e1a4..57667c3e8d 100644 --- a/plugins-dev/sunfish/src/java/pt/lsts/neptus/plugins/sunfish/iridium/feedback/IridiumStatusTableModel.java +++ b/plugins-dev/sunfish/src/java/pt/lsts/neptus/plugins/sunfish/iridium/feedback/IridiumStatusTableModel.java @@ -232,57 +232,71 @@ public int getColumnCount() { @Override public Object getValueAt(int rowIndex, int columnIndex) { + try { + IridiumMessage m = msgs.get(rowIndex); - IridiumMessage m = msgs.get(rowIndex); - - String messageType = m.getMessageType() == 0 ? "Custom Iridium Message" : m.getClass().getSimpleName(); - int src = m.getSource(); - int dst = m.getDestination(); + String messageType = m.getMessageType() == 0 ? "Custom Iridium Message" : m.getClass().getSimpleName(); + int src = m.getSource(); + int dst = m.getDestination(); - switch (columnIndex) { - case TIMESTAMP: - { - if(messageType.equalsIgnoreCase("ImcIridiumMessage")) { - IMCMessage msg = ((ImcIridiumMessage) m).getMsg(); - if(msg.getMgid() == StateReport.ID_STATIC) { - long stime = ((StateReport) msg).getStime() * 1000; - TimeZone.getTimeZone(TimeZone.getDefault().getID()); - StringBuilder sb = new StringBuilder("V "); - sb.append(sdf.format(new Date(stime))); + switch (columnIndex) { + case TIMESTAMP: { + try { + if (messageType.equalsIgnoreCase("ImcIridiumMessage") || + messageType.equalsIgnoreCase("ImcFullIridiumMessage")) { + IMCMessage msg = ((ImcIridiumMessage) m).getMsg(); + if (msg.getMgid() == StateReport.ID_STATIC) { + long stime = ((StateReport) msg).getStime() * 1000; + TimeZone.getTimeZone(TimeZone.getDefault().getID()); + StringBuilder sb = new StringBuilder("V "); + sb.append(sdf.format(new Date(stime))); + return sb.toString(); + } + } + StringBuilder sb = new StringBuilder("M "); + sb.append(sdf.format(new Date(m.timestampMillis))); return sb.toString(); } + catch (Exception e) { + NeptusLog.pub().warn("?? " + messageType + " " + m + " :: " + e.getMessage()); + return "?? " + messageType + " " + m; + } } - StringBuilder sb = new StringBuilder("M "); - sb.append(sdf.format(new Date(m.timestampMillis))); - return sb.toString(); - } - case SYSTEM: - return IMCDefinition.getInstance().getResolver().resolve(src); - case STATUS: - if (status.containsKey(rowIndex)) { - return status.get(rowIndex)._status; - } - /* - * else if(src == ImcMsgManager.getManager().getLocalId().intValue()) return IridiumCommsStatus.SENT; - */ - if(messageType.equalsIgnoreCase("IridiumCommand")) { - IridiumCommand cmd = (IridiumCommand) m; - String txt = cmd.getCommand(); - if(txt.startsWith("ERROR")) - return IridiumCommsStatus.ERROR; + case SYSTEM: + return IMCDefinition.getInstance().getResolver().resolve(src); + case STATUS: + if (status.containsKey(rowIndex)) { + return status.get(rowIndex)._status; + } + /* + * else if(src == ImcMsgManager.getManager().getLocalId().intValue()) return IridiumCommsStatus.SENT; + */ + if (messageType.equalsIgnoreCase("IridiumCommand")) { + IridiumCommand cmd = (IridiumCommand) m; + String txt = cmd.getCommand(); + if (txt.startsWith("ERROR")) + return IridiumCommsStatus.ERROR; - } - else if (dst == 65535 || dst == ImcMsgManager.getManager().getLocalId().intValue()) { // dst - 65535 - 255 // - broadcast - return IridiumCommsStatus.DELIVERED; - } + } + else if (dst == 65535 || dst == ImcMsgManager.getManager().getLocalId().intValue()) { // dst - 65535 - 255 // - broadcast + return IridiumCommsStatus.DELIVERED; + } return IridiumCommsStatus.UNCERTAIN; - case MSG_TYPE: - if(messageType.equalsIgnoreCase("ImcIridiumMessage")) - return ((ImcIridiumMessage) m).getMsg().getClass().getSimpleName(); - else - return messageType; - default: - return "??"; + case MSG_TYPE: + try { + if (messageType.equalsIgnoreCase("ImcIridiumMessage") || + messageType.equalsIgnoreCase("ImcFullIridiumMessage")) + return ((ImcIridiumMessage) m).getMsg().getClass().getSimpleName(); + else + return messageType; + } catch (Exception e) { + return "::" + messageType; + } + default: + return "??"; + } + } catch (Exception e) { + return "?? " + rowIndex + " " + columnIndex + " " + e.getMessage(); } } @@ -319,13 +333,15 @@ else if(date.startsWith("M ")) { * @param milis - milliseconds */ public void cleanupAfter(long millis) { - msgs.removeIf(msg -> (System.currentTimeMillis() - msg.timestampMillis) > millis); + synchronized (msgs) { + msgs.removeIf(msg -> (System.currentTimeMillis() - msg.timestampMillis) > millis); + } fireTableDataChanged(); } } /** - * Iridium Local Transmissions Status + * Iridium Local Transmissions StatusTDefaultRowSorter * */ class TransmissionStatus { diff --git a/src/java/pt/lsts/neptus/comm/IMCSendMessageUtils.java b/src/java/pt/lsts/neptus/comm/IMCSendMessageUtils.java index 4dfb29a1fd..793e2ba015 100644 --- a/src/java/pt/lsts/neptus/comm/IMCSendMessageUtils.java +++ b/src/java/pt/lsts/neptus/comm/IMCSendMessageUtils.java @@ -60,6 +60,7 @@ import pt.lsts.neptus.types.vehicle.VehicleType.SystemTypeEnum; import pt.lsts.neptus.util.GuiUtils; import pt.lsts.neptus.util.StringUtils; +import pt.lsts.neptus.util.conf.GeneralPreferences; /** * @author pdias @@ -121,6 +122,20 @@ public static boolean sendMessage(IMCMessage msg, Component parent, String error acousticOpUseOnlyActive, acousticOpUserAprovedQuestion, sendOnlyThroughOneAcoustically, ids); } + public static boolean sendMessage(IMCMessage msg, String sendProperties, MessageDeliveryListener listener, Component parent, String errorTextForDialog, + boolean ignoreAcousticSending, String acousticOpServiceName, boolean acousticOpUseOnlyActive, + boolean acousticOpUserApprovedQuestion, boolean sendOnlyThroughOneAcoustically, String... ids) { + return sendMessage(msg, sendProperties, listener, parent, errorTextForDialog, ignoreAcousticSending, acousticOpServiceName, + acousticOpUseOnlyActive, acousticOpUserApprovedQuestion, sendOnlyThroughOneAcoustically, true, new String[0], ids); + } + + public static boolean sendMessage(IMCMessage msg, String sendProperties, MessageDeliveryListener listener, Component parent, String errorTextForDialog, + boolean ignoreAcousticSending, String acousticOpServiceName, boolean acousticOpUseOnlyActive, + boolean acousticOpUserApprovedQuestion, boolean sendOnlyThroughOneAcoustically, boolean popGuiOnError, String... ids) { + return sendMessage(msg, sendProperties, listener, parent, errorTextForDialog, ignoreAcousticSending, acousticOpServiceName, + acousticOpUseOnlyActive, acousticOpUserApprovedQuestion, sendOnlyThroughOneAcoustically, popGuiOnError, new String[0], ids); + } + /** * @param msg * @param sendProperties The same of {@link ImcMsgManager#sendMessage(IMCMessage, pt.lsts.neptus.comm.manager.imc.ImcId16, String, pt.lsts.neptus.comm.manager.imc.MessageDeliveryListener)}, @@ -128,27 +143,34 @@ public static boolean sendMessage(IMCMessage msg, Component parent, String error * @param parent The parent component for popup error message * @param errorTextForDialog * @param ignoreAcousticSending If this is true mean don't use acoustic. - * @param acousticOpServiceName + * @param acousticOpServiceName * @param acousticOpUseOnlyActive - * @param acousticOpUserAprovedQuestion + * @param acousticOpUserApprovedQuestion * @param ids * @return */ public static boolean sendMessage(IMCMessage msg, String sendProperties, MessageDeliveryListener listener, Component parent, String errorTextForDialog, boolean ignoreAcousticSending, String acousticOpServiceName, boolean acousticOpUseOnlyActive, - boolean acousticOpUserAprovedQuestion, boolean sendOnlyThroughOneAcoustically, String... ids) { + boolean acousticOpUserApprovedQuestion, boolean sendOnlyThroughOneAcoustically, boolean popGuiOnError, String[] channelsToSend, String... ids) { ImcSystem[] acousticOpSysLst = !ignoreAcousticSending ? ImcSystemsHolder.lookupSystemByService( acousticOpServiceName, SystemTypeEnum.ALL, acousticOpUseOnlyActive) : new ImcSystem[0]; - boolean acousticOpUserAprovalRequired = acousticOpUserAprovedQuestion; - boolean acousticOpUserAproved = !acousticOpUserAprovedQuestion; + boolean acousticOpUserAprovalRequired = acousticOpUserApprovedQuestion; + boolean acousticOpUserAproved = !acousticOpUserApprovedQuestion; boolean retAll = true; for (String sid : ids) { boolean ret; ImcSystem sysL = ImcSystemsHolder.lookupSystemByName(sid); - if (acousticOpSysLst.length != 0 && sysL != null && !sysL.isActive()) { + if (GeneralPreferences.imcUseNewMultiChannelCommsEnable && sysL != null && !sysL.isActive()) { + ret = ImcMsgManager.getManager().sendMessageUsingActiveChannelWait(msg, sid, -1, parent, + acousticOpUserApprovedQuestion, channelsToSend); + if (ret) { + acousticOpUserAproved = true; + } + } + else if (acousticOpSysLst.length != 0 && sysL != null && !sysL.isActive()) { if (acousticOpUserAprovalRequired) { acousticOpUserAproved = (GuiUtils.confirmDialog(parent, I18n.text("Send by Acoustic Modem"), I18n.text("Some systems are not active. Do you want to send by acoustic modem?")) == JOptionPane.YES_OPTION); @@ -164,11 +186,12 @@ public static boolean sendMessage(IMCMessage msg, String sendProperties, Message } retAll = retAll && ret; if (!ret) { - if (parent instanceof ConsolePanel) { + NeptusLog.pub().warn(errorTextForDialog); + if (popGuiOnError && parent instanceof ConsolePanel) { ((ConsolePanel) parent).post(Notification.error(I18n.text("Send Message"), errorTextForDialog).src( I18n.text("Console"))); } - else { + else if (popGuiOnError && parent != null) { GuiUtils.errorMessage(parent, I18n.text("Send Message"), errorTextForDialog); } } diff --git a/src/java/pt/lsts/neptus/comm/admin/CommsAdmin.java b/src/java/pt/lsts/neptus/comm/admin/CommsAdmin.java new file mode 100644 index 0000000000..bf741a5e39 --- /dev/null +++ b/src/java/pt/lsts/neptus/comm/admin/CommsAdmin.java @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2004-2025 Universidade do Porto - Faculdade de Engenharia + * Laboratório de Sistemas e Tecnologia Subaquática (LSTS) + * All rights reserved. + * Rua Dr. Roberto Frias s/n, sala I203, 4200-465 Porto, Portugal + * + * This file is part of Neptus, Command and Control Framework. + * + * Commercial Licence Usage + * Licencees holding valid commercial Neptus licences may use this file + * in accordance with the commercial licence agreement provided with the + * Software or, alternatively, in accordance with the terms contained in a + * written agreement between you and Universidade do Porto. For licensing + * terms, conditions, and further information contact lsts@fe.up.pt. + * + * Modified European Union Public Licence - EUPL v.1.1 Usage + * Alternatively, this file may be used under the terms of the Modified EUPL, + * Version 1.1 only (the "Licence"), appearing in the file LICENSE.md + * included in the packaging of this file. You may not use this work + * except in compliance with the Licence. Unless required by applicable + * law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the Licence for the specific + * language governing permissions and limitations at + * https://github.com/LSTS/neptus/blob/develop/LICENSE.md + * and http://ec.europa.eu/idabc/eupl.html. + * + * For more information please see . + * + * Author: Paulo Dias + * 16/4/2024 + */ +package pt.lsts.neptus.comm.admin; + +import pt.lsts.imc.IMCDefinition; +import pt.lsts.imc.IMCMessage; +import pt.lsts.imc.MessagePart; +import pt.lsts.imc.TransmissionRequest; +import pt.lsts.imc.net.IMCFragmentHandler; +import pt.lsts.neptus.NeptusLog; +import pt.lsts.neptus.comm.IMCSendMessageUtils; +import pt.lsts.neptus.comm.iridium.ImcIridiumMessage; +import pt.lsts.neptus.comm.iridium.IridiumManager; +import pt.lsts.neptus.comm.iridium.UpdateDeviceActivation; +import pt.lsts.neptus.comm.manager.imc.ImcMsgManager; +import pt.lsts.neptus.comm.manager.imc.ImcSystem; +import pt.lsts.neptus.comm.manager.imc.ImcSystemsHolder; +import pt.lsts.neptus.comm.manager.imc.MessageDeliveryListener; +import pt.lsts.neptus.types.vehicle.VehicleType; +import pt.lsts.neptus.util.ByteUtil; +import pt.lsts.neptus.util.conf.GeneralPreferences; + +import java.awt.Component; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +public class CommsAdmin { + + public static final int COMM_TIMEOUT_MILLIS = 20000; + public static final int MAX_ACOMMS_PAYLOAD_SIZE = 998; + public static final double TIMEOUT_ACOMMS_SECS = 60; + private int minutesBetweenDeviceActivationSendMinutes = 5; + + public enum CommChannelType { + WIFI("WiFi", "Wi-Fi channel", "images/channels/wifi.png", + "images/channels/wifi_selected.png", "images/channels/wifi_disabled.png", true, false, false), + ACOUSTIC("Acoustic", "Acoustic channel", "images/channels/acoustic.png", + "images/channels/acoustic_selected.png", "images/channels/acoustic_disabled.png", true, false, false), + IRIDIUM("Iridium", "Iridium channel", "images/channels/iridium.png", + "images/channels/iridium_selected.png", "images/channels/iridium_disabled.png", true, false, false), + GSM("GSM", "GSM channel", "images/channels/gsm.png", + "images/channels/gsm_selected.png", "images/channels/gsm_disabled.png", true, false, false), + ; + + public final String name; + public final String description; + public final String icon; + public final String iconSelected; + public final String iconDisabled; + private boolean enabled; + private boolean active; + private boolean reliable; + + private CommChannelType(String name, String description, String icon, String iconSelected, + String iconDisabled, boolean enabled, boolean active, boolean reliable) { + this.name = name; + this.description = description; + this.icon = icon; + this.iconSelected = iconSelected; + this.iconDisabled = iconDisabled; + this.enabled = enabled; + this.active = active; + this.reliable = reliable; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public boolean isReliable() { + return reliable; + } + + public void setReliable(boolean reliable) { + this.reliable = reliable; + } + } // CommChannelType + + private final String acousticOpServiceName = "acoustic/operation"; + private final String iridiumOpServiceName = "iridium"; + + private ImcMsgManager imcMsgManager = null; + private List channels = new ArrayList<>(); + + private Map lastIridiumMessageSent = new HashMap<>(); + + public CommsAdmin(ImcMsgManager imcMsgManager) { + this.imcMsgManager = imcMsgManager; + + Collections.addAll(channels, CommChannelType.values()); + } + + //public static boolean sendMessage(IMCMessage msg, String sendProperties, MessageDeliveryListener listener, + // Component parent, String errorTextForDialog, String... destinationIds) { + + public Future sendMessage(IMCMessage message, String destinationName, int timeoutMillis, + Component parentComponentForAlert, + boolean requireUserConfirmOtherThanWifi, String... channelsToSend) { + final ResultWaiter waiter = new ResultWaiter(timeoutMillis); // COMM_TIMEOUT_MILLIS + FutureTask result = new FutureTask(waiter) { + private long start = System.currentTimeMillis(); + + @Override + public ImcMsgManager.SendResult get() throws InterruptedException, ExecutionException { + try { + return waiter.call(); + } catch (Exception e) { + throw new ExecutionException(e); + } + } + + @Override + public ImcMsgManager.SendResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, + TimeoutException { + long end = start + unit.toMillis(timeout); + while (System.currentTimeMillis() < end) { + if (waiter.result == null) { + Thread.sleep(100); + } else { + try { + return waiter.call(); + } catch (Exception e) { + throw new ExecutionException(e); + } + } + } + throw new TimeoutException("Time out exceeded"); + } + }; + + String channelsToUseConf = GeneralPreferences.imcChannelsToUse; + + //sendMessageToSystem(message, systemName, "TCP", waiter); + // Let us try to send the message to the destination + + // First check which channels are enabled here + List channelsToUse = new ArrayList<>(); + if (channelsToSend == null || channelsToSend.length == 0) { + channelsToUse.addAll(channels); + } else { + for (String channelName : channelsToSend) { + for (CommChannelType channel : channels) { + if (channel.name.equalsIgnoreCase(channelName)) { + channelsToUse.add(channel); + break; + } + } + } + } + + if (channelsToUse.isEmpty()) { + waiter.deliveryUnreacheable(message); + return result; + } + + final AtomicReference proxyAcousticSystem = new AtomicReference<>(); + final AtomicReference proxyGsmSystem = new AtomicReference<>(); + final AtomicReference proxyIridiumSystem = new AtomicReference<>(); + + ImcSystem system = ImcSystemsHolder.lookupSystemByName(destinationName); + + if (system == null) { + waiter.deliveryUnreacheable(message); + return result; + } + + // From the enabled channels, check which are active for the system + channelsToUse = channelsToUse.stream().filter(channel -> { + switch (channel) { + case WIFI: + if (system.isActive()) { + return true; + } + break; + case ACOUSTIC: + // Let's check if the system has this comm mean + + ImcSystem[] acousticOpSysLst = ImcSystemsHolder.lookupSystemByService( + acousticOpServiceName, VehicleType.SystemTypeEnum.ALL, true); + List canditatesList = Arrays.asList(acousticOpSysLst); + Collections.shuffle(canditatesList); + proxyAcousticSystem.set(canditatesList.stream().filter(sys -> IMCSendMessageUtils + .doesSystemWithAcousticCanReachSystem(sys, destinationName)) + .findFirst().orElse(null)); + if (proxyAcousticSystem.get() != null) { + return true; + } + break; + case IRIDIUM: + if (IridiumManager.getManager().isAvailable()) + return true; + break; + case GSM: + default: + break; + } + return false; + }).collect(Collectors.toList()); + + // We should now filter by the ones active for the system + + + // Now send the message to the first available channel + if (channelsToUse.isEmpty()) { + waiter.deliveryUnreacheable(message); + return result; + } + + // Try to check if the distance to vehícle is too far for the comm mean to be used + // **************************************************************************************** + + + // Send the message + for (CommChannelType channel : channelsToUse) { + switch (channel) { + case WIFI: + if (system.isActive()) { + imcMsgManager.sendMessage(message, system.getId(), null, waiter); + return result; + } + break; + case ACOUSTIC: + if (proxyAcousticSystem.get() != null && proxyAcousticSystem.get().isActive()) { + try { + ArrayList requests = new ArrayList(); + if (message.getPayloadSize() > MAX_ACOMMS_PAYLOAD_SIZE) { + IMCFragmentHandler handler = new IMCFragmentHandler(IMCDefinition.getInstance()); + + MessagePart[] parts = handler.fragment(message, MAX_ACOMMS_PAYLOAD_SIZE); + NeptusLog.pub().info("PlanDB message resulted in " + parts.length + " fragments"); + for (MessagePart part : parts) { + TransmissionRequest request = getAcousticTransmissionRequestForImcMessage(part, system); + requests.add(request); + } + } else { + TransmissionRequest request = getAcousticTransmissionRequestForImcMessage(message, system); + requests.add(request); + } + + for (TransmissionRequest request : requests) { + ImcMsgManager.getManager().sendMessageToSystem(request, proxyAcousticSystem.get().getName(), waiter); + } + + return result; + } catch (Exception e) { + NeptusLog.pub().error(this, e); + } + } + break; + case IRIDIUM: + sendDeviceActivationViaIridiumIfNeeded(destinationName); + sendViaIridium(destinationName, message, waiter); + return result; + case GSM: + default: + break; + } + } + + waiter.deliveryUnreacheable(message); + return result; + } + + private TransmissionRequest getAcousticTransmissionRequestForImcMessage(IMCMessage part, ImcSystem system) { + TransmissionRequest request = new TransmissionRequest(); + request.setCommMean(TransmissionRequest.COMM_MEAN.ACOUSTIC); + request.setReqId(Long.valueOf(imcMsgManager.getNextSeqInstanceNr()).intValue()); + request.setDataMode(TransmissionRequest.DATA_MODE.INLINEMSG); + request.setMsgData(part); + request.setDestination(system.getName()); + request.setDeadline(System.currentTimeMillis() / 1000.0 + TIMEOUT_ACOMMS_SECS); + return request; + } + + static class ResultWaiter implements Callable, MessageDeliveryListener { + + public ImcMsgManager.SendResult result = null; + private long timeoutMillis = 10000; + private long start; + + public ResultWaiter(long timeoutMillis) { + this.timeoutMillis = timeoutMillis; + this.start = System.currentTimeMillis(); + } + + @Override + public ImcMsgManager.SendResult call() throws Exception { + while (true) { + synchronized (this) { + if (result != null) { + return result; + } + if (System.currentTimeMillis() - start > timeoutMillis) { + return ImcMsgManager.SendResult.TIMEOUT; + } + } + Thread.sleep(100); + } + } + + @Override + public void deliveryError(IMCMessage message, Object error) { + result = ImcMsgManager.SendResult.ERROR; + } + + @Override + public void deliverySuccess(IMCMessage message) { + result = ImcMsgManager.SendResult.SUCCESS; + } + + @Override + public void deliveryTimeOut(IMCMessage message) { + result = ImcMsgManager.SendResult.TIMEOUT; + } + + @Override + public void deliveryUncertain(IMCMessage message, Object msg) { + result = ImcMsgManager.SendResult.UNCERTAIN_DELIVERY; + } + + @Override + public void deliveryUnreacheable(IMCMessage message) { + result = ImcMsgManager.SendResult.UNREACHABLE; + } + } + + public void sendViaIridium(String destination, IMCMessage message, ResultWaiter waiter) { + if (message.getTimestamp() == 0) + message.setTimestampMillis(System.currentTimeMillis()); + Collection irMsgs; + try { + irMsgs = IridiumManager.iridiumEncode(message); + } + catch (Exception e) { + NeptusLog.pub().warn("Send by Iridium :: " + e.getMessage()); + waiter.deliveryError(message, e); + return; + } + int src = ImcMsgManager.getManager().getLocalId().intValue(); + int dst = IMCDefinition.getInstance().getResolver().resolve(destination); + int count = 0; + try { + NeptusLog.pub().warn(message.getAbbrev() + " resulted in " + irMsgs.size() + " iridium SBD messages."); + for (ImcIridiumMessage irMsg : irMsgs) { + irMsg.setDestination(dst); + irMsg.setSource(src); + irMsg.timestampMillis = message.getTimestampMillis(); + if (irMsg.timestampMillis == 0) + irMsg.timestampMillis = System.currentTimeMillis(); + IridiumManager.getManager().send(irMsg); + count++; + } + + NeptusLog.pub().warn("Iridium message sent", count + " Iridium messages were sent using " + + IridiumManager.getManager().getCurrentMessenger().getName()); + waiter.deliverySuccess(message); + } + catch (Exception e) { + NeptusLog.pub().warn("Send by Iridium :: " + e.getMessage()); + waiter.deliveryError(message, e); + } + } + + private void sendDeviceActivationViaIridiumIfNeeded(String destinationName) { + LocalDateTime lastSent = lastIridiumMessageSent.get(destinationName); + if (lastSent == null || lastSent.plusMinutes(minutesBetweenDeviceActivationSendMinutes).isBefore(LocalDateTime.now())) { + lastIridiumMessageSent.put(destinationName, LocalDateTime.now()); + int src = ImcMsgManager.getManager().getLocalId().intValue(); + int dst = IMCDefinition.getInstance().getResolver().resolve(destinationName); + UpdateDeviceActivation act = new UpdateDeviceActivation(); + act.setDestination(dst); + act.setSource(src); + act.timestampMillis = System.currentTimeMillis(); + act.setOperation(UpdateDeviceActivation.OperationType.OP_ACTIVATE); + act.setTimestampSeconds(act.timestampMillis / 1000.); + try { + NeptusLog.pub().warn(">>>> Send activation request <<<< " + ByteUtil.encodeToHex(act.serialize())); + IridiumManager.getManager().send(act); + } catch (Exception e) { + NeptusLog.pub().warn("Send by Iridium :: " + e.getMessage()); + } + } + } +} diff --git a/src/java/pt/lsts/neptus/comm/iridium/HubIridiumMessenger.java b/src/java/pt/lsts/neptus/comm/iridium/HubIridiumMessenger.java index 0290faabd8..7833f4a924 100644 --- a/src/java/pt/lsts/neptus/comm/iridium/HubIridiumMessenger.java +++ b/src/java/pt/lsts/neptus/comm/iridium/HubIridiumMessenger.java @@ -40,6 +40,9 @@ import pt.lsts.neptus.comm.manager.imc.ImcId16; import pt.lsts.neptus.comm.manager.imc.ImcSystem; import pt.lsts.neptus.comm.manager.imc.ImcSystemsHolder; +import pt.lsts.neptus.types.comm.CommMean; +import pt.lsts.neptus.types.comm.protocol.IridiumArgs; +import pt.lsts.neptus.types.comm.protocol.ProtocolArgs; import pt.lsts.neptus.types.coord.LocationType; import pt.lsts.neptus.types.vehicle.VehicleType; import pt.lsts.neptus.types.vehicle.VehiclesHolder; @@ -267,6 +270,7 @@ public Collection pollMessages(Date timeSince) throws Exception for (HubMessage m : msgs) { try { ret.add(m.message()); + updateVehicleWithLastSeenImei(m.imei, m.createdAt()); } catch (Exception e) { String report = new String(Hex.decodeHex(m.msg.toCharArray())); @@ -284,11 +288,12 @@ public Collection pollMessages(Date timeSince) throws Exception long timestamp = parseTimeString(timeOfDay).getTime(); ImcSystem system = ImcSystemsHolder.getSystemWithName(vehicle); - + if (system != null) { system.setLocation(new LocationType(lat, lon), timestamp); } + updateVehicleWithLastSeenImei(m.imei, m.createdAt() != null ? m.createdAt() : m.updatedAt()); NeptusLog.pub().info("Text report: {}", report); } @@ -300,7 +305,17 @@ public Collection pollMessages(Date timeSince) throws Exception return ret; } - + + public static void updateVehicleWithLastSeenImei(String imei, Date date) { + VehicleType veh = VehiclesHolder.getVehicleWithImei(imei); + if (veh != null) { + IridiumArgs iridiumArgs = (IridiumArgs) veh.getProtocolsArgs().get(CommMean.IRIDIUM); + if (iridiumArgs != null) { + iridiumArgs.setLastSeenImei(imei, date); + } + } + } + public static Date parseTimeString(String timeOfDay) { GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC")); String[] timeParts = timeOfDay.split(":"); diff --git a/src/java/pt/lsts/neptus/comm/iridium/ImcFullIridiumMessage.java b/src/java/pt/lsts/neptus/comm/iridium/ImcFullIridiumMessage.java new file mode 100644 index 0000000000..90681429ef --- /dev/null +++ b/src/java/pt/lsts/neptus/comm/iridium/ImcFullIridiumMessage.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2004-2025 Universidade do Porto - Faculdade de Engenharia + * Laboratório de Sistemas e Tecnologia Subaquática (LSTS) + * All rights reserved. + * Rua Dr. Roberto Frias s/n, sala I203, 4200-465 Porto, Portugal + * + * This file is part of Neptus, Command and Control Framework. + * + * Commercial Licence Usage + * Licencees holding valid commercial Neptus licences may use this file + * in accordance with the commercial licence agreement provided with the + * Software or, alternatively, in accordance with the terms contained in a + * written agreement between you and Universidade do Porto. For licensing + * terms, conditions, and further information contact lsts@fe.up.pt. + * + * Modified European Union Public Licence - EUPL v.1.1 Usage + * Alternatively, this file may be used under the terms of the Modified EUPL, + * Version 1.1 only (the "Licence"), appearing in the file LICENSE.md + * included in the packaging of this file. You may not use this work + * except in compliance with the Licence. Unless required by applicable + * law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the Licence for the specific + * language governing permissions and limitations at + * https://github.com/LSTS/neptus/blob/develop/LICENSE.md + * and http://ec.europa.eu/idabc/eupl.html. + * + * For more information please see . + * + * Author: pdias + * 18 Jul, 2024 + */ +package pt.lsts.neptus.comm.iridium; + +import pt.lsts.imc.IMCDefinition; +import pt.lsts.imc.IMCInputStream; +import pt.lsts.imc.IMCMessage; +import pt.lsts.imc.IMCOutputStream; + +import java.util.Collection; +import java.util.Vector; + +/** + * @author pdias + * + */ +public class ImcFullIridiumMessage extends ImcIridiumMessage { + + // 5 bytes for RB addressing, 6 bytes for type and timestamp, 6 bytes for IMC header + public static int MaxPayloadSize = 270 - 17; + + public ImcFullIridiumMessage() { + super(2013); + } + + @Override + public int serializeFields(IMCOutputStream out) throws Exception { + if (msg != null) { + IMCDefinition.getInstance().serialize(msg, out); + timestampMillis = msg.getTimestampMillis(); + int size = IMCDefinition.getInstance().serializationSize(msg); + return size; + } + return 0; + } + + @Override + public int deserializeFields(IMCInputStream in) throws Exception { + try { + msg = IMCDefinition.getInstance().unserialize(in); + timestampMillis = msg.getTimestampMillis(); + setSource(msg.getSrc()); + setDestination(msg.getDst()); + } + catch (Exception e) { + e.printStackTrace(); + } + return IMCDefinition.getInstance().serializationSize(msg); + } + + @Override + public Collection asImc() { + Vector vec = new Vector<>(); + if (msg != null) + vec.add(msg); + + //msg.setSrc(getSource()); + //msg.setDst(getDestination()); + //msg.setTimestampMillis(timestampMillis); + return vec; + } +} diff --git a/src/java/pt/lsts/neptus/comm/iridium/ImcIridiumMessage.java b/src/java/pt/lsts/neptus/comm/iridium/ImcIridiumMessage.java index 0b94347258..0348c041c5 100644 --- a/src/java/pt/lsts/neptus/comm/iridium/ImcIridiumMessage.java +++ b/src/java/pt/lsts/neptus/comm/iridium/ImcIridiumMessage.java @@ -53,8 +53,12 @@ public class ImcIridiumMessage extends IridiumMessage { public ImcIridiumMessage() { super(2010); - } - + } + + protected ImcIridiumMessage(int msgType) { + super(msgType); + } + @Override public int serializeFields(IMCOutputStream out) throws Exception { if (msg != null) { diff --git a/src/java/pt/lsts/neptus/comm/iridium/IridiumManager.java b/src/java/pt/lsts/neptus/comm/iridium/IridiumManager.java index 653e6121d4..3c0ccd6fe7 100644 --- a/src/java/pt/lsts/neptus/comm/iridium/IridiumManager.java +++ b/src/java/pt/lsts/neptus/comm/iridium/IridiumManager.java @@ -64,10 +64,12 @@ import pt.lsts.neptus.NeptusLog; import pt.lsts.neptus.comm.manager.imc.EntitiesResolver; import pt.lsts.neptus.comm.manager.imc.ImcMsgManager; +import pt.lsts.neptus.i18n.I18n; import pt.lsts.neptus.util.ByteUtil; import pt.lsts.neptus.util.ImageUtils; import pt.lsts.neptus.util.MathMiscUtils; import pt.lsts.neptus.util.conf.GeneralPreferences; +import pt.lsts.neptus.util.speech.SpeechUtil; /** * This class will handle Iridium communications @@ -89,6 +91,8 @@ public class IridiumManager { public static final int IRIDIUM_MTU = 270; public static final int IRIDIUM_HEADER = 6; + + private long timeSinceLastUpdateVoiceWarning = -1; public enum IridiumMessengerEnum { DuneIridiumMessenger, @@ -140,6 +144,9 @@ public void run() { NeptusLog.pub().info("Start polling messages from Iridium network."); Collection msgs = getCurrentMessenger().pollMessages(lastTime); NeptusLog.pub().info("Polled {} messages from Iridium network.", msgs.size()); + if (!msgs.isEmpty()) { + speakUpdateEntityState(); + } for (IridiumMessage m : msgs) { try { processMessage(m); @@ -161,6 +168,14 @@ public void run() { } }; + private synchronized void speakUpdateEntityState() { + if (System.currentTimeMillis() - timeSinceLastUpdateVoiceWarning > Duration.ofSeconds(10).toMillis()) { + timeSinceLastUpdateVoiceWarning = System.currentTimeMillis(); + String msg = I18n.text("Ireedeehum received"); // To be able to speak Iridium + SpeechUtil.readSimpleText(msg); + } + } + public static Date parseTimeString(String timeOfDay) { GregorianCalendar date = new GregorianCalendar(TimeZone.getTimeZone("UTC")); String[] timeParts = timeOfDay.split(":"); diff --git a/src/java/pt/lsts/neptus/comm/iridium/IridiumMessage.java b/src/java/pt/lsts/neptus/comm/iridium/IridiumMessage.java index dff7a4aa1d..75e39ca022 100644 --- a/src/java/pt/lsts/neptus/comm/iridium/IridiumMessage.java +++ b/src/java/pt/lsts/neptus/comm/iridium/IridiumMessage.java @@ -72,6 +72,8 @@ public IridiumMessage(int msgType) { iridiumTypes.put(2007, TargetAssetPosition.class); iridiumTypes.put(2010, ImcIridiumMessage.class); iridiumTypes.put(2011, ExtendedDeviceUpdate.class); + iridiumTypes.put(2012, UpdateDeviceActivation.class); + iridiumTypes.put(2013, ImcFullIridiumMessage.class); } public byte[] serialize() throws Exception { @@ -104,8 +106,8 @@ public static IridiumMessage deserialize(byte[] data) throws Exception { } if (m != null) { - m.setSource(mgid > -1 ? source : 0xFFFF); - m.setDestination(mgid > -1 ? dest : 0xFFFF); + //m.setSource(mgid > -1 ? source : 0xFFFF); + //m.setDestination(mgid > -1 ? dest : 0xFFFF); m.setMessageType(mgid); if (mgid > -1) m.deserializeFields(iis); diff --git a/src/java/pt/lsts/neptus/comm/iridium/PlainTextMessage.java b/src/java/pt/lsts/neptus/comm/iridium/PlainTextMessage.java index 93545a2d9c..2367e053db 100644 --- a/src/java/pt/lsts/neptus/comm/iridium/PlainTextMessage.java +++ b/src/java/pt/lsts/neptus/comm/iridium/PlainTextMessage.java @@ -32,27 +32,35 @@ */ package pt.lsts.neptus.comm.iridium; +import pt.lsts.imc.IMCDefinition; import pt.lsts.imc.IMCInputStream; import pt.lsts.imc.IMCMessage; import pt.lsts.imc.IMCOutputStream; import pt.lsts.imc.TextMessage; import pt.lsts.neptus.NeptusLog; +import javax.xml.bind.annotation.adapters.HexBinaryAdapter; +import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @author pdias * */ public class PlainTextMessage extends IridiumMessage { + private static final Pattern p0 = Pattern.compile("\\(([^\\)]*)\\).*"); String text; byte[] rawData; + public String vehicle = ""; + public PlainTextMessage() { super(-1); } @@ -73,6 +81,17 @@ public int deserializeFields(IMCInputStream in) throws Exception { rawData = data; text = new String(data, StandardCharsets.UTF_8); text = text.trim(); + + Matcher matcher = p0.matcher(text); + if (matcher.matches()) { + vehicle = matcher.group(1).trim(); + String[] tks = vehicle.split(" - "); + if (tks.length > 1) { + vehicle = tks[0]; + } + source = IMCDefinition.getInstance().getResolver().resolve(vehicle); + } + return text.getBytes(StandardCharsets.UTF_8).length; } @@ -125,4 +144,23 @@ static IridiumMessage createTextMessageFrom(IMCInputStream in) throws Exception plainTextMessage.deserializeFields(in); return plainTextMessage; } + + public static void main(String[] args) { + String textMsg = "(caravel) 2025/01/16 09:52:16 (APC):Using Modem 1"; + + byte[] bytesMsh = textMsg.getBytes(); + + IMCInputStream iis = new IMCInputStream(new ByteArrayInputStream(bytesMsh), IMCDefinition.getInstance()); + iis.setBigEndian(false); + PlainTextMessage txtIridium = new PlainTextMessage(); + try { + txtIridium.deserializeFields(iis); + NeptusLog.pub().info("Received a plain text from " + txtIridium.text); + System.out.println("Received a plain text from " + txtIridium + " :: " + txtIridium.vehicle); + } + catch (Exception e) { + NeptusLog.pub().error(e); + e.printStackTrace(); + } + } } diff --git a/src/java/pt/lsts/neptus/comm/iridium/PlainTextReportMessage.java b/src/java/pt/lsts/neptus/comm/iridium/PlainTextReportMessage.java index 33fa60a010..a888fc9780 100644 --- a/src/java/pt/lsts/neptus/comm/iridium/PlainTextReportMessage.java +++ b/src/java/pt/lsts/neptus/comm/iridium/PlainTextReportMessage.java @@ -60,6 +60,7 @@ public class PlainTextReportMessage extends IridiumMessage { String report; String vehicle; + String vehicleAlt; String timeOfDay; int source = 0xFFFF; double latDeg; @@ -104,6 +105,7 @@ public Collection asImc() { public String toString() { return "Report: " + report + "\n" + "Vehicle: " + vehicle + "\n" + + "Vehicle Alt: " + (vehicleAlt == null ? "" : vehicleAlt) + "\n" + "Time of day: " + timeOfDay + "\n" + "Lat: " + latDeg + "\n" + "Lon: " + lonDeg + "\n" @@ -124,6 +126,11 @@ private void parse() throws Exception { } vehicle = matcher.group(2); + String[] tks = vehicle.split(" - "); + if (tks.length > 1) { + vehicleAlt = vehicle.replaceFirst(tks[0], "").trim(); + vehicle = tks[0]; + } timeOfDay = matcher.group(3); String latMins = matcher.group(4); String lonMins = matcher.group(5); diff --git a/src/java/pt/lsts/neptus/comm/iridium/RockBlockIridiumMessenger.java b/src/java/pt/lsts/neptus/comm/iridium/RockBlockIridiumMessenger.java index 7971cb5457..7b12b373ae 100644 --- a/src/java/pt/lsts/neptus/comm/iridium/RockBlockIridiumMessenger.java +++ b/src/java/pt/lsts/neptus/comm/iridium/RockBlockIridiumMessenger.java @@ -85,6 +85,8 @@ import pt.lsts.neptus.util.conf.ConfigFetch; import pt.lsts.neptus.util.http.client.HttpClientConnectionHelper; +import static pt.lsts.neptus.comm.iridium.HubIridiumMessenger.updateVehicleWithLastSeenImei; + /** * This class uses the RockBlock HTTP API (directly) to send messages to Iridium destinations and a gmail inbox to poll * for incoming messages @@ -225,7 +227,7 @@ public void sendMessage(IridiumMessage msg) throws Exception { if (askCredentials()) return; - String result = sendToRockBlockHttp(args.getImei(), getRockBlockUsername(), getRockBlockPassword(), + String result = sendToRockBlockHttp(args.getLastSeenImei(), getRockBlockUsername(), getRockBlockPassword(), msg.serialize()); checkResponseFromServer(result); } @@ -238,7 +240,7 @@ public void sendMessageRaw(String destinationName, String imeiAddr, byte[] data) throw new Exception("Cannot send message to an unknown destination"); } IridiumArgs args = (IridiumArgs) vt.getProtocolsArgs().get("iridium"); - imeiAddr = args.getImei(); + imeiAddr = args.getLastSeenImei(); } if (askCredentials()) @@ -326,7 +328,10 @@ else if (m.getContent() instanceof MimeMultipart) { if (matcher.matches()) { InputStream stream = (InputStream) p.getContent(); byte[] data = IOUtils.toByteArray(stream); - IridiumMessage msg = process(data, matcher.group(1), matcher.group(2), m.getSentDate()); + String fromImei = matcher.group(1); + String seqNumber = matcher.group(2); + IridiumMessage msg = process(data, fromImei, seqNumber, + m.getSentDate() == null ? m.getReceivedDate() : m.getSentDate()); if (msg != null) messages.add(msg); } @@ -364,6 +369,7 @@ private IridiumMessage process(byte[] data, String fromImei, String seqNumber, D // Let us try to fill the source from imei irMsg.source = HubIridiumMessenger.HubMessage.findSystemIdByImei(fromImei); } + updateVehicleWithLastSeenImei(fromImei, sentDate); // If not set, set the timestamp if (!new Date(irMsg.timestampMillis).before(now) && sentDate != null) { diff --git a/src/java/pt/lsts/neptus/comm/iridium/UpdateDeviceActivation.java b/src/java/pt/lsts/neptus/comm/iridium/UpdateDeviceActivation.java new file mode 100644 index 0000000000..51ae4c1b1e --- /dev/null +++ b/src/java/pt/lsts/neptus/comm/iridium/UpdateDeviceActivation.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2004-2025 Universidade do Porto - Faculdade de Engenharia + * Laboratório de Sistemas e Tecnologia Subaquática (LSTS) + * All rights reserved. + * Rua Dr. Roberto Frias s/n, sala I203, 4200-465 Porto, Portugal + * + * This file is part of Neptus, Command and Control Framework. + * + * Commercial Licence Usage + * Licencees holding valid commercial Neptus licences may use this file + * in accordance with the commercial licence agreement provided with the + * Software or, alternatively, in accordance with the terms contained in a + * written agreement between you and Universidade do Porto. For licensing + * terms, conditions, and further information contact lsts@fe.up.pt. + * + * Modified European Union Public Licence - EUPL v.1.1 Usage + * Alternatively, this file may be used under the terms of the Modified EUPL, + * Version 1.1 only (the "Licence"), appearing in the file LICENSE.md + * included in the packaging of this file. You may not use this work + * except in compliance with the Licence. Unless required by applicable + * law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the Licence for the specific + * language governing permissions and limitations at + * https://github.com/LSTS/neptus/blob/develop/LICENSE.md + * and http://ec.europa.eu/idabc/eupl.html. + * + * For more information please see . + * + * Author: pdias + * Jun 19, 2024 + */ +package pt.lsts.neptus.comm.iridium; + +import pt.lsts.imc.IMCInputStream; +import pt.lsts.imc.IMCMessage; +import pt.lsts.imc.IMCOutputStream; + +import java.util.Collection; +import java.util.Vector; + +/** + * @author zp + * + */ +public class UpdateDeviceActivation extends IridiumMessage { + + public static enum OperationType { + OP_DEACTIVATE, + OP_ACTIVATE; + + } + + public UpdateDeviceActivation() { + super(2012); + } + + + protected double timestampSeconds = System.currentTimeMillis() / 1000.; + protected OperationType operation = OperationType.OP_ACTIVATE; + + public double getTimestampSeconds() { + return timestampSeconds; + } + + public void setTimestampSeconds(double timestampSeconds) { + this.timestampSeconds = timestampSeconds; + } + + public OperationType getOperation() { + return operation; + } + + public void setOperation(OperationType operation) { + this.operation = operation; + } + + @Override + public int serializeFields(IMCOutputStream out) throws Exception { + out.writeDouble(timestampSeconds); + out.writeByte(operation.ordinal()); + return 9; + } + + @Override + public int deserializeFields(IMCInputStream in) throws Exception { + try { + timestampSeconds = in.readDouble(); + operation = OperationType.values()[in.readUnsignedByte()]; + } + catch (Exception e) { + // empty + } + return 9; + } + + @Override + public Collection asImc() { + return new Vector<>(); + } + + @Override + public String toString() { + String s = super.toString(); + s += "\t[timestampSeconds: "+timestampSeconds+"]\n"; + s += "\t[operation: "+operation.name()+"]\n"; + return s; + } +} diff --git a/src/java/pt/lsts/neptus/comm/manager/imc/EntitiesResolver.java b/src/java/pt/lsts/neptus/comm/manager/imc/EntitiesResolver.java index 6fa3b19f56..28bf495e34 100644 --- a/src/java/pt/lsts/neptus/comm/manager/imc/EntitiesResolver.java +++ b/src/java/pt/lsts/neptus/comm/manager/imc/EntitiesResolver.java @@ -32,40 +32,90 @@ */ package pt.lsts.neptus.comm.manager.imc; -import java.util.LinkedHashMap; -import java.util.Map; - import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; - import pt.lsts.imc.EntityList; +import pt.lsts.imc.IMCMessage; +import pt.lsts.neptus.NeptusLog; +import pt.lsts.neptus.util.PropertiesLoader; +import pt.lsts.neptus.util.conf.ConfigFetch; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; /** * @author zp * */ public class EntitiesResolver { + public static final String ENTITIES_RESOLVER_PROPERTIES_FILE = ".cache/db/entities-resolver.properties"; + + protected static LinkedHashMap> entitiesMap = new LinkedHashMap>(); + public static final int DEFAULT_ENTITY = 255; + + private static PropertiesLoader properties = null; + + static { + String propertiesFile = ConfigFetch.resolvePathBasedOnConfigFile(ENTITIES_RESOLVER_PROPERTIES_FILE); + if (!new File(propertiesFile).exists()) { + String testFile = ConfigFetch.resolvePathBasedOnConfigFile("../" + ENTITIES_RESOLVER_PROPERTIES_FILE); + if (new File(testFile).exists()) + propertiesFile = testFile; + } + new File(propertiesFile).getParentFile().mkdirs(); + properties = new PropertiesLoader(propertiesFile, PropertiesLoader.PROPERTIES); + + Enumeration it = properties.keys(); + while (it.hasMoreElements()) { + String key = it.nextElement().toString(); + String value = properties.getProperty(key); + setEntities(key, value, false); + } + } + + public static void saveProperties() { + try { + properties.store("EntitiesResolver properties"); + } + catch (IOException e) { + NeptusLog.pub().error("saveProperties", e); + } + } - protected static LinkedHashMap> entitiesMap = new LinkedHashMap>(); - public static final int DEFAULT_ENTITY = 255; - - /** * Based on a EntityList message set all the messages for further queries * @param id Name of the name (String id) * @param message EntityList message */ public static void setEntities(String id, EntityList message) { - LinkedHashMap tlist = null; - tlist = message.getList(); - - BiMap aliases = HashBiMap.create(); - for (String key : tlist.keySet()) - aliases.put(Integer.parseInt(tlist.get(key)), key); - - entitiesMap.put(id, aliases); - } - + LinkedHashMap tlist = null; + tlist = message.getList(); + String listAsStr = IMCMessage.encodeTupleList(tlist); + setEntities(id, listAsStr, true); + } + + public static void setEntities(String id, String listAsStr, boolean save) { + LinkedHashMap tlist = null; + tlist = IMCMessage.decodeTupleList(listAsStr); + + BiMap aliases = HashBiMap.create(); + for (String key : tlist.keySet()) { + aliases.put(Integer.parseInt(tlist.get(key)), key); + } + + if (save) { + String old = properties.getProperty(id); + if (old == null || !old.equals(listAsStr)) { + properties.setProperty(id, listAsStr); + saveProperties(); + } + } + + entitiesMap.put(id, aliases); + } public static final Map getEntities(Object systemId) { return entitiesMap.get(systemId.toString()); } diff --git a/src/java/pt/lsts/neptus/comm/manager/imc/ImcId16.java b/src/java/pt/lsts/neptus/comm/manager/imc/ImcId16.java index a9e32cb55e..d781c57342 100644 --- a/src/java/pt/lsts/neptus/comm/manager/imc/ImcId16.java +++ b/src/java/pt/lsts/neptus/comm/manager/imc/ImcId16.java @@ -105,10 +105,14 @@ public static boolean isValidIdForSource(long id) { @Override public int compareTo(ImcId16 o) { - //return (int) (longValue() - o.longValue()); return (longValue() < o.longValue() ? -1 : (longValue() == o.longValue() ? 0 : 1)); } + public int compareTo(int id) { + long i = id & 0xFFFF; + return (longValue() < i ? -1 : (longValue() == i ? 0 : 1)); + } + @Override public boolean equals(Object obj) { if(this == obj) diff --git a/src/java/pt/lsts/neptus/comm/manager/imc/ImcMsgManager.java b/src/java/pt/lsts/neptus/comm/manager/imc/ImcMsgManager.java index a651c84a67..3e25cf9189 100644 --- a/src/java/pt/lsts/neptus/comm/manager/imc/ImcMsgManager.java +++ b/src/java/pt/lsts/neptus/comm/manager/imc/ImcMsgManager.java @@ -31,22 +31,20 @@ */ package pt.lsts.neptus.comm.manager.imc; -import java.io.IOException; +import java.awt.Component; import java.net.Inet4Address; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.net.URL; import java.time.Duration; import java.time.LocalTime; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; -import java.util.Map; import java.util.Vector; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -68,24 +66,20 @@ import pt.lsts.imc.AssetReport; import pt.lsts.imc.EntityInfo; import pt.lsts.imc.EntityList; -import pt.lsts.imc.FuelLevel; import pt.lsts.imc.IMCDefinition; import pt.lsts.imc.IMCMessage; import pt.lsts.imc.MessagePart; -import pt.lsts.imc.PlanControlState; -import pt.lsts.imc.PlanControlState.STATE; import pt.lsts.imc.RemoteSensorInfo; import pt.lsts.imc.ReportedState; import pt.lsts.imc.StateReport; import pt.lsts.imc.lsf.LsfMessageLogger; -import pt.lsts.imc.net.IMCFragmentHandler; import pt.lsts.imc.state.ImcSystemState; import pt.lsts.neptus.NeptusLog; import pt.lsts.neptus.comm.CommUtil; import pt.lsts.neptus.comm.IMCSendMessageUtils; import pt.lsts.neptus.comm.IMCUtils; import pt.lsts.neptus.comm.NoTransportAvailableException; -import pt.lsts.neptus.comm.SystemUtils; +import pt.lsts.neptus.comm.admin.CommsAdmin; import pt.lsts.neptus.comm.manager.CommBaseManager; import pt.lsts.neptus.comm.manager.CommManagerStatusChangeListener; import pt.lsts.neptus.comm.manager.MessageFrequencyCalculator; @@ -99,18 +93,11 @@ import pt.lsts.neptus.messages.listener.MessageInfoImpl; import pt.lsts.neptus.messages.listener.MessageListener; import pt.lsts.neptus.plugins.PluginUtils; -import pt.lsts.neptus.systems.external.ExternalSystem; -import pt.lsts.neptus.systems.external.ExternalSystem.ExternalTypeEnum; -import pt.lsts.neptus.systems.external.ExternalSystemsHolder; import pt.lsts.neptus.types.XmlOutputMethods; -import pt.lsts.neptus.types.coord.LocationType; import pt.lsts.neptus.types.vehicle.VehicleType; import pt.lsts.neptus.types.vehicle.VehicleType.SystemTypeEnum; -import pt.lsts.neptus.types.vehicle.VehicleType.VehicleTypeEnum; import pt.lsts.neptus.types.vehicle.VehiclesHolder; -import pt.lsts.neptus.util.AngleUtils; import pt.lsts.neptus.util.GuiUtils; -import pt.lsts.neptus.util.MathMiscUtils; import pt.lsts.neptus.util.NetworkInterfacesUtil; import pt.lsts.neptus.util.NetworkInterfacesUtil.NInterface; import pt.lsts.neptus.util.StringUtils; @@ -127,7 +114,7 @@ public class ImcMsgManager extends public static final String TRANSPORT_UDP = "UDP"; public static final String TRANSPORT_TCP = "TCP"; - private static final int DEFAULT_UDP_VEH_PORT = 6002; + static final int DEFAULT_UDP_VEH_PORT = 6002; /** * Singleton @@ -138,8 +125,6 @@ public class ImcMsgManager extends private boolean sameIdErrorDetected = false; private long sameIdErrorDetectedTimeMillis = -1; - protected IMCFragmentHandler fragmentHandler;; - protected ImcSystemState imcState; // public static String CCU_VEH_STRING = "CCU-VEH"; @@ -156,15 +141,20 @@ public class ImcMsgManager extends private boolean logSentMsg = false; @Deprecated - private boolean dontIgnoreIpSourceRequest = true; + boolean dontIgnoreIpSourceRequest = true; private boolean multicastEnabled = true; private String multicastAddress = "224.0.75.69"; private int[] multicastPorts = new int[] { 6969 }; private boolean broadcastEnabled = true; - private final IMCDefinition imcDefinition; // = IMCDefinition.getInstance(); - private AnnounceWorker announceWorker; // = new AnnounceWorker(this, imcDefinition); + private CommsAdmin commsAdmin = null; + + private ImcMsgManagerAnnounceProcessor announceProcessor; + private ImcMsgManagerMessageProcessor messageProcessor; + + final IMCDefinition imcDefinition; // = IMCDefinition.getInstance(); + AnnounceWorker announceWorker; // = new AnnounceWorker(this, imcDefinition); private long announceLastArriveTime = -1; private final PreferencesListener gplistener; @@ -280,12 +270,20 @@ public void preferencesUpdated() { this.imcDefinition = imcDefinition; announceWorker = new AnnounceWorker(this, imcDefinition); - fragmentHandler = new IMCFragmentHandler(imcDefinition); imcState = new ImcSystemState(imcDefinition); imcState.setIgnoreEntities(true); + announceProcessor = new ImcMsgManagerAnnounceProcessor(this); + messageProcessor = new ImcMsgManagerMessageProcessor(this); + GeneralPreferences.addPreferencesListener(gplistener); gplistener.preferencesUpdated(); + + commsAdmin = new CommsAdmin(this); + } + + public CommsAdmin getCommsAdmin() { + return commsAdmin; } /* (non-Javadoc) @@ -351,7 +349,7 @@ private void updateUdpOnIpMapper() { } } - private void updateUdpOnIpMapper(SystemImcMsgCommInfo vsci) { + void updateUdpOnIpMapper(SystemImcMsgCommInfo vsci) { if (isUdpOn()) udpOnIpMapper.forcePut(vsci.getIpAddress() + (isFilterByPort ? ":" + vsci.getIpRemotePort() : ""), vsci.getSystemCommId()); @@ -773,283 +771,6 @@ public boolean is2IdErrorMode() { return sameIdErrorDetected; } - private void processEntityInfo(MessageInfo info, EntityInfo msg) { - imcDefinition.getResolver().setEntityName(msg.getSrc(), msg.getSrcEnt(), msg.getLabel()); - } - - private void processMessagePart(MessageInfo info, MessagePart msg) { - IMCMessage m = fragmentHandler.setFragment((MessagePart)msg); - if (m != null) - postInternalMessage(msg.getSourceName(), m); - } - - private void processEntityList(ImcId16 id, MessageInfo info, EntityList msg) { - EntitiesResolver.setEntities(id.toString(), msg); - imcDefinition.getResolver().setEntityMap(msg.getSrc(), msg.getList()); - ImcSystem sys = ImcSystemsHolder.lookupSystem(id); - if (sys != null) { - EntitiesResolver.setEntities(sys.getName(), msg); - } - } - - private void processReportedState(MessageInfo info, ReportedState msg) { - // Process pos. state reported from other system - String sysId = msg.getSid(); - - double latRad = msg.getLat(); - double lonRad = msg.getLon(); - double depth = msg.getDepth(); - - double rollRad = msg.getRoll(); - double pitchRad = msg.getPitch(); - double yawRad = msg.getYaw(); - - double recTimeSecs = msg.getRcpTime(); - long recTimeMillis = Double.isFinite(recTimeSecs) ? (long) (recTimeSecs * 1E3) : msg.getTimestampMillis(); - - // msg.getSType(); // Not used - - ImcSystem imcSys = ImcSystemsHolder.lookupSystemByName(sysId); - ExternalSystem extSys = null; - if (imcSys == null) { - extSys = ExternalSystemsHolder.lookupSystem(sysId); - if (extSys == null) { - extSys = new ExternalSystem(sysId); - ExternalSystemsHolder.registerSystem(extSys); - } - } - - if (Double.isFinite(latRad) && Double.isFinite(lonRad)) { - LocationType loc = new LocationType(Math.toDegrees(latRad), Math.toDegrees(lonRad)); - if (Double.isFinite(depth)) - loc.setDepth(depth); - - if (imcSys != null) - imcSys.setLocation(loc, recTimeMillis); - else - extSys.setLocation(loc, recTimeMillis); - } - - if (Double.isFinite(rollRad) || Double.isFinite(pitchRad) || Double.isFinite(yawRad)) { - double rollDeg = Double.isFinite(rollRad) ? Math.toDegrees(rollRad) : 0; - double pitchDeg = Double.isFinite(pitchRad) ? Math.toDegrees(pitchRad) : 0; - double yawDeg = Double.isFinite(yawRad) ? Math.toDegrees(yawRad) : 0; - - if (imcSys != null) - imcSys.setAttitudeDegrees(rollDeg, pitchDeg, yawDeg, recTimeMillis); - else - extSys.setAttitudeDegrees(rollDeg, pitchDeg, yawDeg, recTimeMillis); - } - } - - private void processStateReport(MessageInfo info, StateReport msg, ArrayList messagesCreatedToFoward) { - - String sysId = msg.getSourceName(); - - long dataTimeMillis = msg.getStime() * 1000; - - double lat = msg.getLatitude(); - double lon = msg.getLongitude(); - double depth = msg.getDepth() == 0xFFFF ? -1 : msg.getDepth() / 10.0; - // double altitude = msg.getAltitude() == 0xFFFF ? -1 : msg.getAltitude() / 10.0; - double heading = Math.toDegrees(AngleUtils.nomalizeAngleRads2Pi(((double) msg.getHeading() / 65535.0) * Math.PI * 2)); - double speedMS = msg.getSpeed() / 100.; - NeptusLog.pub().info("Received report from "+msg.getSourceName()); - - ImcSystem imcSys = ImcSystemsHolder.lookupSystemByName(sysId); - if (imcSys == null) { - NeptusLog.pub().error("Could not find system with id "+sysId); - return; - } - - LocationType loc = new LocationType(lat, lon); - loc.setDepth(depth); - imcSys.setLocation(loc, dataTimeMillis); - imcSys.setAttitudeDegrees(heading, dataTimeMillis); - - imcSys.storeData(SystemUtils.GROUND_SPEED_KEY, speedMS, dataTimeMillis, true); - imcSys.storeData(SystemUtils.COURSE_DEGS_KEY, - (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(heading, 0)), - dataTimeMillis, true); - imcSys.storeData( - SystemUtils.HEADING_DEGS_KEY, - (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(heading, 0)), - dataTimeMillis, true); - - int fuelPerc = msg.getFuel(); - if (fuelPerc > 0) { - FuelLevel fuelLevelMsg = new FuelLevel(); - IMCUtils.copyHeader(msg, fuelLevelMsg); - fuelLevelMsg.setTimestampMillis(dataTimeMillis); - fuelLevelMsg.setValue(fuelPerc); - fuelLevelMsg.setConfidence(0); - imcSys.storeData(SystemUtils.FUEL_LEVEL_KEY, fuelLevelMsg, dataTimeMillis, true); - - messagesCreatedToFoward.add(fuelLevelMsg); - } - - int execState = msg.getExecState(); - PlanControlState pcsMsg = new PlanControlState(); - IMCUtils.copyHeader(msg, pcsMsg); - pcsMsg.setTimestampMillis(dataTimeMillis); - switch (execState) { - case -1: - pcsMsg.setState(STATE.READY); - break; - case -3: - pcsMsg.setState(STATE.INITIALIZING); - break; - case -2: - case -4: - pcsMsg.setState(STATE.BLOCKED); - break; - default: - if (execState > 0) - pcsMsg.setState(STATE.EXECUTING); - else - pcsMsg.setState(STATE.BLOCKED); - break; - } - - pcsMsg.setPlanEta(-1); - pcsMsg.setPlanProgress(execState >= 0 ? execState : -1); - pcsMsg.setManId(""); - pcsMsg.setManEta(-1); - pcsMsg.setManType(0xFFFF); - - messagesCreatedToFoward.add(pcsMsg); - } - - private void processAssetReport(MessageInfo info, AssetReport msg, ArrayList messagesCreatedToFoward) { - - String reporterId = msg.getSourceName(); - String sysId = msg.getName(); - - long dataTimeMillis = Double.valueOf(msg.getReportTime() * 1000).longValue(); - - AssetReport.MEDIUM mediumReported = msg.getMedium(); - - double latRad = msg.getLat(); - double lonRad = msg.getLon(); - double depth = msg.getDepth(); - double altitude = msg.getAlt(); - - double speedMS = msg.getSog(); - double cogRads = msg.getCog(); - - ArrayList otherMsgs = Collections.list(msg.getMsgs().elements()); // TODO - - ImcSystem imcSys = ImcSystemsHolder.lookupSystemByName(sysId); - ExternalSystem extSys = null; - if (imcSys == null) { - extSys = ExternalSystemsHolder.lookupSystem(sysId); - if (extSys == null) { - extSys = new ExternalSystem(sysId); - ExternalSystemsHolder.registerSystem(extSys); - } - } - - if (Double.isFinite(latRad) && Double.isFinite(lonRad)) { - LocationType loc = new LocationType(AngleUtils.nomalizeAngleDegrees180(Math.toDegrees(latRad)), - AngleUtils.nomalizeAngleDegrees180(Math.toDegrees(lonRad))); - if (Double.isFinite(depth)) { - loc.setDepth(depth); - } - if (imcSys != null) { - imcSys.setLocation(loc, dataTimeMillis); - } else { - extSys.setLocation(loc, dataTimeMillis); - } - } - double headingRads = Double.NaN; - if (Double.isFinite(cogRads) && Double.isFinite(speedMS) && Math.abs(speedMS) > 0.2) { - headingRads = AngleUtils.nomalizeAngleRads2Pi(cogRads * (speedMS < 0 ? -1 : 1)); - if (imcSys != null) { - imcSys.setAttitudeDegrees(headingRads, dataTimeMillis); - imcSys.storeData( - SystemUtils.HEADING_DEGS_KEY, - (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(Math.toDegrees(headingRads), 0)), - dataTimeMillis, true); - } else { - extSys.setAttitudeDegrees(headingRads, dataTimeMillis); - extSys.storeData( - SystemUtils.HEADING_DEGS_KEY, - (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(Math.toDegrees(headingRads), 0)), - dataTimeMillis, true); - } - } - - if (imcSys != null) { - imcSys.storeData(SystemUtils.GROUND_SPEED_KEY, speedMS, dataTimeMillis, true); - imcSys.storeData(SystemUtils.COURSE_DEGS_KEY, - (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(Math.toDegrees(cogRads), 0)), - dataTimeMillis, true); - } else { - extSys.storeData(SystemUtils.GROUND_SPEED_KEY, speedMS, dataTimeMillis, true); - extSys.storeData(SystemUtils.COURSE_DEGS_KEY, - (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(Math.toDegrees(cogRads), 0)), - dataTimeMillis, true); - } - } - - private void processRemoteSensorInfo(MessageInfo info, RemoteSensorInfo msg) { - // Process pos. state reported from other system - String sysId = msg.getId(); - - double latRad = msg.getLat(); - double lonRad = msg.getLon(); - double altitude = msg.getAlt(); - - double headingRad = msg.getHeading(); - - String sensorClass = msg.getSensorClass(); - - long recTimeMillis = msg.getTimestampMillis(); - - ImcSystem imcSys = ImcSystemsHolder.lookupSystemByName(sysId); - ExternalSystem extSys = null; - if (imcSys == null) { - extSys = ExternalSystemsHolder.lookupSystem(sysId); - if (extSys == null) { - extSys = new ExternalSystem(sysId); - ExternalSystemsHolder.registerSystem(extSys); - } - } - - if (Double.isFinite(latRad) && Double.isFinite(lonRad)) { - LocationType loc = new LocationType(Math.toDegrees(latRad), Math.toDegrees(lonRad)); - if (Double.isFinite(altitude)) - loc.setDepth(-altitude); - - if (imcSys != null) - imcSys.setLocation(loc, recTimeMillis); - else - extSys.setLocation(loc, recTimeMillis); - } - - if (Double.isFinite(headingRad)) { - double headingDeg = Math.toDegrees(headingRad); - - if (imcSys != null) - imcSys.setAttitudeDegrees(headingDeg, recTimeMillis); - else - extSys.setAttitudeDegrees(headingDeg, recTimeMillis); - } - - // Process sensor class - SystemTypeEnum type = SystemUtils.getSystemTypeFrom(sensorClass); - VehicleTypeEnum typeVehicle = SystemUtils.getVehicleTypeFrom(sensorClass); - ExternalTypeEnum typeExternal = SystemUtils.getExternalTypeFrom(sensorClass); - if (imcSys != null) { - imcSys.setType(type); - imcSys.setTypeVehicle(typeVehicle); - } - else { - extSys.setType(type); - extSys.setTypeVehicle(typeVehicle); - extSys.setTypeExternal(typeExternal); - } - } @Override protected boolean processMsgLocally(MessageInfo info, IMCMessage msg) { @@ -1105,28 +826,28 @@ protected boolean processMsgLocally(MessageInfo info, IMCMessage msg) { switch (msg.getMgid()) { case Announce.ID_STATIC: announceLastArriveTime = System.currentTimeMillis(); - vci = processAnnounceMessage(info, (Announce) msg, vci, id); + vci = announceProcessor.processAnnounceMessage(info, (Announce) msg, vci, id); break; case EntityList.ID_STATIC: - processEntityList(id, info, (EntityList) msg); + messageProcessor.processEntityList(id, info, (EntityList) msg); break; case EntityInfo.ID_STATIC: - processEntityInfo(info, (EntityInfo) msg); + messageProcessor.processEntityInfo(info, (EntityInfo) msg); break; case MessagePart.ID_STATIC: - processMessagePart(info, (MessagePart) msg); + messageProcessor.processMessagePart(info, (MessagePart) msg); break; case ReportedState.ID_STATIC: - processReportedState(info, (ReportedState) msg); + messageProcessor.processReportedState(info, (ReportedState) msg); break; case RemoteSensorInfo.ID_STATIC: - processRemoteSensorInfo(info, (RemoteSensorInfo) msg); + messageProcessor.processRemoteSensorInfo(info, (RemoteSensorInfo) msg); break; case StateReport.ID_STATIC: - processStateReport(info, new StateReport(msg), messagesCreatedToFoward); + messageProcessor.processStateReport(info, new StateReport(msg), messagesCreatedToFoward); break; case AssetReport.ID_STATIC: - processAssetReport(info, new AssetReport(msg), messagesCreatedToFoward); + messageProcessor.processAssetReport(info, new AssetReport(msg), messagesCreatedToFoward); break; default: break; @@ -1238,301 +959,6 @@ private void postToBus(IMCMessage msg) { } } - /** - * @param info - * @param msg - * @param vci - * @param id - * @return - * @throws IOException - */ - private SystemImcMsgCommInfo processAnnounceMessage(MessageInfo info, Announce ann, SystemImcMsgCommInfo vci, - ImcId16 id) throws IOException { - - LocalTime timeStart = LocalTime.now(); - - String sia = info.getPublisherInetAddress(); - NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: publisher host address " + sia); - - boolean hostWasGuessed = true; - - InetSocketAddress[] retId = announceWorker.getImcIpsPortsFromMessageImcUdp(ann); - int portUdp = 0; - String hostUdp = ""; - boolean udpIpPortFound = false; - if (retId.length > 0) { - portUdp = retId[0].getPort(); - hostUdp = retId[0].getAddress().getHostAddress(); - } - for (InetSocketAddress add : retId) { - if (sia.equalsIgnoreCase(add.getAddress().getHostAddress())) { - if (ReachableCache.firstReachable(GeneralPreferences.imcReachabilityTestTimeout, add) != null) { - udpIpPortFound = true; - portUdp = add.getPort(); - hostUdp = add.getAddress().getHostAddress(); - hostWasGuessed = false; - NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "UDP reachable @ " + hostUdp + ":" + portUdp); - break; - } - } - } - - // Let us try know any one in the announce IPs - if (portUdp > 0 && !udpIpPortFound) { - InetSocketAddress reachableAddr = ReachableCache.firstReachable(GeneralPreferences.imcReachabilityTestTimeout, retId); - if (reachableAddr != null) { - udpIpPortFound = true; - portUdp = reachableAddr.getPort(); - hostUdp = reachableAddr.getAddress().getHostAddress(); - hostWasGuessed = false; - NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "UDP reachable @ " + hostUdp + ":" + portUdp); - } - } - - if (portUdp > 0 && !udpIpPortFound) { - // Lets try to see if we received a message from any of the IPs - String ipReceived = hostUdp.isEmpty() ? info.getPublisherInetAddress() : hostUdp; - hostWasGuessed = hostUdp.isEmpty() ? hostWasGuessed : true; - hostUdp = ipReceived; - udpIpPortFound = true; - NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "no UDP reachable using " + hostUdp + ":" + portUdp); - } - - InetSocketAddress[] retIdT = announceWorker.getImcIpsPortsFromMessageImcTcp(ann); - int portTcp = 0; - boolean tcpIpPortFound = false; - if (retIdT.length > 0) { - portTcp = retIdT[0].getPort(); - if ("".equalsIgnoreCase(hostUdp)) - hostUdp = retIdT[0].getAddress().getHostAddress(); - } - for (InetSocketAddress add : retIdT) { - if (sia.equalsIgnoreCase(add.getAddress().getHostAddress())) { - if ("".equalsIgnoreCase(hostUdp)) { - if (ReachableCache.firstReachable(GeneralPreferences.imcReachabilityTestTimeout, add) != null) { - tcpIpPortFound = true; - hostUdp = add.getAddress().getHostAddress(); - hostWasGuessed = false; - portTcp = add.getPort(); - NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "TCP reachable @ " + hostUdp + ":" + portTcp); - break; - } - else - continue; - } - portTcp = add.getPort(); - tcpIpPortFound = true; - NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "no TCP reachable using " + hostUdp + ":" + portTcp); - break; - } - } - - // Let us try know any one in the announce IPs - if (portTcp > 0 && !tcpIpPortFound) { - InetSocketAddress reachableAddr = ReachableCache.firstReachable(GeneralPreferences.imcReachabilityTestTimeout, retId); - if (reachableAddr != null) { - if ("".equalsIgnoreCase(hostUdp)) { - tcpIpPortFound = true; - hostUdp = reachableAddr.getAddress().getHostAddress(); - hostWasGuessed = false; - portTcp = reachableAddr.getPort(); - NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "TCP reachable @ " + hostUdp + ":" + portTcp); - } - portTcp = reachableAddr.getPort(); - tcpIpPortFound = true; - NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "no TCP reachable using " + hostUdp + ":" + portTcp); - } - } - - NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "using UDP@" + hostUdp - + ":" + portUdp + " and using TCP@" + hostUdp + ":" + portTcp + " with host " - + (hostWasGuessed ? "guessed" : "found")); - - boolean requestEntityList = false; - if (vci == null) { - // Create a new system - vci = initSystemCommInfo(id, info.getPublisherInetAddress() + ":" - + (portUdp == 0 ? DEFAULT_UDP_VEH_PORT : portUdp)); - updateUdpOnIpMapper(vci); - requestEntityList = true; - } - // announceWorker.processAnnouceMessage(msg); - String name = ann.getSysName(); - String type = ann.getSysType().toString(); - vci.setSystemIdName(name); - ImcSystem resSys = ImcSystemsHolder.lookupSystem(id); - // NeptusLog.pub().info("<###>......................Announce..." + name + " | " + type + " :: " + hostUdp + " " + - // portUdp); - // NeptusLog.pub().warn(ReflectionUtil.getCallerStamp()+ " ..........................| " + name + " | " + type); - if (resSys != null) { - resSys.setServicesProvided(announceWorker.getImcServicesFromMessage(ann)); - AnnounceWorker.processUidFromServices(resSys); - - // new 2012-06-23 - if (resSys.isOnIdErrorState()) { - EntitiesResolver.clearAliases(resSys.getName()); - EntitiesResolver.clearAliases(resSys.getId()); - } - - resSys.setName(name); - resSys.setType(ImcSystem.translateSystemTypeFromMessage(type)); - resSys.setTypeVehicle(ImcSystem.translateVehicleTypeFromMessage(type)); - // NeptusLog.pub().info(ReflectionUtil.getCallerStamp()+ " ------------------------| " + resSys.getName() + - // " | " + resSys.getType()); - if (portUdp != 0 && udpIpPortFound) { - resSys.setRemoteUDPPort(portUdp); - } - else { - if (resSys.getRemoteUDPPort() == 0) - resSys.setRemoteUDPPort(DEFAULT_UDP_VEH_PORT); - } - if (!"".equalsIgnoreCase(hostUdp) && !AnnounceWorker.NONE_IP.equalsIgnoreCase(hostUdp)) { -// hostWasGuessed = true; - if (AnnounceWorker.USE_REMOTE_IP.equalsIgnoreCase(hostUdp)) { - if (dontIgnoreIpSourceRequest) - resSys.setHostAddress(info.getPublisherInetAddress()); - } - else { - if ((udpIpPortFound || tcpIpPortFound) && !hostWasGuessed) { - resSys.setHostAddress(hostUdp); - } - else if (hostWasGuessed) { - boolean alreadyFound = false; - try { - Map fAddr = new LinkedHashMap<>(); - InetAddress publisherIAddr = InetAddress.getByName(sia); - byte[] pba = publisherIAddr.getAddress(); - int i = 0; - for (InetSocketAddress inetSAddr : retId) { - byte[] lta = inetSAddr.getAddress().getAddress(); - if (lta.length != pba.length) - continue; - i = 0; - for (; i < lta.length; i++) { - if (pba[i] != lta[i]) - break; - } - if (i > 0 && i <= pba.length) - fAddr.put(inetSAddr, i); - } - for (InetSocketAddress inetSAddr : retIdT) { - if (fAddr.containsKey(inetSAddr)) - continue; - byte[] lta = inetSAddr.getAddress().getAddress(); - if (lta.length != pba.length) - continue; - i = 0; - for (; i < lta.length; i++) { - if (pba[i] != lta[i]) - break; - } - if (i > 0 && i <= pba.length) - fAddr.put(inetSAddr, i); - } - - InetSocketAddress foundCandidateAddr = fAddr.keySet().stream().max((a1, a2) -> { - return fAddr.get(a1) - fAddr.get(a2); - }).orElse(null); - if (foundCandidateAddr != null) { - resSys.setHostAddress(foundCandidateAddr.getAddress().getHostAddress()); - alreadyFound = true; - } - } - catch (Exception e) { - e.printStackTrace(); - } - - if (!alreadyFound) { - String curHostAddr = resSys.getHostAddress(); - boolean currIsInAnnounce = false; - for (InetSocketAddress inetSAddr : retId) { - if (curHostAddr.equalsIgnoreCase(inetSAddr.getAddress().getHostAddress())) { - currIsInAnnounce = true; - break; - } - } - if (!currIsInAnnounce) { - for (InetSocketAddress inetSAddr : retIdT) { - if (curHostAddr.equalsIgnoreCase(inetSAddr.getAddress().getHostAddress())) { - currIsInAnnounce = true; - break; - } - } - } - - if (!currIsInAnnounce) - resSys.setHostAddress(hostUdp); - } - } - } - } - if (portTcp != 0 && tcpIpPortFound) { - resSys.setTCPOn(true); - resSys.setRemoteTCPPort(portTcp); - } - else if (portTcp == 0) { - resSys.setTCPOn(false); - } - - if (resSys.isTCPOn() && retId.length == 0) { - resSys.setUDPOn(false); - } - else { - resSys.setUDPOn(true); - } - - NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "final setup UDP@" + resSys.getHostAddress() - + ":" + resSys.getRemoteUDPPort() + " and using TCP@" + resSys.getHostAddress() + ":" + resSys.getRemoteTCPPort()); - - resSys.setOnAnnounceState(true); - - try { - double latRad = ann.getLat(); - double lonRad = ann.getLon(); - double height = ann.getHeight(); - if (latRad != 0 && lonRad != 0) { - LocationType loc = new LocationType(); - loc.setLatitudeDegs(Math.toDegrees(latRad)); - loc.setLongitudeDegs(Math.toDegrees(lonRad)); - loc.setHeight(height); - loc.setDepth(-1); - long locTime = (long) (info.getTimeSentSec() * 1000); - resSys.setLocation(loc, locTime); - } - } - catch (Exception e) { - e.printStackTrace(); - } - - // Adding temp getting heading from services - double headingDegreesFromServices = AnnounceWorker.processHeadingDegreesFromServices(resSys); - if (!Double.isNaN(headingDegreesFromServices) && !Double.isInfinite(headingDegreesFromServices)) { - long attTime = (long) (info.getTimeSentSec() * 1000); - resSys.setAttitudeDegrees(headingDegreesFromServices, attTime); - } - - Map er = EntitiesResolver.getEntities(resSys.getName()); - if (er == null || er.size() == 0) - requestEntityList = true; - - if (requestEntityList) - announceWorker.sendEntityListRequestMsg(resSys); - - ImcSystemsHolder.registerSystem(resSys); - } - - Duration deltaT = Duration.between(timeStart, LocalTime.now()); - if (deltaT.getSeconds() > 1) { - NeptusLog.pub().warn("=====!!===== Too long processing announce DF " + deltaT + " :: " + ann.getAbbrev() + - " @ " + new ImcId16(ann.getSrc()).toPrettyString() + "\n=====!!===== Try reducing " + - "'General Preference->[IMC Communications]-> Reachability Test Timeout' from " + - GeneralPreferences.imcReachabilityTestTimeout + - " to in the order of tens or 1 or 2 hundreds of ms."); - } - - imcDefinition.getResolver().addEntry(ann.getSrc(), ann.getSysName()); - return vci; - } /** * @return the sentMessagesFreqCalc @@ -1744,6 +1170,54 @@ public boolean sendMessage(IMCMessage message, ImcId16 vehicleCommId, String sen return sendMessage(message, vehicleCommId, sendProperties, null); } + /** + * This method is used to send a message to a specific system using new channels. + * Set timeoutMillis to -1 to use the default timeout. + * Leave channelsToSend empty to use the default channels. + * @return Future with the result of the send operation. + */ + public Future sendMessageUsingActiveChannel(IMCMessage message, String destinationName, int timeoutMillis, + Component parentComponentForAlert, + boolean requireUserConfirmOtherThanWifi, + String... channelsToSend) { + + //if (GeneralPreferences.imcUseNewMultiChannelCommsEnable) { + ImcSystem sys = ImcSystemsHolder.lookupSystemByName(destinationName); + if (sys == null) { + return CompletableFuture.completedFuture(SendResult.ERROR); + } + + timeoutMillis = timeoutMillis < 0 ? CommsAdmin.COMM_TIMEOUT_MILLIS : timeoutMillis; + + Future ret = commsAdmin.sendMessage(message, destinationName, timeoutMillis, + parentComponentForAlert, requireUserConfirmOtherThanWifi, channelsToSend); + return ret; + } + + public boolean sendMessageUsingActiveChannelWait(IMCMessage message, String destinationName, int timeoutMillis, + Component parentComponentForAlert, + boolean requireUserConfirmOtherThanWifi, + String... channelsToSend) { + Future ret = sendMessageUsingActiveChannel(message, destinationName, timeoutMillis, + parentComponentForAlert, requireUserConfirmOtherThanWifi, channelsToSend); + try { + SendResult sendResult = ret.get(); + switch (sendResult) { + case SUCCESS: + case UNCERTAIN_DELIVERY: + return true; + case ERROR: + case TIMEOUT: + case UNREACHABLE: + default: + return false; + } + } + catch (Exception e) { + return false; + } + } + /** * @param message The message to send * @param systemCommId The id of the destination. @@ -1751,7 +1225,7 @@ public boolean sendMessage(IMCMessage message, ImcId16 vehicleCommId, String sen * separated string values. Possible values: Multicast and/or Broadcast (if the message is sent by either * or both, it exists), and alternatively UDP or TCP (only if the Multicast and/or Broadcast are not * used). - * @param listener If you want to be warn on the send status of the message. Use null if you don't care. + * @param msgListener If you want to be warn on the send status of the message. Use null if you don't care. * @return Return true if the message went to the transport to be delivered. If you need to know if the message left * the transport use the listener. */ @@ -2001,7 +1475,8 @@ public int registerEntity(String name) throws InvalidNameException { * @param message */ private void checkAndSetMessageSrcEntity(IMCMessage message) { - if (message.getSrcEnt() == 0 || message.getSrcEnt() == IMCMessage.DEFAULT_ENTITY_ID) { + if (ImcMsgManager.getManager().getLocalId().intValue() == message.getSrc() && + (message.getSrcEnt() == 0 || message.getSrcEnt() == IMCMessage.DEFAULT_ENTITY_ID)) { String caller = getCallerClass(); if (caller != null) { diff --git a/src/java/pt/lsts/neptus/comm/manager/imc/ImcMsgManagerAnnounceProcessor.java b/src/java/pt/lsts/neptus/comm/manager/imc/ImcMsgManagerAnnounceProcessor.java new file mode 100644 index 0000000000..4ba7404ab1 --- /dev/null +++ b/src/java/pt/lsts/neptus/comm/manager/imc/ImcMsgManagerAnnounceProcessor.java @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2004-2025 Universidade do Porto - Faculdade de Engenharia + * Laboratório de Sistemas e Tecnologia Subaquática (LSTS) + * All rights reserved. + * Rua Dr. Roberto Frias s/n, sala I203, 4200-465 Porto, Portugal + * + * This file is part of Neptus, Command and Control Framework. + * + * Commercial Licence Usage + * Licencees holding valid commercial Neptus licences may use this file + * in accordance with the commercial licence agreement provided with the + * Software or, alternatively, in accordance with the terms contained in a + * written agreement between you and Universidade do Porto. For licensing + * terms, conditions, and further information contact lsts@fe.up.pt. + * + * Modified European Union Public Licence - EUPL v.1.1 Usage + * Alternatively, this file may be used under the terms of the Modified EUPL, + * Version 1.1 only (the "Licence"), appearing in the file LICENSE.md + * included in the packaging of this file. You may not use this work + * except in compliance with the Licence. Unless required by applicable + * law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the Licence for the specific + * language governing permissions and limitations at + * https://github.com/LSTS/neptus/blob/develop/LICENSE.md + * and http://ec.europa.eu/idabc/eupl.html. + * + * For more information please see . + * + * Author: Paulo Dias + * 15/4/2024 + */ +package pt.lsts.neptus.comm.manager.imc; + +import pt.lsts.imc.Announce; +import pt.lsts.neptus.NeptusLog; +import pt.lsts.neptus.messages.listener.MessageInfo; +import pt.lsts.neptus.types.coord.LocationType; +import pt.lsts.neptus.util.conf.GeneralPreferences; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.time.LocalTime; +import java.util.LinkedHashMap; +import java.util.Map; + +import static pt.lsts.neptus.comm.manager.imc.ImcMsgManager.DEFAULT_UDP_VEH_PORT; + +public class ImcMsgManagerAnnounceProcessor { + private final ImcMsgManager manager; + + public ImcMsgManagerAnnounceProcessor(ImcMsgManager manager) { + this.manager = manager; + } + + /** + * @param info + * @param ann + * @param vci + * @param id + * @return + * @throws IOException + */ + SystemImcMsgCommInfo processAnnounceMessage(MessageInfo info, Announce ann, SystemImcMsgCommInfo vci, + ImcId16 id) throws IOException { + + LocalTime timeStart = LocalTime.now(); + + String sia = info.getPublisherInetAddress(); + NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: publisher host address " + sia); + + boolean hostWasGuessed = true; + + InetSocketAddress[] retId = manager.announceWorker.getImcIpsPortsFromMessageImcUdp(ann); + int portUdp = 0; + String hostUdp = ""; + boolean udpIpPortFound = false; + if (retId.length > 0) { + portUdp = retId[0].getPort(); + hostUdp = retId[0].getAddress().getHostAddress(); + } + for (InetSocketAddress add : retId) { + if (sia.equalsIgnoreCase(add.getAddress().getHostAddress())) { + if (ReachableCache.firstReachable(GeneralPreferences.imcReachabilityTestTimeout, add) != null) { + udpIpPortFound = true; + portUdp = add.getPort(); + hostUdp = add.getAddress().getHostAddress(); + hostWasGuessed = false; + NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "UDP reachable @ " + hostUdp + ":" + portUdp); + break; + } + } + } + + // Let us try to know any one in the announce IPs + if (portUdp > 0 && !udpIpPortFound) { + InetSocketAddress reachableAddr = ReachableCache.firstReachable(GeneralPreferences.imcReachabilityTestTimeout, retId); + if (reachableAddr != null) { + udpIpPortFound = true; + portUdp = reachableAddr.getPort(); + hostUdp = reachableAddr.getAddress().getHostAddress(); + hostWasGuessed = false; + NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "UDP reachable @ " + hostUdp + ":" + portUdp); + } + } + + if (portUdp > 0 && !udpIpPortFound) { + // Let's try to see if we received a message from any of the IPs + String ipReceived = hostUdp.isEmpty() ? info.getPublisherInetAddress() : hostUdp; + hostWasGuessed = hostUdp.isEmpty() ? hostWasGuessed : true; + hostUdp = ipReceived; + udpIpPortFound = true; + NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "no UDP reachable using " + hostUdp + ":" + portUdp); + } + + InetSocketAddress[] retIdT = manager.announceWorker.getImcIpsPortsFromMessageImcTcp(ann); + int portTcp = 0; + boolean tcpIpPortFound = false; + if (retIdT.length > 0) { + portTcp = retIdT[0].getPort(); + if ("".equalsIgnoreCase(hostUdp)) + hostUdp = retIdT[0].getAddress().getHostAddress(); + } + for (InetSocketAddress add : retIdT) { + if (sia.equalsIgnoreCase(add.getAddress().getHostAddress())) { + if ("".equalsIgnoreCase(hostUdp)) { + if (ReachableCache.firstReachable(GeneralPreferences.imcReachabilityTestTimeout, add) != null) { + tcpIpPortFound = true; + hostUdp = add.getAddress().getHostAddress(); + hostWasGuessed = false; + portTcp = add.getPort(); + NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "TCP reachable @ " + hostUdp + ":" + portTcp); + break; + } + else + continue; + } + portTcp = add.getPort(); + tcpIpPortFound = true; + NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "no TCP reachable using " + hostUdp + ":" + portTcp); + break; + } + } + + // Let us try to know any one in the announce IPs + if (portTcp > 0 && !tcpIpPortFound) { + InetSocketAddress reachableAddr = ReachableCache.firstReachable(GeneralPreferences.imcReachabilityTestTimeout, retId); + if (reachableAddr != null) { + if ("".equalsIgnoreCase(hostUdp)) { + tcpIpPortFound = true; + hostUdp = reachableAddr.getAddress().getHostAddress(); + hostWasGuessed = false; + portTcp = reachableAddr.getPort(); + NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "TCP reachable @ " + hostUdp + ":" + portTcp); + } + portTcp = reachableAddr.getPort(); + tcpIpPortFound = true; + NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "no TCP reachable using " + hostUdp + ":" + portTcp); + } + } + + NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "using UDP@" + hostUdp + + ":" + portUdp + " and using TCP@" + hostUdp + ":" + portTcp + " with host " + + (hostWasGuessed ? "guessed" : "found")); + + boolean requestEntityList = false; + if (vci == null) { + // Create a new system + vci = manager.initSystemCommInfo(id, info.getPublisherInetAddress() + ":" + + (portUdp == 0 ? DEFAULT_UDP_VEH_PORT : portUdp)); + manager.updateUdpOnIpMapper(vci); + requestEntityList = true; + } + // announceWorker.processAnnouceMessage(msg); + String name = ann.getSysName(); + String type = ann.getSysType().toString(); + vci.setSystemIdName(name); + ImcSystem resSys = ImcSystemsHolder.lookupSystem(id); + // NeptusLog.pub().info("<###>......................Announce..." + name + " | " + type + " :: " + hostUdp + " " + + // portUdp); + // NeptusLog.pub().warn(ReflectionUtil.getCallerStamp()+ " ..........................| " + name + " | " + type); + if (resSys != null) { + resSys.setServicesProvided(manager.announceWorker.getImcServicesFromMessage(ann)); + AnnounceWorker.processUidFromServices(resSys); + + // new 2012-06-23 + if (resSys.isOnIdErrorState()) { + EntitiesResolver.clearAliases(resSys.getName()); + EntitiesResolver.clearAliases(resSys.getId()); + } + + resSys.setName(name); + resSys.setType(ImcSystem.translateSystemTypeFromMessage(type)); + resSys.setTypeVehicle(ImcSystem.translateVehicleTypeFromMessage(type)); + // NeptusLog.pub().info(ReflectionUtil.getCallerStamp()+ " ------------------------| " + resSys.getName() + + // " | " + resSys.getType()); + if (portUdp != 0 && udpIpPortFound) { + resSys.setRemoteUDPPort(portUdp); + } + else { + if (resSys.getRemoteUDPPort() == 0) + resSys.setRemoteUDPPort(DEFAULT_UDP_VEH_PORT); + } + if (!"".equalsIgnoreCase(hostUdp) && !AnnounceWorker.NONE_IP.equalsIgnoreCase(hostUdp)) { +// hostWasGuessed = true; + if (AnnounceWorker.USE_REMOTE_IP.equalsIgnoreCase(hostUdp)) { + if (manager.dontIgnoreIpSourceRequest) + resSys.setHostAddress(info.getPublisherInetAddress()); + } + else { + if ((udpIpPortFound || tcpIpPortFound) && !hostWasGuessed) { + resSys.setHostAddress(hostUdp); + } + else if (hostWasGuessed) { + boolean alreadyFound = false; + try { + Map fAddr = new LinkedHashMap<>(); + InetAddress publisherIAddr = InetAddress.getByName(sia); + byte[] pba = publisherIAddr.getAddress(); + int i = 0; + for (InetSocketAddress inetSAddr : retId) { + byte[] lta = inetSAddr.getAddress().getAddress(); + if (lta.length != pba.length) + continue; + i = 0; + for (; i < lta.length; i++) { + if (pba[i] != lta[i]) + break; + } + if (i > 0 && i <= pba.length) + fAddr.put(inetSAddr, i); + } + for (InetSocketAddress inetSAddr : retIdT) { + if (fAddr.containsKey(inetSAddr)) + continue; + byte[] lta = inetSAddr.getAddress().getAddress(); + if (lta.length != pba.length) + continue; + i = 0; + for (; i < lta.length; i++) { + if (pba[i] != lta[i]) + break; + } + if (i > 0 && i <= pba.length) + fAddr.put(inetSAddr, i); + } + + InetSocketAddress foundCandidateAddr = fAddr.keySet().stream().max((a1, a2) -> { + return fAddr.get(a1) - fAddr.get(a2); + }).orElse(null); + if (foundCandidateAddr != null) { + resSys.setHostAddress(foundCandidateAddr.getAddress().getHostAddress()); + alreadyFound = true; + } + } + catch (Exception e) { + e.printStackTrace(); + } + + if (!alreadyFound) { + String curHostAddr = resSys.getHostAddress(); + boolean currIsInAnnounce = false; + for (InetSocketAddress inetSAddr : retId) { + if (curHostAddr.equalsIgnoreCase(inetSAddr.getAddress().getHostAddress())) { + currIsInAnnounce = true; + break; + } + } + if (!currIsInAnnounce) { + for (InetSocketAddress inetSAddr : retIdT) { + if (curHostAddr.equalsIgnoreCase(inetSAddr.getAddress().getHostAddress())) { + currIsInAnnounce = true; + break; + } + } + } + + if (!currIsInAnnounce) + resSys.setHostAddress(hostUdp); + } + } + } + } + if (portTcp != 0 && tcpIpPortFound) { + resSys.setTCPOn(true); + resSys.setRemoteTCPPort(portTcp); + } + else if (portTcp == 0) { + resSys.setTCPOn(false); + } + + if (resSys.isTCPOn() && retId.length == 0) { + resSys.setUDPOn(false); + } + else { + resSys.setUDPOn(true); + } + + NeptusLog.pub().debug("processAnnounceMessage for " + ann.getSysName() + "@" + id + " :: " + "final setup UDP@" + resSys.getHostAddress() + + ":" + resSys.getRemoteUDPPort() + " and using TCP@" + resSys.getHostAddress() + ":" + resSys.getRemoteTCPPort()); + + resSys.setOnAnnounceState(true); + + try { + double latRad = ann.getLat(); + double lonRad = ann.getLon(); + double height = ann.getHeight(); + if (latRad != 0 && lonRad != 0) { + LocationType loc = new LocationType(); + loc.setLatitudeDegs(Math.toDegrees(latRad)); + loc.setLongitudeDegs(Math.toDegrees(lonRad)); + loc.setHeight(height); + loc.setDepth(-1); + long locTime = (long) (info.getTimeSentSec() * 1000); + resSys.setLocation(loc, locTime); + } + } + catch (Exception e) { + e.printStackTrace(); + } + + // Adding temp getting heading from services + double headingDegreesFromServices = AnnounceWorker.processHeadingDegreesFromServices(resSys); + if (!Double.isNaN(headingDegreesFromServices) && !Double.isInfinite(headingDegreesFromServices)) { + long attTime = (long) (info.getTimeSentSec() * 1000); + resSys.setAttitudeDegrees(headingDegreesFromServices, attTime); + } + + Map er = EntitiesResolver.getEntities(resSys.getName()); + if (er == null || er.isEmpty()) + requestEntityList = true; + + if (requestEntityList) + manager.announceWorker.sendEntityListRequestMsg(resSys); + + ImcSystemsHolder.registerSystem(resSys); + } + + Duration deltaT = Duration.between(timeStart, LocalTime.now()); + if (deltaT.getSeconds() > 1) { + NeptusLog.pub().warn("=====!!===== Too long processing announce DF " + deltaT + " :: " + ann.getAbbrev() + + " @ " + new ImcId16(ann.getSrc()).toPrettyString() + "\n=====!!===== Try reducing " + + "'General Preference->[IMC Communications]-> Reachability Test Timeout' from " + + GeneralPreferences.imcReachabilityTestTimeout + + " to in the order of tens or 1 or 2 hundreds of ms."); + } + + manager.imcDefinition.getResolver().addEntry(ann.getSrc(), ann.getSysName()); + return vci; + } +} diff --git a/src/java/pt/lsts/neptus/comm/manager/imc/ImcMsgManagerMessageProcessor.java b/src/java/pt/lsts/neptus/comm/manager/imc/ImcMsgManagerMessageProcessor.java new file mode 100644 index 0000000000..6c2c98eda4 --- /dev/null +++ b/src/java/pt/lsts/neptus/comm/manager/imc/ImcMsgManagerMessageProcessor.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2004-2025 Universidade do Porto - Faculdade de Engenharia + * Laboratório de Sistemas e Tecnologia Subaquática (LSTS) + * All rights reserved. + * Rua Dr. Roberto Frias s/n, sala I203, 4200-465 Porto, Portugal + * + * This file is part of Neptus, Command and Control Framework. + * + * Commercial Licence Usage + * Licencees holding valid commercial Neptus licences may use this file + * in accordance with the commercial licence agreement provided with the + * Software or, alternatively, in accordance with the terms contained in a + * written agreement between you and Universidade do Porto. For licensing + * terms, conditions, and further information contact lsts@fe.up.pt. + * + * Modified European Union Public Licence - EUPL v.1.1 Usage + * Alternatively, this file may be used under the terms of the Modified EUPL, + * Version 1.1 only (the "Licence"), appearing in the file LICENSE.md + * included in the packaging of this file. You may not use this work + * except in compliance with the Licence. Unless required by applicable + * law or agreed to in writing, software distributed under the Licence is + * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the Licence for the specific + * language governing permissions and limitations at + * https://github.com/LSTS/neptus/blob/develop/LICENSE.md + * and http://ec.europa.eu/idabc/eupl.html. + * + * For more information please see . + * + * Author: Paulo Dias + * 15/4/2024 + */ +package pt.lsts.neptus.comm.manager.imc; + +import pt.lsts.imc.AssetReport; +import pt.lsts.imc.EntityInfo; +import pt.lsts.imc.EntityList; +import pt.lsts.imc.FuelLevel; +import pt.lsts.imc.IMCMessage; +import pt.lsts.imc.MessagePart; +import pt.lsts.imc.PlanControlState; +import pt.lsts.imc.RemoteSensorInfo; +import pt.lsts.imc.ReportedState; +import pt.lsts.imc.StateReport; +import pt.lsts.imc.net.IMCFragmentHandler; +import pt.lsts.neptus.NeptusLog; +import pt.lsts.neptus.comm.IMCUtils; +import pt.lsts.neptus.comm.SystemUtils; +import pt.lsts.neptus.messages.listener.MessageInfo; +import pt.lsts.neptus.systems.external.ExternalSystem; +import pt.lsts.neptus.systems.external.ExternalSystemsHolder; +import pt.lsts.neptus.types.coord.LocationType; +import pt.lsts.neptus.types.vehicle.VehicleType; +import pt.lsts.neptus.util.AngleUtils; +import pt.lsts.neptus.util.MathMiscUtils; + +import java.util.ArrayList; +import java.util.Collections; + +class ImcMsgManagerMessageProcessor { + private final ImcMsgManager manager; + private final IMCFragmentHandler fragmentHandler; + + public ImcMsgManagerMessageProcessor(ImcMsgManager manager) { + this.manager = manager; + fragmentHandler = new IMCFragmentHandler(manager.imcDefinition); + } + + void processEntityInfo(MessageInfo info, EntityInfo msg) { + manager.imcDefinition.getResolver().setEntityName(msg.getSrc(), msg.getSrcEnt(), msg.getLabel()); + } + + void processMessagePart(MessageInfo info, MessagePart msg) { + IMCMessage m = fragmentHandler.setFragment((MessagePart)msg); + if (m != null) + manager.postInternalMessage(msg.getSourceName(), m); + } + + void processEntityList(ImcId16 id, MessageInfo info, EntityList msg) { + EntitiesResolver.setEntities(id.toString(), msg); + manager.imcDefinition.getResolver().setEntityMap(msg.getSrc(), msg.getList()); + ImcSystem sys = ImcSystemsHolder.lookupSystem(id); + if (sys != null) { + EntitiesResolver.setEntities(sys.getName(), msg); + } + } + + void processReportedState(MessageInfo info, ReportedState msg) { + // Process pos. state reported from other system + String sysId = msg.getSid(); + + double latRad = msg.getLat(); + double lonRad = msg.getLon(); + double depth = msg.getDepth(); + + double rollRad = msg.getRoll(); + double pitchRad = msg.getPitch(); + double yawRad = msg.getYaw(); + + double recTimeSecs = msg.getRcpTime(); + long recTimeMillis = Double.isFinite(recTimeSecs) ? (long) (recTimeSecs * 1E3) : msg.getTimestampMillis(); + + // msg.getSType(); // Not used + + ImcSystem imcSys = ImcSystemsHolder.lookupSystemByName(sysId); + ExternalSystem extSys = null; + if (imcSys == null) { + extSys = ExternalSystemsHolder.lookupSystem(sysId); + if (extSys == null) { + extSys = new ExternalSystem(sysId); + ExternalSystemsHolder.registerSystem(extSys); + } + } + + if (Double.isFinite(latRad) && Double.isFinite(lonRad)) { + LocationType loc = new LocationType(Math.toDegrees(latRad), Math.toDegrees(lonRad)); + if (Double.isFinite(depth)) + loc.setDepth(depth); + + if (imcSys != null) + imcSys.setLocation(loc, recTimeMillis); + else + extSys.setLocation(loc, recTimeMillis); + } + + if (Double.isFinite(rollRad) || Double.isFinite(pitchRad) || Double.isFinite(yawRad)) { + double rollDeg = Double.isFinite(rollRad) ? Math.toDegrees(rollRad) : 0; + double pitchDeg = Double.isFinite(pitchRad) ? Math.toDegrees(pitchRad) : 0; + double yawDeg = Double.isFinite(yawRad) ? Math.toDegrees(yawRad) : 0; + + if (imcSys != null) + imcSys.setAttitudeDegrees(rollDeg, pitchDeg, yawDeg, recTimeMillis); + else + extSys.setAttitudeDegrees(rollDeg, pitchDeg, yawDeg, recTimeMillis); + } + } + + void processStateReport(MessageInfo info, StateReport msg, ArrayList messagesCreatedToFoward) { + + String sysId = msg.getSourceName(); + + long dataTimeMillis = msg.getStime() * 1000; + + double lat = msg.getLatitude(); + double lon = msg.getLongitude(); + double depth = msg.getDepth() == 0xFFFF ? -1 : msg.getDepth() / 10.0; + // double altitude = msg.getAltitude() == 0xFFFF ? -1 : msg.getAltitude() / 10.0; + double heading = Math.toDegrees(AngleUtils.nomalizeAngleRads2Pi(((double) msg.getHeading() / 65535.0) * Math.PI * 2)); + double speedMS = msg.getSpeed() / 100.; + NeptusLog.pub().info("Received report from "+msg.getSourceName()); + + ImcSystem imcSys = ImcSystemsHolder.lookupSystemByName(sysId); + if (imcSys == null) { + NeptusLog.pub().error("Could not find system with id "+sysId); + return; + } + + LocationType loc = new LocationType(lat, lon); + loc.setDepth(depth); + imcSys.setLocation(loc, dataTimeMillis); + imcSys.setAttitudeDegrees(heading, dataTimeMillis); + + imcSys.storeData(SystemUtils.GROUND_SPEED_KEY, speedMS, dataTimeMillis, true); + imcSys.storeData(SystemUtils.COURSE_DEGS_KEY, + (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(heading, 0)), + dataTimeMillis, true); + imcSys.storeData( + SystemUtils.HEADING_DEGS_KEY, + (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(heading, 0)), + dataTimeMillis, true); + + int fuelPerc = msg.getFuel(); + if (fuelPerc > 0) { + FuelLevel fuelLevelMsg = new FuelLevel(); + IMCUtils.copyHeader(msg, fuelLevelMsg); + fuelLevelMsg.setTimestampMillis(dataTimeMillis); + fuelLevelMsg.setValue(fuelPerc); + fuelLevelMsg.setConfidence(0); + imcSys.storeData(SystemUtils.FUEL_LEVEL_KEY, fuelLevelMsg, dataTimeMillis, true); + + messagesCreatedToFoward.add(fuelLevelMsg); + } + + int execState = msg.getExecState(); + PlanControlState pcsMsg = new PlanControlState(); + IMCUtils.copyHeader(msg, pcsMsg); + pcsMsg.setTimestampMillis(dataTimeMillis); + switch (execState) { + case -1: + pcsMsg.setState(PlanControlState.STATE.READY); + break; + case -3: + pcsMsg.setState(PlanControlState.STATE.INITIALIZING); + break; + case -2: + case -4: + pcsMsg.setState(PlanControlState.STATE.BLOCKED); + break; + default: + if (execState > 0) + pcsMsg.setState(PlanControlState.STATE.EXECUTING); + else + pcsMsg.setState(PlanControlState.STATE.BLOCKED); + break; + } + + pcsMsg.setPlanEta(-1); + pcsMsg.setPlanProgress(execState >= 0 ? execState : -1); + pcsMsg.setManId(""); + pcsMsg.setManEta(-1); + pcsMsg.setManType(0xFFFF); + + messagesCreatedToFoward.add(pcsMsg); + } + + void processAssetReport(MessageInfo info, AssetReport msg, ArrayList messagesCreatedToFoward) { + + String reporterId = msg.getSourceName(); + String sysId = msg.getName(); + + long dataTimeMillis = Double.valueOf(msg.getReportTime() * 1000).longValue(); + + AssetReport.MEDIUM mediumReported = msg.getMedium(); + + double latRad = msg.getLat(); + double lonRad = msg.getLon(); + double depth = msg.getDepth(); + double altitude = msg.getAlt(); + + double speedMS = msg.getSog(); + double cogRads = msg.getCog(); + + ArrayList otherMsgs = Collections.list(msg.getMsgs().elements()); // TODO + + ImcSystem imcSys = ImcSystemsHolder.lookupSystemByName(sysId); + ExternalSystem extSys = null; + if (imcSys == null) { + extSys = ExternalSystemsHolder.lookupSystem(sysId); + if (extSys == null) { + extSys = new ExternalSystem(sysId); + ExternalSystemsHolder.registerSystem(extSys); + } + } + + if (Double.isFinite(latRad) && Double.isFinite(lonRad)) { + LocationType loc = new LocationType(AngleUtils.nomalizeAngleDegrees180(Math.toDegrees(latRad)), + AngleUtils.nomalizeAngleDegrees180(Math.toDegrees(lonRad))); + if (Double.isFinite(depth)) { + loc.setDepth(depth); + } + if (imcSys != null) { + imcSys.setLocation(loc, dataTimeMillis); + } else { + extSys.setLocation(loc, dataTimeMillis); + } + } + double headingRads = Double.NaN; + if (Double.isFinite(cogRads) && Double.isFinite(speedMS) && Math.abs(speedMS) > 0.2) { + headingRads = AngleUtils.nomalizeAngleRads2Pi(cogRads * (speedMS < 0 ? -1 : 1)); + if (imcSys != null) { + imcSys.setAttitudeDegrees(headingRads, dataTimeMillis); + imcSys.storeData( + SystemUtils.HEADING_DEGS_KEY, + (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(Math.toDegrees(headingRads), 0)), + dataTimeMillis, true); + } else { + extSys.setAttitudeDegrees(headingRads, dataTimeMillis); + extSys.storeData( + SystemUtils.HEADING_DEGS_KEY, + (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(Math.toDegrees(headingRads), 0)), + dataTimeMillis, true); + } + } + + if (imcSys != null) { + imcSys.storeData(SystemUtils.GROUND_SPEED_KEY, speedMS, dataTimeMillis, true); + imcSys.storeData(SystemUtils.COURSE_DEGS_KEY, + (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(Math.toDegrees(cogRads), 0)), + dataTimeMillis, true); + } else { + extSys.storeData(SystemUtils.GROUND_SPEED_KEY, speedMS, dataTimeMillis, true); + extSys.storeData(SystemUtils.COURSE_DEGS_KEY, + (int) AngleUtils.nomalizeAngleDegrees360(MathMiscUtils.round(Math.toDegrees(cogRads), 0)), + dataTimeMillis, true); + } + } + + void processRemoteSensorInfo(MessageInfo info, RemoteSensorInfo msg) { + // Process pos. state reported from other system + String sysId = msg.getId(); + + double latRad = msg.getLat(); + double lonRad = msg.getLon(); + double altitude = msg.getAlt(); + + double headingRad = msg.getHeading(); + + String sensorClass = msg.getSensorClass(); + + long recTimeMillis = msg.getTimestampMillis(); + + ImcSystem imcSys = ImcSystemsHolder.lookupSystemByName(sysId); + ExternalSystem extSys = null; + if (imcSys == null) { + extSys = ExternalSystemsHolder.lookupSystem(sysId); + if (extSys == null) { + extSys = new ExternalSystem(sysId); + ExternalSystemsHolder.registerSystem(extSys); + } + } + + if (Double.isFinite(latRad) && Double.isFinite(lonRad)) { + LocationType loc = new LocationType(Math.toDegrees(latRad), Math.toDegrees(lonRad)); + if (Double.isFinite(altitude)) + loc.setDepth(-altitude); + + if (imcSys != null) + imcSys.setLocation(loc, recTimeMillis); + else + extSys.setLocation(loc, recTimeMillis); + } + + if (Double.isFinite(headingRad)) { + double headingDeg = Math.toDegrees(headingRad); + + if (imcSys != null) + imcSys.setAttitudeDegrees(headingDeg, recTimeMillis); + else + extSys.setAttitudeDegrees(headingDeg, recTimeMillis); + } + + // Process sensor class + VehicleType.SystemTypeEnum type = SystemUtils.getSystemTypeFrom(sensorClass); + VehicleType.VehicleTypeEnum typeVehicle = SystemUtils.getVehicleTypeFrom(sensorClass); + ExternalSystem.ExternalTypeEnum typeExternal = SystemUtils.getExternalTypeFrom(sensorClass); + if (imcSys != null) { + imcSys.setType(type); + imcSys.setTypeVehicle(typeVehicle); + } + else { + extSys.setType(type); + extSys.setTypeVehicle(typeVehicle); + extSys.setTypeExternal(typeExternal); + } + } +} diff --git a/src/java/pt/lsts/neptus/console/ConsolePanel.java b/src/java/pt/lsts/neptus/console/ConsolePanel.java index c62593d87e..ee546b04e0 100644 --- a/src/java/pt/lsts/neptus/console/ConsolePanel.java +++ b/src/java/pt/lsts/neptus/console/ConsolePanel.java @@ -832,7 +832,7 @@ public boolean send(IMCMessage message) { return send(destination, message); } - public void sendViaIridium(String destination, IMCMessage message) { + public boolean sendViaIridium(String destination, IMCMessage message) { if (message.getTimestamp() == 0) message.setTimestampMillis(System.currentTimeMillis()); Collection irMsgs = new ArrayList(); @@ -841,7 +841,7 @@ public void sendViaIridium(String destination, IMCMessage message) { } catch (Exception e) { GuiUtils.errorMessage(getConsole(), "Send by Iridium", e.getMessage()); - return; + return false; } int src = getConsole().getImcMsgManager().getLocalId().intValue(); int dst = IMCDefinition.getInstance().getResolver().resolve(destination); @@ -860,10 +860,11 @@ public void sendViaIridium(String destination, IMCMessage message) { getConsole().post(Notification.success("Iridium message sent", count + " Iridium messages were sent using " + IridiumManager.getManager().getCurrentMessenger().getName())); + return true; } catch (Exception e) { GuiUtils.errorMessage(getConsole(), "Send by Iridium", e.getMessage()); - return; + return false; } } diff --git a/src/java/pt/lsts/neptus/console/plugins/AbortPanel.java b/src/java/pt/lsts/neptus/console/plugins/AbortPanel.java index bc1722baff..026c9e47bb 100644 --- a/src/java/pt/lsts/neptus/console/plugins/AbortPanel.java +++ b/src/java/pt/lsts/neptus/console/plugins/AbortPanel.java @@ -58,8 +58,11 @@ import pt.lsts.imc.Abort; import pt.lsts.imc.IMCMessage; import pt.lsts.neptus.NeptusLog; +import pt.lsts.neptus.comm.IMCSendMessageUtils; +import pt.lsts.neptus.comm.manager.imc.ImcMsgManager; import pt.lsts.neptus.comm.manager.imc.ImcSystem; import pt.lsts.neptus.comm.manager.imc.ImcSystemsHolder; +import pt.lsts.neptus.comm.manager.imc.MessageDeliveryListener; import pt.lsts.neptus.console.ConsoleLayout; import pt.lsts.neptus.console.ConsolePanel; import pt.lsts.neptus.console.notifications.Notification; @@ -249,7 +252,11 @@ public void actionPerformed(final ActionEvent e) { SwingWorker worker = new SwingWorker() { @Override protected Void doInBackground() throws Exception { - send(new IMCMessage("Abort")); + // send(new IMCMessage("Abort")); + IMCSendMessageUtils.sendMessage(new IMCMessage("Abort"), ImcMsgManager.TRANSPORT_TCP, + createDefaultMessageDeliveryListener(), getConsole(), "", true, + "", true, true, true, + getConsole().getMainSystem()); boolean sentToAll = false; if ((e.getModifiers() & ActionEvent.CTRL_MASK) != 0) { @@ -318,6 +325,48 @@ protected void done() { return abortAction; } + private MessageDeliveryListener createDefaultMessageDeliveryListener() { + return new MessageDeliveryListener() { + private String getDest(IMCMessage message) { + ImcSystem sys = message != null ? ImcSystemsHolder.lookupSystem(message.getDst()) : null; + String dest = sys != null ? sys.getName() : I18n.text("unknown destination"); + return dest; + } + + @Override + public void deliveryUnreacheable(IMCMessage message) { + post(Notification.error( + I18n.text("Delivering Message"), + I18n.textf("Message %messageType to %destination delivery destination unreacheable", + message.getAbbrev(), getDest(message)))); + } + + @Override + public void deliveryTimeOut(IMCMessage message) { + post(Notification.error( + I18n.text("Delivering Message"), + I18n.textf("Message %messageType to %destination delivery timeout", + message.getAbbrev(), getDest(message)))); + } + + @Override + public void deliveryError(IMCMessage message, Object error) { + post(Notification.error( + I18n.text("Delivering Message"), + I18n.textf("Message %messageType to %destination delivery error. (%error)", + message.getAbbrev(), getDest(message), error))); + } + + @Override + public void deliveryUncertain(IMCMessage message, Object msg) { + } + + @Override + public void deliverySuccess(IMCMessage message) { + } + }; + } + @Override public boolean isLocked() { return !getAbortButton().isEnabled(); diff --git a/src/java/pt/lsts/neptus/console/plugins/EntityStatePanel.java b/src/java/pt/lsts/neptus/console/plugins/EntityStatePanel.java index 7190f26150..fea16d4e4e 100644 --- a/src/java/pt/lsts/neptus/console/plugins/EntityStatePanel.java +++ b/src/java/pt/lsts/neptus/console/plugins/EntityStatePanel.java @@ -38,6 +38,7 @@ import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; +import java.time.Duration; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Timer; @@ -57,12 +58,19 @@ import com.google.common.eventbus.Subscribe; +import pt.lsts.imc.EntityList; import pt.lsts.imc.IMCMessage; import pt.lsts.neptus.NeptusLog; +import pt.lsts.neptus.comm.IMCSendMessageUtils; import pt.lsts.neptus.comm.manager.imc.EntitiesResolver; +import pt.lsts.neptus.comm.manager.imc.ImcMsgManager; +import pt.lsts.neptus.comm.manager.imc.ImcSystem; +import pt.lsts.neptus.comm.manager.imc.ImcSystemsHolder; +import pt.lsts.neptus.comm.manager.imc.MessageDeliveryListener; import pt.lsts.neptus.console.ConsoleLayout; import pt.lsts.neptus.console.ConsolePanel; import pt.lsts.neptus.console.events.ConsoleEventMainSystemChange; +import pt.lsts.neptus.console.notifications.Notification; import pt.lsts.neptus.gui.StatusLed; import pt.lsts.neptus.gui.ToolbarButton; import pt.lsts.neptus.i18n.I18n; @@ -73,6 +81,7 @@ import pt.lsts.neptus.plugins.Popup.POSITION; import pt.lsts.neptus.util.DateTimeUtil; import pt.lsts.neptus.util.ImageUtils; +import pt.lsts.neptus.util.speech.SpeechUtil; /** * @author pdias @@ -91,6 +100,7 @@ public class EntityStatePanel extends ConsolePanel implements NeptusMessageListe private final Color COLOR_RED = Color.RED; private final Icon ICON_CLEAR = ImageUtils.getScaledIcon("images/buttons/clear.png", 16, 16); + private final Icon ICON_RQST = ImageUtils.getScaledIcon("images/buttons/log.png", 16, 16); // Events Data private LinkedHashMap dataMap = new LinkedHashMap(); @@ -105,6 +115,8 @@ public class EntityStatePanel extends ConsolePanel implements NeptusMessageListe private JTable table = null; private StatusLed status; + private long timeSinceLastUpdateVoiceWarning = -1; + /** * @param console */ @@ -160,6 +172,13 @@ public void actionPerformed(ActionEvent e) { } }); clearButton.setToolTipText(I18n.text("Clear table")); + ToolbarButton rqstEntListButton = new ToolbarButton(new AbstractAction("request", ICON_RQST) { + @Override + public void actionPerformed(ActionEvent e) { + sendEntityListRequestMsg();; + } + }); + rqstEntListButton.setToolTipText(I18n.text("Request entity list")); status = new StatusLed(); status.made5LevelIndicator(); status.setLevel(StatusLed.LEVEL_OFF); @@ -167,6 +186,7 @@ public void actionPerformed(ActionEvent e) { wPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); wPanel.add(status); wPanel.add(clearButton); + wPanel.add(rqstEntListButton); this.add(wPanel, BorderLayout.NORTH); } @@ -358,11 +378,14 @@ public void messageArrived(IMCMessage message) { EntityStateType eType = dataMap.get(entityName); Integer index = eType == null ? null : data.indexOf(eType); + boolean wasChange = false; + // Updating (not the first time receiving for this entity) if (index != null) { eType = data.get(index); if (message.getLong("state") != eType.getState().longValue()) { // Means it has changed, time to post a -// msg_type type = msg_type.info; + // msg_type type = msg_type.info; + wasChange = true; } eType.update(entityName, new Enumerated(message.getMessageType().getFieldPossibleValues("state"), message.getLong("state")), message.getString("description"), System.currentTimeMillis()); @@ -370,6 +393,7 @@ public void messageArrived(IMCMessage message) { etmodel.fireTableRowsUpdated(index, index); } else { + wasChange = true; eType = new EntityStateType(entityName, new Enumerated(message.getMessageType().getFieldPossibleValues( "state"), message.getLong("state")), getDescription(), System.currentTimeMillis()); @@ -380,9 +404,83 @@ public void messageArrived(IMCMessage message) { } } calcTotalState(); + + if (wasChange) + speakUpdateEntityState(); } } + void sendEntityListRequestMsg() { + try { + NeptusLog.pub().debug("Sending '" + getConsole().getMainSystem() + " | " + + " EntityList request..."); + EntityList msg = new EntityList(); + msg.setOp(EntityList.OP.QUERY); + boolean ret = IMCSendMessageUtils.sendMessage(msg, ImcMsgManager.TRANSPORT_TCP, + createDefaultMessageDeliveryListener(), this, I18n.text("Error requesting EntityList"), + true, "", true, true, true, + getConsole().getMainSystem()); + } + catch (Exception e) { + NeptusLog.pub().warn(e); + } + } + + private synchronized void speakUpdateEntityState() { + if (System.currentTimeMillis() - timeSinceLastUpdateVoiceWarning > Duration.ofSeconds(10).toMillis()) { + timeSinceLastUpdateVoiceWarning = System.currentTimeMillis(); + String msg = I18n.text("Entity state"); + SpeechUtil.readSimpleText(msg); + } + } + + private MessageDeliveryListener createDefaultMessageDeliveryListener() { + return (new MessageDeliveryListener() { + + private String getDest(IMCMessage message) { + ImcSystem sys = message != null ? ImcSystemsHolder.lookupSystem(message.getDst()) : null; + String dest = sys != null ? sys.getName() : I18n.text("unknown destination"); + return dest; + } + + @Override + public void deliveryUnreacheable(IMCMessage message) { + post(Notification.error( + I18n.text("Delivering Message"), + I18n.textf("Message %messageType to %destination delivery destination unreacheable", + message.getAbbrev(), getDest(message)))); + } + + @Override + public void deliveryTimeOut(IMCMessage message) { + post(Notification.error( + I18n.text("Delivering Message"), + I18n.textf("Message %messageType to %destination delivery timeout", + message.getAbbrev(), getDest(message)))); + } + + @Override + public void deliveryError(IMCMessage message, Object error) { + post(Notification.error( + I18n.text("Delivering Message"), + I18n.textf("Message %messageType to %destination delivery error. (%error)", + message.getAbbrev(), getDest(message), error))); + } + + @Override + public void deliveryUncertain(IMCMessage message, Object msg) { + } + + @Override + public void deliverySuccess(IMCMessage message) { + // post(Notification.success( + // I18n.text("Delivering Message"), + // I18n.textf("Message %messageType to %destination delivery success", + // message.getAbbrev(), getDest(message)))); + } + }); + } + /** * This is for representing an {@link EntityStateType} in a JTable. * diff --git a/src/java/pt/lsts/neptus/console/plugins/planning/MissionTreePanel.java b/src/java/pt/lsts/neptus/console/plugins/planning/MissionTreePanel.java index d76375553c..41848150ff 100644 --- a/src/java/pt/lsts/neptus/console/plugins/planning/MissionTreePanel.java +++ b/src/java/pt/lsts/neptus/console/plugins/planning/MissionTreePanel.java @@ -63,6 +63,8 @@ import pt.lsts.imc.PlanControlState; import pt.lsts.imc.PlanControlState.STATE; import pt.lsts.imc.PlanSpecification; +import pt.lsts.imc.StateReport; +import pt.lsts.imc.state.ImcSystemState; import pt.lsts.neptus.NeptusLog; import pt.lsts.neptus.comm.IMCSendMessageUtils; import pt.lsts.neptus.comm.IMCUtils; @@ -129,7 +131,7 @@ public class MissionTreePanel extends ConsolePanel private int maxAcceptableElapsedTime = 600; @NeptusProperty(name = "Plan Names to Automatically Accept Updates", description = "Comma separated values. This upon reception of a plan " + "with the same name as the ones listed here, it will be automatically accepted the changes and replace it.") - private String planNamesToAutoAcceptUpdates = "teleoperation-mode, service_loiter"; + private String planNamesToAutoAcceptUpdates = "teleoperation-mode, service_loiter, emergency_autonaut"; private MissionTreeMouse mouseAdapter; private boolean running = false; @@ -137,6 +139,8 @@ public class MissionTreePanel extends ConsolePanel protected MissionBrowser browser = new MissionBrowser(); protected PlanDBControl pdbControl; + protected String mainVehicleLastPlanId = null; + protected final List planNamesToAutoAcceptUpdatesList = new ArrayList<>(); /** @@ -207,12 +211,37 @@ private void addClearPlanDbMenuItem() { PluginUtils.getPluginIcon(getClass())), new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - if (pdbControl != null) - pdbControl.clearDatabase(); + if (pdbControl != null) { + SwingWorker sw = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + pdbControl.clearDatabase(); + return null; + } + }; + sw.execute(); + } } }); } + @Subscribe + public void mainVehicleChangeNotification(ConsoleEventMainSystemChange ev) { + mainVehicleLastPlanId = null; + try { + ImcSystemState state = getConsole().getImcMsgManager().getState(getMainVehicleId()); + if (state != null) { + PlanControlState pcsMsg = state.last(PlanControlState.class); + if (pcsMsg != null) { + on(pcsMsg); + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + @Subscribe public void on(ConsoleEventMainSystemChange evt) { running = false; @@ -347,6 +376,10 @@ public void on(PlanSpecification msg) { @Subscribe public void on(PlanControlState msg) { + if (getConsole().getMainSystem().equalsIgnoreCase(msg.getSourceName())) { + mainVehicleLastPlanId = msg.getPlanId(); + } + // If vehicle stops, the timers stop as well if (msg.getState() == STATE.READY || msg.getState() == STATE.BLOCKED) { browser.transStopTimers(); @@ -426,22 +459,55 @@ private void addActionSendPlan(final ConsoleLayout console2, final PlanDBControl final ArrayList selectedItems, JPopupMenu popupMenu) { if (!usePlanDBSyncFeatures) return; - + popupMenu.add( I18n.textf("Send %planName to %system", getPlanNamesString(selectedItems, true), console2.getMainSystem())) - .addActionListener( + .addActionListener(e -> { + SwingWorker worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + for (NameId nameId : selectedItems) { + PlanType sel = (PlanType) nameId; + String mainSystem = console2.getMainSystem(); + pdbControl.setRemoteSystemId(mainSystem); + boolean ret = pdbControl.sendPlan(sel); + if (!ret) { + NeptusLog.pub().error("Error sending plan " + sel.getId()); + break; + } + } + return null; + } + }; + worker.execute(); + }); + } - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - for (NameId nameId : selectedItems) { - PlanType sel = (PlanType) nameId; - String mainSystem = console2.getMainSystem(); - pdbControl.setRemoteSystemId(mainSystem); - pdbControl.sendPlan(sel); - } - } - }); + private void addActionSendPlanInfoRequest(final ConsoleLayout console2, final PlanDBControl pdbControl, + final ArrayList selectedItems, JPopupMenu popupMenu) { + if (!usePlanDBSyncFeatures) + return; + + popupMenu.add(I18n.textf("Get %planName info from %system", getPlanNamesString(selectedItems, true), console2.getMainSystem())) + .addActionListener(e -> { + SwingWorker worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + for (NameId nameId : selectedItems) { + PlanType sel = (PlanType) nameId; + String mainSystem = console2.getMainSystem(); + pdbControl.setRemoteSystemId(mainSystem); + boolean ret = pdbControl.requestPlanInfo(sel.getId()); + if (!ret) { + NeptusLog.pub().error("Error requesting plan info " + sel.getId()); + break; + } + } + return null; + } + }; + worker.execute(); + }); } private StringBuilder getPlanNamesString(final ArrayList selectedItems, boolean plans) { @@ -507,9 +573,21 @@ private void addActionGetRemotePlan(final ConsoleLayout console2, final PlanDBCo .addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - for (NameId nameId : remotePlans) { - pdbControl.requestPlan(nameId.getIdentification()); - } + SwingWorker worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + pdbControl.setRemoteSystemId(console2.getMainSystem()); + for (NameId nameId : remotePlans) { + boolean ret = pdbControl.requestPlan(nameId.getIdentification()); + if (!ret) { + NeptusLog.pub().error("Error requesting plan " + nameId.getIdentification()); + break; + } + } + return null; + } + }; + worker.execute(); } }); } @@ -519,7 +597,7 @@ private void addActionGetRemoteTrans(final ConsoleLayout console2, JPopupMenu po if (!useTransponderFeatures) return; - StringBuilder itemsInString = getPlanNamesString(remoteTrans, false); + StringBuilder itemsInString = getPlanNamesString(remoteTrans, false); // Check info text, seams wrong, it is a transponder popupMenu.add(I18n.textf("Get %planName from %system", itemsInString, console2.getMainSystem())) .addActionListener(new ActionListener() { @Override @@ -529,7 +607,14 @@ public void actionPerformed(ActionEvent e) { // Request LBLConfig LblConfig msgLBLConfiguration = new LblConfig(); msgLBLConfiguration.setOp(LblConfig.OP.GET_CFG); - sendMsg(msgLBLConfiguration); + SwingWorker worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + sendMsg(msgLBLConfiguration); + return null; + } + }; + worker.execute(); } }); } @@ -544,10 +629,21 @@ private void addActionRemovePlanRemotely(final ConsoleLayout console2, final Pla console2.getMainSystem())).addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - pdbControl.setRemoteSystemId(console2.getMainSystem()); - for (NameId nameId : synAndUnsyncPlans) { - pdbControl.deletePlan(nameId.getIdentification()); - } + SwingWorker worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + pdbControl.setRemoteSystemId(console2.getMainSystem()); + for (NameId nameId : synAndUnsyncPlans) { + boolean ret = pdbControl.deletePlan(nameId.getIdentification()); + if (!ret) { + NeptusLog.pub().error("Error deleting plan " + nameId.getIdentification()); + break; + } + } + return null; + } + }; + worker.execute(); } }); } @@ -602,27 +698,36 @@ public void mousePressed(MouseEvent e) { ArrayList toShare = new ArrayList(); + ArrayList toRemoveLocally = new ArrayList(); + ArrayList toRemoveRemotely = new ArrayList(); + ArrayList toGetPlan = new ArrayList(); + ArrayList toGetPlanInfo = new ArrayList(); + ArrayList toSend = new ArrayList(); + + boolean addForMainVehiclePlan = mainVehicleLastPlanId != null; + switch (selecType) { case Plans: if (selectedItems.size() == 1) { addActionRenamePlan(getConsole(), selectedItems, popupMenu); + addForMainVehiclePlan = false; } popupMenu.addSeparator(); - // New - ArrayList toRemoveLocally = new ArrayList(); - ArrayList toRemoveRemotely = new ArrayList(); - ArrayList toGetPlan = new ArrayList(); - ArrayList toSend = new ArrayList(); - + State syncState; // Separate plans by state to give appropriated options to each // addActionChangePlanVehicles(selection, popupMenu); // add appropriatly when multivehicles are // needed for (ExtendedTreeNode extendedTreeNode : selectedNodes) { syncState = (State) extendedTreeNode.getUserInfo().get(NodeInfoKey.SYNC.name()); - + + if (mainVehicleLastPlanId != null && mainVehicleLastPlanId + .equalsIgnoreCase(((NameId) extendedTreeNode.getUserObject()).getIdentification())) { + addForMainVehiclePlan = false; + } + if (syncState != null) { switch (syncState) { case REMOTE: @@ -632,6 +737,7 @@ public void mousePressed(MouseEvent e) { case SYNC: toRemoveRemotely.add((NameId) extendedTreeNode.getUserObject()); toRemoveLocally.add((NameId) extendedTreeNode.getUserObject()); + toGetPlanInfo.add((NameId) extendedTreeNode.getUserObject()); toShare.add((NameId) extendedTreeNode.getUserObject()); break; case NOT_SYNC: @@ -639,11 +745,13 @@ public void mousePressed(MouseEvent e) { toRemoveLocally.add((NameId) extendedTreeNode.getUserObject()); toSend.add((NameId) extendedTreeNode.getUserObject()); toGetPlan.add((NameId) extendedTreeNode.getUserObject()); + toGetPlanInfo.add((NameId) extendedTreeNode.getUserObject()); toShare.add((NameId) extendedTreeNode.getUserObject()); break; case LOCAL: toRemoveLocally.add((NameId) extendedTreeNode.getUserObject()); toSend.add((NameId) extendedTreeNode.getUserObject()); + toGetPlanInfo.add((NameId) extendedTreeNode.getUserObject()); toShare.add((NameId) extendedTreeNode.getUserObject()); break; } @@ -652,14 +760,39 @@ public void mousePressed(MouseEvent e) { NeptusLog.pub().error("The plan " + extendedTreeNode + " has no state."); } } - if (toRemoveRemotely.size() > 0) + + if (addForMainVehiclePlan) { + NameId nameId = new NameId() { + @Override + public String getIdentification() { + return mainVehicleLastPlanId; + } + @Override + public String getDisplayName() { + return mainVehicleLastPlanId; + } + }; + toGetPlan.add(nameId); + + toGetPlanInfo.add(nameId); + } + + if (!toRemoveRemotely.isEmpty()) addActionRemovePlanRemotely(getConsole(), pdbControl, toRemoveRemotely, popupMenu); - if (toRemoveLocally.size() > 0) + if (!toRemoveLocally.isEmpty()) addActionRemovePlanLocally(getConsole(), toRemoveLocally, popupMenu); - if (toSend.size() > 0) + if (!toSend.isEmpty()) addActionSendPlan(getConsole(), pdbControl, toSend, popupMenu); - if (toGetPlan.size() > 0) + if (!toGetPlan.isEmpty()) addActionGetRemotePlan(getConsole(), pdbControl, toGetPlan, popupMenu); + if (!toGetPlanInfo.isEmpty()) { + ArrayList toGetInfo = new ArrayList<>(); + toGetInfo.addAll(toSend); + toGetInfo.addAll(toRemoveRemotely); + toGetInfo.addAll(toGetPlan); + toGetInfo = toGetInfo.stream().distinct().collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + addActionSendPlanInfoRequest(getConsole(), pdbControl, toGetInfo, popupMenu); + } break; case Transponder: addActionAddNewTrans(popupMenu); @@ -678,10 +811,10 @@ public void mousePressed(MouseEvent e) { if (state == State.NOT_SYNC) notSyncTrans.add((TransponderElement) extendedTreeNode.getUserObject()); } - if (localTrans.size() > 0) { + if (!localTrans.isEmpty()) { addActionRemoveTrans(localTrans, popupMenu); } - if (notSyncTrans.size() > 0) { + if (!notSyncTrans.isEmpty()) { addActionGetRemoteTrans(getConsole(), popupMenu, notSyncTrans); } @@ -742,15 +875,22 @@ private void addActionRemoveAllTrans(JPopupMenu popupMenu) { popupMenu.add(I18n.text("Remove all transponders from vehicle")).addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - LblConfig msgLBLConfiguration = new LblConfig(); - msgLBLConfiguration.setOp(LblConfig.OP.SET_CFG); - msgLBLConfiguration.setBeacons(new Vector()); - sendMsg(msgLBLConfiguration); - msgLBLConfiguration = new LblConfig(); - msgLBLConfiguration.setOp(LblConfig.OP.GET_CFG); - sendMsg(msgLBLConfiguration); - // TODO On hold until removing all beacons is stable - // browser.removeAllTransponders(console.getMission()); + SwingWorker sw = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + LblConfig msgLBLConfiguration = new LblConfig(); + msgLBLConfiguration.setOp(LblConfig.OP.SET_CFG); + msgLBLConfiguration.setBeacons(new Vector()); + sendMsg(msgLBLConfiguration); + msgLBLConfiguration = new LblConfig(); + msgLBLConfiguration.setOp(LblConfig.OP.GET_CFG); + sendMsg(msgLBLConfiguration); + // TODO On hold until removing all beacons is stable + // browser.removeAllTransponders(console.getMission()); + return null; + } + }; + sw.execute(); } }); } @@ -959,4 +1099,4 @@ private enum ItemTypes { Mix, None; } -} \ No newline at end of file +} diff --git a/src/java/pt/lsts/neptus/console/plugins/planning/plandb/PlanDBControl.java b/src/java/pt/lsts/neptus/console/plugins/planning/plandb/PlanDBControl.java index 7452d137eb..b31ba56781 100644 --- a/src/java/pt/lsts/neptus/console/plugins/planning/plandb/PlanDBControl.java +++ b/src/java/pt/lsts/neptus/console/plugins/planning/plandb/PlanDBControl.java @@ -40,6 +40,7 @@ import pt.lsts.neptus.comm.manager.imc.ImcId16; import pt.lsts.neptus.comm.manager.imc.ImcMsgManager; import pt.lsts.neptus.comm.manager.imc.ImcSystemsHolder; +import pt.lsts.neptus.i18n.I18n; import pt.lsts.neptus.messages.listener.MessageInfo; import pt.lsts.neptus.messages.listener.MessageListener; import pt.lsts.neptus.types.mission.MissionType; @@ -101,7 +102,10 @@ public boolean clearDatabase() { IMCMessage imc_PlanDB = IMCDefinition.getInstance().create("PlanDB", "type", "REQUEST", "op", "CLEAR", "request_id", IMCSendMessageUtils.getNextRequestId()); - return ImcMsgManager.getManager().sendMessageToSystem(imc_PlanDB, remoteSystemId); + //return ImcMsgManager.getManager().sendMessageToSystem(imc_PlanDB, remoteSystemId); + return IMCSendMessageUtils.sendMessage(imc_PlanDB, ImcMsgManager.TRANSPORT_TCP, null, null, + I18n.text("Error sending clear all plans request"), true, "", + true, true, true, remoteSystemId); } public boolean sendPlan(PlanType plan) { @@ -109,13 +113,19 @@ public boolean sendPlan(PlanType plan) { "request_id", IMCSendMessageUtils.getNextRequestId(), "plan_id", plan.getId(), "arg", plan.asIMCPlan(), "info", ""); - return ImcMsgManager.getManager().sendMessageToSystem(imc_PlanDB, remoteSystemId); + //return ImcMsgManager.getManager().sendMessageToSystem(imc_PlanDB, remoteSystemId); + return IMCSendMessageUtils.sendMessage(imc_PlanDB, ImcMsgManager.TRANSPORT_TCP, null, null, + I18n.text("Error sending plan request"), true, "", + true, true, true, remoteSystemId); } public boolean requestPlan(String plan_id) { IMCMessage imc_PlanDB = IMCDefinition.getInstance().create("PlanDB", "type", "REQUEST", "op", "GET", "request_id", IMCSendMessageUtils.getNextRequestId(), "plan_id", plan_id); - return ImcMsgManager.getManager().sendMessageToSystem(imc_PlanDB, remoteSystemId); + //return ImcMsgManager.getManager().sendMessageToSystem(imc_PlanDB, remoteSystemId); + return IMCSendMessageUtils.sendMessage(imc_PlanDB, ImcMsgManager.TRANSPORT_TCP, null, null, + I18n.text("Error sending request plan request"), true, "", + true, true, true, remoteSystemId); } public boolean requestActivePlan() { @@ -125,14 +135,20 @@ public boolean requestActivePlan() { public boolean requestPlanInfo(String plan_id) { IMCMessage imc_PlanDB = IMCDefinition.getInstance().create("PlanDB", "type", "REQUEST", "op", "GET_INFO", "request_id", IMCSendMessageUtils.getNextRequestId(), "plan_id", plan_id); - return ImcMsgManager.getManager().sendMessageToSystem(imc_PlanDB, remoteSystemId); + // return ImcMsgManager.getManager().sendMessageToSystem(imc_PlanDB, remoteSystemId); + return IMCSendMessageUtils.sendMessage(imc_PlanDB, ImcMsgManager.TRANSPORT_TCP, null, null, + I18n.text("Error sending plan info request"), true, "", + true, true, true, remoteSystemId); } public boolean deletePlan(String plan_id) { IMCMessage imc_PlanDB = IMCDefinition.getInstance().create("PlanDB", "type", "REQUEST", "op", "DEL", "request_id", IMCSendMessageUtils.getNextRequestId(), "plan_id", plan_id); NeptusLog.pub().debug("Sending to " + remoteSystemId); - return ImcMsgManager.getManager().sendMessageToSystem(imc_PlanDB, remoteSystemId); + //return ImcMsgManager.getManager().sendMessageToSystem(imc_PlanDB, remoteSystemId); + return IMCSendMessageUtils.sendMessage(imc_PlanDB, ImcMsgManager.TRANSPORT_TCP, null, null, + I18n.text("Error delete plan request"), true, "", + true, true, true, remoteSystemId); } public void updateKnownState(IMCMessage imc_PlanDBState) { diff --git a/src/java/pt/lsts/neptus/gui/swing/HoldFillButton.java b/src/java/pt/lsts/neptus/gui/swing/HoldFillButton.java index df4b343073..90c4c67733 100644 --- a/src/java/pt/lsts/neptus/gui/swing/HoldFillButton.java +++ b/src/java/pt/lsts/neptus/gui/swing/HoldFillButton.java @@ -52,9 +52,12 @@ public class HoldFillButton extends JButton { private Timer timer; private int progress = 0; - private int holdDurationMs = 2000; // 2 seconds + private int holdDurationMillis = 2000; + private final Color fillColor = new Color(0, 128, 255, 100); + private final int tempButtonDurationMillis = 1000; private final List actionListeners = new ArrayList<>(); private final ImageIcon LOCK_CLOCK = new ImageIcon(ImageUtils.getScaledImage("images/buttons/lock_clock.png", 15, 15)); + private final ImageIcon TICK = new ImageIcon(ImageUtils.getScaledImage("images/buttons/tick.png", 15, 15)); public HoldFillButton(String text) { super(text); @@ -62,10 +65,10 @@ public HoldFillButton(String text) { setupButton(); } - public HoldFillButton(String text, int holdDurationMs) { + public HoldFillButton(String text, int holdDurationMillis) { super(text); setIcon(LOCK_CLOCK); - this.holdDurationMs = holdDurationMs; + this.holdDurationMillis = holdDurationMillis; setupButton(); } @@ -81,13 +84,14 @@ public void mousePressed(MouseEvent e) { timer = new Timer(10, event -> { long elapsed = System.currentTimeMillis() - pressStartTime; - progress = (int) (elapsed * 100 / holdDurationMs); + progress = (int) (elapsed * 100 / holdDurationMillis); repaint(); - if (elapsed >= holdDurationMs) { + if (elapsed >= holdDurationMillis) { timer.stop(); triggerAction(); progress = 0; + repaint(); } }); timer.start(); @@ -104,6 +108,8 @@ public void mouseReleased(MouseEvent e) { } private void triggerAction() { + progress = 0; + changeButtonTemporarily(); fireCustomActionPerformed(); } }); @@ -120,6 +126,15 @@ this, ActionEvent.ACTION_PERFORMED, getActionCommand() } } + private void changeButtonTemporarily() { + setIcon(TICK); + Timer timer = new Timer(tempButtonDurationMillis, (ActionEvent event) -> { + setIcon(LOCK_CLOCK); + }); + timer.setRepeats(false); // Run only once + timer.start(); + } + @Override public void addActionListener(ActionListener l) { if (l == null) { @@ -138,8 +153,6 @@ protected void paintComponent(Graphics g) { int fillWidth = (int) (getWidth() * (progress / 100.0)); - Color fillColor = new Color(0, 128, 255, 100); - g2d.setColor(fillColor); g2d.fillRect(0, 0, fillWidth, getHeight()); } diff --git a/src/java/pt/lsts/neptus/params/SystemConfigurationEditorPanel.java b/src/java/pt/lsts/neptus/params/SystemConfigurationEditorPanel.java index fa05f6457e..11d3277f6f 100644 --- a/src/java/pt/lsts/neptus/params/SystemConfigurationEditorPanel.java +++ b/src/java/pt/lsts/neptus/params/SystemConfigurationEditorPanel.java @@ -32,6 +32,7 @@ */ package pt.lsts.neptus.params; +import java.awt.CardLayout; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; @@ -40,19 +41,27 @@ import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.stream.Collectors; import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; +import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; +import javax.swing.SwingWorker; import com.l2fprod.common.propertysheet.Property; import com.l2fprod.common.propertysheet.PropertyEditorRegistry; @@ -60,8 +69,8 @@ import com.l2fprod.common.propertysheet.PropertySheet; import com.l2fprod.common.propertysheet.PropertySheetPanel; import com.l2fprod.common.propertysheet.PropertySheetTableModel.Item; - import net.miginfocom.swing.MigLayout; + import pt.lsts.imc.EntityParameter; import pt.lsts.imc.EntityParameters; import pt.lsts.imc.IMCMessage; @@ -69,10 +78,13 @@ import pt.lsts.imc.SaveEntityParameters; import pt.lsts.imc.SetEntityParameters; import pt.lsts.neptus.NeptusLog; +import pt.lsts.neptus.comm.IMCSendMessageUtils; +import pt.lsts.neptus.comm.admin.CommsAdmin; import pt.lsts.neptus.comm.manager.imc.ImcMsgManager; import pt.lsts.neptus.comm.manager.imc.ImcSystem; import pt.lsts.neptus.comm.manager.imc.ImcSystemsHolder; import pt.lsts.neptus.comm.manager.imc.MessageDeliveryListener; +import pt.lsts.neptus.gui.InfiniteProgressPanel; import pt.lsts.neptus.i18n.I18n; import pt.lsts.neptus.params.SystemProperty.Scope; import pt.lsts.neptus.params.SystemProperty.Visibility; @@ -86,17 +98,31 @@ @SuppressWarnings("serial") public class SystemConfigurationEditorPanel extends JPanel implements PropertyChangeListener { + public static final String CARD_PROPERTIES = "properties"; + public static final String CARD_CATEGORIES = "categories"; + public static final String CARD_PROGRESS = "progress"; + protected final LinkedHashMap params = new LinkedHashMap<>(); + private static boolean isAskForCategories = false; + + private JPanel swapPropertiesAndCategoriesPanel; + private JPanel mainPanel; + private JPanel categoriesPanel; + + private JPanel propertiesPanel; + private InfiniteProgressPanel progressPanel; protected PropertySheetPanel psp; private JButton sendButton; private JButton saveButton; private JButton refreshButton; private JButton resetButton; private JButton collapseButton; - + private JButton expandButton; + private JLabel titleLabel; private JCheckBox checkAdvance; + private JCheckBox checkSelection; private JComboBox scopeComboBox; protected boolean refreshing = false; @@ -110,7 +136,11 @@ public class SystemConfigurationEditorPanel extends JPanel implements PropertyCh protected ImcSystem sid = null; protected ImcMsgManager imcMsgManager; - + + // Category name, category label on gui + private Map touchedCategoriesList; + private Map lastSelectedCategoriesList; + public SystemConfigurationEditorPanel(String systemId, Scope scopeToUse, Visibility visibility, boolean showSendButton, boolean showScopeCombo, boolean showResetButton, ImcMsgManager imcMsgManager) { this.systemId = systemId; @@ -125,6 +155,8 @@ public SystemConfigurationEditorPanel(String systemId, Scope scopeToUse, Visibil private void initialize(boolean showSendButton, boolean showScopeCombo, boolean showResetButton) { setLayout(new MigLayout()); + mainPanel = new JPanel(new MigLayout("fill, insets 0")); + scopeComboBox = new JComboBox(Scope.values()) { public void setSelectedItem(Object anObject) { super.setSelectedItem(anObject); @@ -153,18 +185,15 @@ public Component getListCellRendererComponent(JList list, Scope @Override public void itemStateChanged(ItemEvent e) { scopeToUse = (Scope) e.getItem(); - new Thread() { - @Override - public void run() { - if (refreshButton != null) - refreshButton.doClick(50); - } - }.start(); + new Thread(() -> { + if (refreshButton != null) + refreshButton.doClick(50); + }).start(); } }); titleLabel = new JLabel("" + createTitle() + ""); - add(titleLabel, "w 100%, wrap"); + mainPanel.add(titleLabel, "w 100%, wrap"); // Configure Property sheet psp = new PropertySheetPanel(); @@ -175,38 +204,72 @@ public void run() { psp.setToolBarVisible(false); resetPropertiesEditorAndRendererFactories(); - - add(psp, "w 100%, h 100%, wrap"); + + propertiesPanel = new JPanel(new CardLayout()); + propertiesPanel.add(psp, CARD_PROPERTIES); + progressPanel = new InfiniteProgressPanel(I18n.text("Please wait...")); + propertiesPanel.add(progressPanel, CARD_PROGRESS); + + mainPanel.add(propertiesPanel, "w 100%, h 100%, wrap"); + + categoriesPanel = new JPanel(new MigLayout("fill, insets 0")); + + swapPropertiesAndCategoriesPanel = new JPanel(new CardLayout()); + swapPropertiesAndCategoriesPanel.add(mainPanel, CARD_PROPERTIES); + swapPropertiesAndCategoriesPanel.add(categoriesPanel, CARD_CATEGORIES); + + add(swapPropertiesAndCategoriesPanel, "w 100%, h 100%"); sendButton = new JButton(new AbstractAction(I18n.text("Send")) { @Override public void actionPerformed(ActionEvent e) { - sendPropertiesToSystem(); + SwingWorker worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + sendPropertiesToSystem(); + return null; + } + }; + worker.execute(); } }); sendButton.setToolTipText(I18n.text("Send the modified properties.")); if (showSendButton) { - add(sendButton, "sg buttons, split"); + mainPanel.add(sendButton, "sg buttons, split"); } refreshButton = new JButton(new AbstractAction(I18n.text("Refresh")) { @Override public void actionPerformed(ActionEvent e) { - refreshPropertiesOnPanel(); + SwingWorker worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + refreshPropertiesOnPanel(true, true, new String[] {CommsAdmin.CommChannelType.WIFI.name}); + return null; + } + }; + worker.execute(); } }); refreshButton.setToolTipText(I18n.text("Requests the entities sections parameters from the vehicle.")); - add(refreshButton, "sg buttons, split"); + mainPanel.add(refreshButton, "sg buttons, split"); saveButton = new JButton(new AbstractAction(I18n.text("Save")) { @Override public void actionPerformed(ActionEvent e) { - savePropertiesToSystem(); + SwingWorker worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + savePropertiesToSystem(); + return null; + } + }; + worker.execute(); } }); saveButton.setToolTipText(I18n.text("Saves the visible entities sections in the vehicle.")); if (showSendButton) { - add(saveButton, "sg buttons, split"); + mainPanel.add(saveButton, "sg buttons, split"); } collapseButton = new JButton(new AbstractAction(I18n.text("Collapse All")) { @@ -221,8 +284,25 @@ public void actionPerformed(ActionEvent e) { } }); collapseButton.setToolTipText(I18n.text("Collapse all sections.")); - add(collapseButton, "sg buttons, split"); + mainPanel.add(collapseButton, "sg buttons, gapbefore unrel"); + expandButton = new JButton(new AbstractAction(I18n.text("Expand All")) { + @Override + public void actionPerformed(ActionEvent e) { + for (int i = 0; i < psp.getTable().getSheetModel().getRowCount(); i++) { + Item o = (Item) psp.getTable().getSheetModel().getObject(i); + if (!o.isVisible()) { + if (o.hasToggle() && !o.isVisible()) { + o.toggle(); + } else if (!o.hasToggle() && o.getParent() != null && !o.getParent().isVisible()) { + o.getParent().toggle(); + } + } + } + } + }); + expandButton.setToolTipText(I18n.text("Expand all sections.")); + mainPanel.add(expandButton, "sg buttons, split"); resetButton = new JButton(new AbstractAction(I18n.text("Reset")) { @Override @@ -232,42 +312,71 @@ public void actionPerformed(ActionEvent e) { }); resetButton.setToolTipText(I18n.text("Local reset. Needs to be sent to system.")); if (showResetButton) - add(resetButton, "sg buttons, gapbefore 30, split, wrap"); + mainPanel.add(resetButton, "sg buttons, gapbefore 30, split, wrap"); resetButton.setToolTipText(I18n.text("Local reset. Needs to be sent to system.")); if (showScopeCombo) - add(scopeComboBox, "split, w :160:"); + mainPanel.add(scopeComboBox, "split, w :160:"); checkAdvance = new JCheckBox(I18n.text("Access Developer Parameters")); checkAdvance.setToolTipText("" + I18n.textc("Be careful changing these values.
They may make the vehicle inoperable.", "This will be a tooltip, and use
to change line.")); -// if (ConfigFetch.getDistributionType() == DistributionEnum.DEVELOPER) - add(checkAdvance); -// else -// visibility = Visibility.USER; - if (visibility == Visibility.DEVELOPER) - checkAdvance.setSelected(true); - else - checkAdvance.setSelected(false); - checkAdvance.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - if (checkAdvance.isSelected()) - visibility = Visibility.DEVELOPER; - else - visibility = Visibility.USER; - - refreshPropertiesOnPanel(); - } + mainPanel.add(checkAdvance, "split, sg checkboxes"); + checkAdvance.setSelected(visibility == Visibility.DEVELOPER); + checkAdvance.addItemListener(e -> { + if (checkAdvance.isSelected()) + visibility = Visibility.DEVELOPER; + else + visibility = Visibility.USER; + + // FIXME This might not make sense to not always ask for categories + SwingWorker worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + refreshPropertiesOnPanel(false, false, new String[] {CommsAdmin.CommChannelType.WIFI.name}); + return null; + } + }; + worker.execute(); }); checkAdvance.setFocusable(false); - refreshPropertiesOnPanel(); + checkSelection = new JCheckBox(I18n.text("Ask for categories")); + checkSelection.setToolTipText("" + I18n.textc("Ask for categories before send.", + "This will be a tooltip, and use
to change line.")); + checkSelection.addItemListener(e -> { + isAskForCategories = checkSelection.isSelected(); + updateSendButtons(); + }); + checkSelection.setSelected(isAskForCategories); + checkSelection.setFocusable(false); + mainPanel.add(checkSelection, "sg checkboxes"); + + // FIXME This might not make sense to not always ask for categories if no wifi + refreshPropertiesOnPanel(false, false, new String[] {CommsAdmin.CommChannelType.WIFI.name}); revalidate(); repaint(); } + private void updateSendButtons() { + List actions = new ArrayList<>(); + actions.add(sendButton.getAction()); + actions.add(refreshButton.getAction()); + actions.add(saveButton.getAction()); + + String suffix = " >"; + for (Action action : actions) { + String name = (String) action.getValue("Name"); + if (!isAskForCategories && name.endsWith(suffix)) { + name = name.substring(0, name.length() - 2); + } else if (isAskForCategories && !name.endsWith(suffix)) { + name += suffix; + } + action.putValue("Name", name); + } + } + private void resetPropertiesEditorAndRendererFactories() { per = new PropertyEditorRegistry(); // per.registerDefaults(); @@ -297,7 +406,8 @@ public String getSystemId() { public void setSystemId(String systemId) { this.systemId = systemId; sid = ImcSystemsHolder.getSystemWithName(this.systemId); - refreshPropertiesOnPanel(); + // FIXME This might not make sense to not always ask for categories + refreshPropertiesOnPanel(false, false, new String[]{CommsAdmin.CommChannelType.WIFI.name}); } /** @@ -314,42 +424,89 @@ public void setRefreshing(boolean refreshing) { this.refreshing = refreshing; } - private synchronized void refreshPropertiesOnPanel() { - titleLabel.setText("" + createTitle() + ""); - removeAllPropertiesFromPanel(); - - resetPropertiesEditorAndRendererFactories(); - - ArrayList pr = ConfigurationManager.getInstance().getProperties(systemId, visibility, scopeToUse); - ArrayList secNames = new ArrayList<>(); - for (SystemProperty sp : pr) { - String sectionName = sp.getCategoryId(); - String name = sp.getName(); - if (!secNames.contains(sectionName)) - secNames.add(sectionName); - params.put(sectionName + "." + name, sp); - sp.addPropertyChangeListener(this); - psp.addProperty(sp); - if (sp.getEditor() != null) { - per.registerEditor(sp, sp.getEditor()); - } - if (sp.getRenderer() != null) { - prr.registerRenderer(sp, sp.getRenderer()); + private synchronized void refreshPropertiesOnPanel(boolean askForCategories, boolean popGuiOnError, String[] channelsToUse) { + try { + showWaiterUpdateProperties(true); + + // FIXME + //Map oldCategoriesOnPanel = getCategoriesOnPanel(true); + + titleLabel.setText("" + createTitle() + ""); + List openCategories = removeAllPropertiesFromPanel(); + + resetPropertiesEditorAndRendererFactories(); + + ArrayList pr = ConfigurationManager.getInstance().getProperties(systemId, visibility, scopeToUse); + ArrayList secNames = new ArrayList<>(); + for (SystemProperty sp : pr) { + String sectionName = sp.getCategoryId(); + String name = sp.getName(); + if (!secNames.contains(sectionName)) + secNames.add(sectionName); + params.put(sectionName + "." + name, sp); + sp.addPropertyChangeListener(this); + psp.addProperty(sp); + if (sp.getEditor() != null) { + per.registerEditor(sp, sp.getEditor()); + } + if (sp.getRenderer() != null) { + prr.registerRenderer(sp, sp.getRenderer()); + } } - } - // Let us make sure all dependencies between properties are ok - for (SystemProperty spCh : params.values()) { - for (SystemProperty sp : params.values()) { - PropertyChangeEvent evt = new PropertyChangeEvent(spCh, spCh.getName(), null, spCh.getValue()); - sp.propertyChange(evt); + // Let us make sure all dependencies between properties are ok + for (SystemProperty spCh : params.values()) { + for (SystemProperty sp : params.values()) { + PropertyChangeEvent evt = new PropertyChangeEvent(spCh, spCh.getName(), null, spCh.getValue()); + sp.propertyChange(evt); + } + } + + // Close all categories + for (int i = 0; i < psp.getTable().getSheetModel().getRowCount(); i++) { + Item o = (Item) psp.getTable().getSheetModel().getObject(i); + if (o.isVisible() && !o.hasToggle()) { + if (!openCategories.contains(o.getParent().getName())) { + o.getParent().toggle(); + } + } + } + + List queryCategoriesList; + if (askForCategories && isAskForCategories) { + List validCategories; + try { + validCategories = askForCategories("refresh", lastSelectedCategoriesList).get(); + } + catch (Exception e) { + // Do nothing + validCategories = Collections.emptyList(); + } + queryCategoriesList = new ArrayList<>(); + for (String category : validCategories) { + if (category != null && validCategories.contains(category)) + queryCategoriesList.add(category); + } + } else { + queryCategoriesList = secNames; + } + + showWaiterUpdateProperties(false); + revalidate(); + repaint(); + + for (String sectionName : queryCategoriesList) { + boolean ret = queryValues(sectionName, scopeToUse.getText(), visibility.getText(), popGuiOnError, channelsToUse); + if (!ret) + break; } } - for (String sectionName : secNames) { - queryValues(sectionName, scopeToUse.getText(), visibility.getText()); + catch (Exception e) { + NeptusLog.pub().error(e); + } finally { + showWaiterUpdateProperties(false); + revalidate(); + repaint(); } - - revalidate(); - repaint(); } private synchronized void resetPropertiesOnPanel() { @@ -359,11 +516,29 @@ private synchronized void resetPropertiesOnPanel() { psp.repaint(); } - private void removeAllPropertiesFromPanel() { + private List removeAllPropertiesFromPanel() { + List toggledCategories = new ArrayList<>(); + for (int i = 0; i < psp.getTable().getSheetModel().getRowCount(); i++) { + Item o = (Item) psp.getTable().getSheetModel().getObject(i); + if (o.isVisible() && !o.hasToggle()) { + String name = o.getParent().getName(); + if (!toggledCategories.contains(name)) { + toggledCategories.add(name); + } + } + } + params.clear(); for (Property p : psp.getProperties()) { psp.removeProperty(p); } + + return toggledCategories; + } + + private void showWaiterUpdateProperties(boolean wait) { + ((CardLayout) propertiesPanel.getLayout()).show(propertiesPanel, wait ? CARD_PROGRESS : CARD_PROPERTIES); + progressPanel.setBusy(wait); } private String createTitle() { @@ -384,25 +559,29 @@ public void propertyChange(PropertyChangeEvent evt) { sprop.propertyChange(evt); } sp.propertyChange(evt); + + if (touchedCategoriesList == null) + touchedCategoriesList = new LinkedHashMap<>(); + touchedCategoriesList.putIfAbsent(sp.getCategoryId(), sp.getCategory()); } } - private void queryValues(String entityName, String scope, String visibility) { + private boolean queryValues(String entityName, String scope, String visibility, boolean popGuiOnError, String... channelsToUse) { QueryEntityParameters qep = new QueryEntityParameters(); qep.setScope(scope); qep.setVisibility(visibility); qep.setName(entityName); - send(qep); + return send(qep, popGuiOnError, channelsToUse); } - private void saveRequest(String entityName) { + private boolean saveRequest(String entityName) { SaveEntityParameters qep = new SaveEntityParameters(); qep.setName(entityName); - send(qep); + return send(qep, true); } - private void sendProperty(SystemProperty... propsList) { - Map> mapCategoryParameterList = new LinkedHashMap>(); + private boolean sendProperty(SystemProperty... propsList) { + Map> mapCategoryParameterList = new LinkedHashMap<>(); for (SystemProperty prop : propsList) { if (prop.getValue() == null) continue; @@ -439,8 +618,151 @@ private void sendProperty(SystemProperty... propsList) { } for (SetEntityParameters setEntityParameters : msgs) { - send(setEntityParameters); + if (!send(setEntityParameters, true)) + return false; // If one fails, rest is skipped, this helps to avoid iridium to be flooded all errors } + return true; + } + + private Map getCategoriesOnPanel(boolean onlyVisible) { + Map categories = new LinkedHashMap<>(); + for (SystemProperty sp : params.values()) { + String category = sp.getCategoryId(); + if (!categories.containsKey(category)) + categories.put(category, sp.getCategory()); + } + + if (onlyVisible) { + List visibleCategoriesI18n = new ArrayList<>(); + for (int i = 0; i < psp.getTable().getSheetModel().getRowCount(); i++) { + Item o = (Item) psp.getTable().getSheetModel().getObject(i); + if (o.hasToggle()) { // Is a category + if (!visibleCategoriesI18n.contains(o.getName())) { + visibleCategoriesI18n.add(o.getName()); //I18n name + } + } else if (!o.hasToggle() && o.getParent() != null) { + if (!visibleCategoriesI18n.contains(o.getParent().getName())) { + visibleCategoriesI18n.add(o.getParent().getName()); //I18n name + } + } + } + + categories = categories.entrySet().stream().filter(e -> visibleCategoriesI18n.contains(e.getValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + return categories; + } + + private Future> askForCategories(String forWhat) { + return askForCategories(forWhat, null); + } + + private Future> askForCategories(String forWhat, Map previousCheckCategoriesOnPanel) { + CompletableFuture> future = new CompletableFuture<>(); + Map categories = getCategoriesOnPanel(true); + List chosenCategories = new ArrayList<>(categories.keySet()); + List checkBoxes = new ArrayList<>(); + + if ((previousCheckCategoriesOnPanel == null || previousCheckCategoriesOnPanel.isEmpty()) + && lastSelectedCategoriesList != null) { + previousCheckCategoriesOnPanel = new LinkedHashMap<>(); + previousCheckCategoriesOnPanel.putAll(lastSelectedCategoriesList); + } + + for (String category : chosenCategories.stream().sorted().collect(Collectors.toList())) { + JCheckBox cb = new JCheckBox(categories.get(category)); + cb.addActionListener(e -> { + if (((JCheckBox) e.getSource()).isSelected()) { + if (!chosenCategories.contains(category)) + chosenCategories.add(category); + } else { + chosenCategories.remove(category); + } + }); + cb.setSelected(true); // To set ticked; + if (previousCheckCategoriesOnPanel != null && !previousCheckCategoriesOnPanel.containsKey(category)) { + cb.setSelected(false); + } + // if selected add to checkCategories + if (!chosenCategories.contains(category)) + chosenCategories.add(category); + checkBoxes.add(cb); + } + categoriesPanel.removeAll(); + // categoriesPanel.setLayout(new MigLayout("align center, fill, insets 0")); + + JLabel systemLabel = new JLabel("" + createTitle() + ""); + + systemLabel.setFont(systemLabel.getFont().deriveFont(systemLabel.getFont().getStyle() | java.awt.Font.BOLD)); + JLabel label = new JLabel(I18n.textf("Select the categories to be used for >> %action", forWhat)); + categoriesPanel.add(systemLabel, "wrap"); + categoriesPanel.add(label, "wrap"); + + JPanel checklistPanels = new JPanel(new MigLayout("fillx, wrap 2", "[left]rel[grow,fill]", "[]10[]")); + JScrollPane scrollPane = new JScrollPane(checklistPanels); + categoriesPanel.add(scrollPane, "w 100%, h 100%, wrap"); + for (JCheckBox cb : checkBoxes) { + checklistPanels.add(cb, ""); + } + + JButton selectAllButton = new JButton(I18n.text("Sel All")); + JButton selectNoneButton = new JButton(I18n.text("Sel None")); + categoriesPanel.add(selectAllButton, "sg buttons, split"); + categoriesPanel.add(selectNoneButton, "sg buttons, split, gapafter 30"); + selectAllButton.addActionListener(e -> { + checkBoxes.forEach(cb -> { + if (!cb.isSelected()) { + String categoryLabel = cb.getText(); + String category = categories.entrySet().stream() + .filter(ee -> ee.getValue().equals(categoryLabel)) + .map(Map.Entry::getKey).findFirst().orElse(null); + if (!chosenCategories.contains(cb.getText())) + chosenCategories.add(category); + cb.setSelected(true); + } + }); + }); + selectNoneButton.addActionListener(e -> { + chosenCategories.clear(); + checkBoxes.forEach(cb -> cb.setSelected(false)); + }); + + JButton okButton = new JButton(I18n.text("Ok")); + JButton cancelButton = new JButton(I18n.text("Cancel")); + categoriesPanel.add(okButton, "gapbefore push, sg buttons, split"); + categoriesPanel.add(cancelButton, "sg buttons, split"); + okButton.addActionListener(e -> { + future.complete(new ArrayList<>(chosenCategories)); + // SwingUtilities.getWindowAncestor(categoriesPanel).dispose(); + CardLayout card = (CardLayout) swapPropertiesAndCategoriesPanel.getLayout(); + card.show(swapPropertiesAndCategoriesPanel, CARD_PROPERTIES); + revalidate(); + repaint(); + + if (lastSelectedCategoriesList == null) { + lastSelectedCategoriesList = new LinkedHashMap<>(); + } else { + lastSelectedCategoriesList.clear(); + } + chosenCategories.forEach(cat -> lastSelectedCategoriesList.putIfAbsent(cat, categories.get(cat))); + }); + cancelButton.addActionListener(e -> { + future.completeExceptionally(new RuntimeException("User cancelled")); + // SwingUtilities.getWindowAncestor(categoriesPanel).dispose(); + CardLayout card = (CardLayout) swapPropertiesAndCategoriesPanel.getLayout(); + card.show(swapPropertiesAndCategoriesPanel, CARD_PROPERTIES); + revalidate(); + repaint(); + }); + GuiUtils.reactEnterKeyPress(okButton); + GuiUtils.reactEscapeKeyPress(cancelButton); + + CardLayout card = (CardLayout) swapPropertiesAndCategoriesPanel.getLayout(); + card.show(swapPropertiesAndCategoriesPanel, CARD_CATEGORIES); + revalidate(); + repaint(); + return future; } /** @@ -448,18 +770,35 @@ private void sendProperty(SystemProperty... propsList) { * that are needed. It will only send the SystemProperty messages that are locally dirty. */ private void sendPropertiesToSystem() { - Set sentProps = new LinkedHashSet(); + List validCategories = null; + try { + validCategories = isAskForCategories ? askForCategories("send", touchedCategoriesList).get() : null; + } + catch (Exception e) { + return; + } + + Set sentProps = new LinkedHashSet<>(); ArrayList sysPropToSend = new ArrayList<>(); for (SystemProperty sp : params.values()) { + if (validCategories != null && !validCategories.contains(sp.getCategoryId())) + continue; // Skip if not in the list of valid categories if (sp.getTimeDirty() > sp.getTimeSync()) { // sendProperty(sp); sysPropToSend.add(sp); sentProps.add(sp); } } - if (sysPropToSend.size() > 0) { - sendProperty(sysPropToSend.toArray(new SystemProperty[sysPropToSend.size()])); - + if (!sysPropToSend.isEmpty()) { + boolean ret = sendProperty(sysPropToSend.toArray(new SystemProperty[0])); + if (ret) { + touchedCategoriesList.clear(); + } + + if (!ret) { + return; + } + ArrayList secNames = new ArrayList<>(); for (SystemProperty sp : sentProps) { String sectionName = sp.getCategoryId(); @@ -467,31 +806,49 @@ private void sendPropertiesToSystem() { secNames.add(sectionName); } for (String sec : secNames) { - queryValues(sec, scopeToUse.getText(), visibility.getText()); + // TODO See if we want to ask back from Iridium + if (!queryValues(sec, scopeToUse.getText(), visibility.getText(), true)) + break; } } } private void savePropertiesToSystem() { + List validCategories; + try { + validCategories = isAskForCategories ? askForCategories("save").get() : null; + } + catch (Exception e) { + return; + } Collection propsInPanel = params.values(); - if (propsInPanel.size() > 0) { + if (!propsInPanel.isEmpty()) { ArrayList secNames = new ArrayList<>(); for (SystemProperty sp : propsInPanel) { String sectionName = sp.getCategoryId(); + if (validCategories != null && !validCategories.contains(sectionName)) + continue; // Skip if not in the list of valid categories if (!secNames.contains(sectionName)) secNames.add(sectionName); - } - for (String sec : secNames) { - queryValues(sec, scopeToUse.getText(), visibility.getText()); } - + boolean ret = true; for (String sec : secNames) { - saveRequest(sec); + // TODO See if we want to ask back from Iridium + ret = queryValues(sec, scopeToUse.getText(), visibility.getText(), true); + if (!ret) + break; + } + + if (ret) { + for (String sec : secNames) { + if (!saveRequest(sec)) + break; + } } } } - private void send(IMCMessage msg) { + private boolean send(IMCMessage msg, boolean popGuiOnError, String... channelsToUse) { //}, boolean askApprovalOtherThanWifi) { MessageDeliveryListener mdl = new MessageDeliveryListener() { @Override public void deliveryUnreacheable(IMCMessage message) { @@ -515,12 +872,18 @@ public void deliveryError(IMCMessage message, Object error) { }; if (sid == null) sid = ImcSystemsHolder.getSystemWithName(getSystemId()); - if (sid != null) { - imcMsgManager.sendReliablyNonBlocking(msg, sid.getId(), mdl); - } - else { - imcMsgManager.sendMessageToSystem(msg, getSystemId()); - } + boolean sendReliably = sid != null; + String system = sid != null ? sid.getName() : systemId; +// if (sid != null) { +// imcMsgManager.sendReliablyNonBlocking(msg, sid.getId(), mdl); +// } +// else { +// imcMsgManager.sendMessageToSystem(msg, getSystemId()); +// } + return IMCSendMessageUtils.sendMessage(msg, (sendReliably ? ImcMsgManager.TRANSPORT_TCP + : null), mdl, this, I18n.text("Error sending msg params"), + false, "", true, true, + true, popGuiOnError, channelsToUse, system); } public static void updatePropertyWithMessageArrived(SystemConfigurationEditorPanel systemConfEditor, IMCMessage message) { @@ -570,7 +933,7 @@ public static void main(String[] args) { final SystemConfigurationEditorPanel sc1 = new SystemConfigurationEditorPanel(vehicle, Scope.MANEUVER, Visibility.USER, true, true, true, ImcMsgManager.getManager()); final SystemConfigurationEditorPanel sc2 = new SystemConfigurationEditorPanel(vehicle, Scope.MANEUVER, - Visibility.USER, true, true, true, ImcMsgManager.getManager()); + Visibility.USER, true, false, true, ImcMsgManager.getManager()); // ImcMsgManager.getManager().addListener(new MessageListener() { // @Override diff --git a/src/java/pt/lsts/neptus/types/comm/protocol/IridiumArgs.java b/src/java/pt/lsts/neptus/types/comm/protocol/IridiumArgs.java index e338c733bd..95bf4fdb77 100644 --- a/src/java/pt/lsts/neptus/types/comm/protocol/IridiumArgs.java +++ b/src/java/pt/lsts/neptus/types/comm/protocol/IridiumArgs.java @@ -39,10 +39,13 @@ import com.l2fprod.common.propertysheet.DefaultProperty; import com.l2fprod.common.propertysheet.Property; +import org.dom4j.Node; import pt.lsts.neptus.gui.PropertiesProvider; import pt.lsts.neptus.plugins.NeptusProperty; import pt.lsts.neptus.plugins.PluginUtils; +import java.util.Date; + /** * @author zp * @@ -51,13 +54,20 @@ public class IridiumArgs extends ProtocolArgs implements PropertiesProvider { @NeptusProperty private String imei = ""; - + @NeptusProperty + private String imei1 = ""; + + private int lastActiveImei = 0; + private Date lastImeiDateReceived = new Date(0); + @Override public Document asDocument(String rootElementName) { Document document = DocumentHelper.createDocument(); Element root = document.addElement(rootElementName); if (!imei.isEmpty()) root.addElement("imei", imei); + if (!imei1.isEmpty()) + root.addElement("imei1", imei1); return document; } @@ -65,7 +75,9 @@ public Document asDocument(String rootElementName) { public boolean load(Element elem) { try { imei = elem.selectSingleNode("//imei").getText(); - return true; + Node imei1Elem = elem.selectSingleNode("//imei1"); + imei1 = imei1Elem != null ? imei1Elem.getText() : ""; + return true; } catch (Exception e) { e.printStackTrace(); @@ -73,6 +85,48 @@ public boolean load(Element elem) { } } + public boolean setLastSeenImei(int imei, Date date) { + if (!lastImeiDateReceived.before(date)) + return false; + int oldImei = lastActiveImei; + if (imei == 0) + lastActiveImei = 0; + else if (imei == 1) + lastActiveImei = 1; + else + return false; + if (oldImei != lastActiveImei) + System.out.println("Switched to IMEI " + getLastSeenImei()); + lastImeiDateReceived = date; + return true; + } + + public boolean setLastSeenImei(String imei, Date date) { + if (!lastImeiDateReceived.before(date)) + return false; + int oldImei = lastActiveImei; + if (imei == null || imei.isEmpty()) + return false; + if (imei.equals(this.imei)) + lastActiveImei = 0; + else if (imei.equals(this.imei1)) + lastActiveImei = 1; + else + return false; + if (oldImei != lastActiveImei) + System.out.println("Switched to IMEI " + getLastSeenImei()); + lastImeiDateReceived = date; + return true; + } + + public String getLastSeenImei() { + if (lastActiveImei == 0) + return imei; + else if (lastActiveImei == 1) + return imei1; + return ""; + } + /** * @return the imei */ @@ -86,7 +140,15 @@ public String getImei() { public void setImei(String imei) { this.imei = imei; } - + + public String getImei1() { + return imei1; + } + + public void setImei1(String imei1) { + this.imei1 = imei1; + } + @Override public DefaultProperty[] getProperties() { return PluginUtils.getPluginProperties(this); diff --git a/src/java/pt/lsts/neptus/types/vehicle/VehiclesHolder.java b/src/java/pt/lsts/neptus/types/vehicle/VehiclesHolder.java index 0e554c1c12..bd791c4a92 100644 --- a/src/java/pt/lsts/neptus/types/vehicle/VehiclesHolder.java +++ b/src/java/pt/lsts/neptus/types/vehicle/VehiclesHolder.java @@ -185,7 +185,7 @@ public static VehicleType getVehicleWithImei(String imei) { return vtList.stream().filter(vt -> vt.getProtocolsArgs().containsKey(CommMean.IRIDIUM)).filter(vt -> { IridiumArgs args = (IridiumArgs) vt.getProtocolsArgs().get(CommMean.IRIDIUM); - return args.getImei().equals(imei); + return args.getImei().equals(imei) || (args.getImei1() != null && args.getImei1().equals(imei)); }).findFirst().orElse(null); } diff --git a/src/java/pt/lsts/neptus/util/conf/GeneralPreferences.java b/src/java/pt/lsts/neptus/util/conf/GeneralPreferences.java index 1de2ed78b6..a2d5ac2bb4 100644 --- a/src/java/pt/lsts/neptus/util/conf/GeneralPreferences.java +++ b/src/java/pt/lsts/neptus/util/conf/GeneralPreferences.java @@ -132,6 +132,18 @@ public class GeneralPreferences implements PropertiesProvider { " A value too high will create inbound messages delay.") public static int imcReachabilityTestTimeout = 50; + @NeptusProperty(name = "Communication - Send Use New Multi-Channels", category = "IMC Communications", userLevel = LEVEL.ADVANCED, + description = "To use the new multichannel comms. Enable or disable") + public static boolean imcUseNewMultiChannelCommsEnable = false; + + @NeptusProperty(name = "IMC Channels to Use", category = "IMC Communications", userLevel = LEVEL.ADVANCED, + description = "Comma separated transports list. Valid values are (WiFi, Acoustic, GSM, Satellite). (The order implies preference of use.)") + public static String imcChannelsToUse = "WiFi, Acoustic, Satellite"; + + @NeptusProperty(name = "IMC Channel - Max Acoustic Distance (m)", category = "IMC Communications", userLevel = LEVEL.ADVANCED, + description = "The maximum distance in meters to use the acoustic channel.") + public static int imcChannelMaxAcousticDistanceMeters = 2_000; + // ------------------------------------------------------------------------- @NeptusProperty(name = "Logs Downloader - Enable Partial Download", category = "IMC Logs Downloader", userLevel = LEVEL.ADVANCED, diff --git a/src/resources/images/buttons/tick.png b/src/resources/images/buttons/tick.png new file mode 100644 index 0000000000..9b79a6f4c8 Binary files /dev/null and b/src/resources/images/buttons/tick.png differ