From 6e4b76e71f6c2a1b8f9c446a7cd2bf183324e5a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:18:18 +0000 Subject: [PATCH 01/25] Bump com.google.protobuf:protobuf-java from 3.19.6 to 3.25.5 Bumps [com.google.protobuf:protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.19.6 to 3.25.5. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.19.6...v3.25.5) --- updated-dependencies: - dependency-name: com.google.protobuf:protobuf-java dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b4bc0d6d..c7ff27fc 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ com.google.protobuf protobuf-java - 3.19.6 + 3.25.5 org.orekit From 78f42968ebfbb91da9a11df5a4c36512237a7dc1 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sun, 22 Sep 2024 12:05:37 +0100 Subject: [PATCH 02/25] direct control for rotator SkyWatcher Allview --- .../rotator/allview/AllviewClient.java | 250 ++++++++++++++++++ .../r2cloud/rotator/allview/AllviewMotor.java | 162 ++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 src/test/java/ru/r2cloud/rotator/allview/AllviewClient.java create mode 100644 src/test/java/ru/r2cloud/rotator/allview/AllviewMotor.java diff --git a/src/test/java/ru/r2cloud/rotator/allview/AllviewClient.java b/src/test/java/ru/r2cloud/rotator/allview/AllviewClient.java new file mode 100644 index 00000000..3ed0aa0b --- /dev/null +++ b/src/test/java/ru/r2cloud/rotator/allview/AllviewClient.java @@ -0,0 +1,250 @@ +package ru.r2cloud.rotator.allview; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fazecast.jSerialComm.SerialPort; +import com.fazecast.jSerialComm.SerialPortInvalidPortException; + +import ru.r2cloud.lora.loraat.SerialInterface; +import ru.r2cloud.lora.loraat.SerialPortInterface; +import ru.r2cloud.rotctrld.Position; + +public class AllviewClient { + + private static final Logger LOG = LoggerFactory.getLogger(AllviewClient.class); + private static final int CHECK_STATUS_DELAY = 100; + + private final String portDescriptor; + private final int timeout; + private final SerialInterface serial; + + private SerialPortInterface port; + private BufferedReader is; + private BufferedWriter os; + + private AllviewMotor az; + private AllviewMotor el; + + public AllviewClient(String portDescriptor, int timeout, SerialInterface serial) { + this.portDescriptor = portDescriptor; + this.timeout = timeout; + this.serial = serial; + } + + public void slew(Position nextPosition, Position previousPosition) throws IOException { + double currentAzimuth; + if (az.isFastMode()) { + try { + // check if position overrun. i.e. + currentAzimuth = az.getPosition(); + } catch (IOException e) { + currentAzimuth = previousPosition.getAzimuth(); + } + } else { + currentAzimuth = previousPosition.getAzimuth(); + } + double azimuthDelta = nextPosition.getAzimuth() - currentAzimuth; + // counter clock wise + if (azimuthDelta > 180.0) { + azimuthDelta = 0 - currentAzimuth - (360.0 - nextPosition.getAzimuth()); + } + // clock wise + if (azimuthDelta < -180.0) { + azimuthDelta = (360.0 - currentAzimuth) + nextPosition.getAzimuth(); + } + double elevationDelta; + if (el.isFastMode()) { + try { + elevationDelta = nextPosition.getElevation() - el.getPosition(); + } catch (IOException e) { + elevationDelta = nextPosition.getElevation() - previousPosition.getElevation(); + } + } else { + elevationDelta = nextPosition.getElevation() - previousPosition.getElevation(); + } + double azimuthDeltaAbs = Math.abs(azimuthDelta); + double elevationDeltaAbs = Math.abs(elevationDelta); + + boolean azStopRequired = az.isStopRequired(azimuthDelta); + boolean elStopRequired = el.isStopRequired(elevationDelta); + if (!azStopRequired && !elStopRequired) { + if (azimuthDeltaAbs > elevationDeltaAbs) { + az.slew(azimuthDelta); + el.slew(elevationDelta); + } else { + el.slew(elevationDelta); + az.slew(azimuthDelta); + } + } else if (azStopRequired && !elStopRequired) { + el.slew(elevationDelta); + az.stop(); + while (!az.waitMotorStop()) { + try { + Thread.sleep(CHECK_STATUS_DELAY); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + az.slew(azimuthDelta); + } else if (!azStopRequired && elStopRequired) { + az.slew(azimuthDelta); + el.stop(); + while (!el.waitMotorStop()) { + try { + Thread.sleep(CHECK_STATUS_DELAY); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + el.slew(elevationDelta); + } else { + az.stop(); + el.stop(); + while (!az.waitMotorStop() || !el.waitMotorStop()) { + try { + Thread.sleep(CHECK_STATUS_DELAY); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + if (azimuthDeltaAbs > elevationDeltaAbs) { + az.slew(azimuthDelta); + el.slew(elevationDelta); + } else { + el.slew(elevationDelta); + az.slew(azimuthDelta); + } + } + } + + public synchronized void start() throws Exception { + port = serial.getCommPort(portDescriptor); + port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, timeout, timeout); + port.setBaudRate(9600); + port.setParity(SerialPort.NO_PARITY); + port.setNumStopBits(SerialPort.ONE_STOP_BIT); + if (!port.openPort()) { + throw new SerialPortInvalidPortException("can't open port"); + } + is = new BufferedReader(new InputStreamReader(port.getInputStream(), StandardCharsets.US_ASCII)); + os = new BufferedWriter(new OutputStreamWriter(port.getOutputStream(), StandardCharsets.US_ASCII)); + + az = new AllviewMotor(1, this); + el = new AllviewMotor(2, this); + } + + public synchronized void stop() { + if (az != null) { + try { + az.stop(); + } catch (IOException e) { + LOG.info("can't stop azimuth", e); + } + } + if (el != null) { + try { + el.stop(); + } catch (IOException e) { + LOG.info("can't stop elevation", e); + } + } + if (port != null) { + port.closePort(); + } + } + + public synchronized String sendMessage(String message) throws IOException { + os.append(message); + os.flush(); + char curChar = '\r'; + while ((curChar = (char) is.read()) != '\r') { + // skip + } + StringBuilder str = new StringBuilder(); + while ((curChar = (char) is.read()) != '\r') { + str.append(curChar); + } + String result = str.toString().trim(); + if (result.startsWith("!")) { + throw new IOException("invalid response: " + convertErrorCode(Integer.valueOf(result.substring(1))) + " request: " + message); + } + // ignore "=" + result = result.substring(1); + return result; + } + + private static String convertErrorCode(int code) { + switch (code) { + case 0: { + return "Unknown Command"; + } + case 1: { + return "Command Length Error"; + } + case 2: { + return "Motor not Stopped"; + } + case 3: { + return "Invalid Character"; + } + case 4: { + return "Not Initialized"; + } + case 5: { + return "Driver Sleeping"; + } + case 7: { + return "PEC Training is running"; + } + case 8: { + return "No Valid PEC data"; + } + default: + return "unknown error code: " + code; + } + } + + public Position getPosition() throws IOException { + Position result = new Position(); + result.setAzimuth(az.getPosition()); + result.setElevation(el.getPosition()); + return result; + } + + public void setPosition(Position position) throws IOException { + az.setPosition(position.getAzimuth()); + el.setPosition(position.getElevation()); + } + + public void stopMotors() throws IOException { + az.stop(); + el.stop(); + } + + public void waitMotorStop() throws IOException { + while (!az.waitMotorStop() || !el.waitMotorStop()) { + try { + Thread.sleep(CHECK_STATUS_DELAY); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + + public String getMotorBoardVersion() throws IOException { + return sendMessage(":e1\r"); + } + +} diff --git a/src/test/java/ru/r2cloud/rotator/allview/AllviewMotor.java b/src/test/java/ru/r2cloud/rotator/allview/AllviewMotor.java new file mode 100644 index 00000000..1243c769 --- /dev/null +++ b/src/test/java/ru/r2cloud/rotator/allview/AllviewMotor.java @@ -0,0 +1,162 @@ +package ru.r2cloud.rotator.allview; + +import java.io.IOException; + +public class AllviewMotor { + + private boolean fastMode; + private int index; + private AllviewClient client; + private boolean stopped = false; + private int currentSpeed = 0; + private double currentDegree; + private int cpr; + private int timerInterruptFreq; + + public AllviewMotor(int index, AllviewClient client) { + this.index = index; + this.client = client; + } + + public void setPosition(double degrees) throws IOException { + client.sendMessage(":G" + index + "00\r"); + if (cpr == 0) { + cpr = convertInteger(client.sendMessage(":a" + index + "\r")); + } + int number = (int) ((cpr / 360.0) * degrees) | 0x800000; + String command = String.format(":S%d%02X%02X%02X\r", index, (number & 0xFF), ((number >> 8) & 0xFF), ((number >> 16) & 0xFF)); + client.sendMessage(command); + client.sendMessage(":J" + index + "\r"); + stopped = false; + fastMode = true; + currentSpeed = 0; + } + + public double getPosition() throws IOException { + return convertAngle(client.sendMessage(":j" + index + "\r")); + } + + private static int convertInteger(String inputStr) { + int input = Integer.valueOf(inputStr, 16); + return ((input & 0x0000FF) << 16) | (input & 0x00FF00) | ((input & 0xFF0000) >> 16); + } + + public static double convertAngle(String inputStr) { + int converted = convertInteger(inputStr); + boolean positive = true; + if (converted > 0x800000) { + converted = converted - 0x800000; + } else { + converted = 0x800000 - converted; + positive = false; + } + double ticks_per_angle = (double) 865050 / 360.0; + double result = converted / ticks_per_angle; + if (!positive) { + return 360.0 - result; + } + return result; + } + + public void stop() throws IOException { + if (stopped) { + return; + } + client.sendMessage(":K" + index + "\r"); + } + + public boolean waitMotorStop() throws IOException { + if (stopped) { + return stopped; + } + String status = client.sendMessage(":f" + index + "\r"); + int num = Integer.valueOf(status); + if (((num >> 1) & 0x1) == 0) { + stopped = true; + currentSpeed = 0; + } + return stopped; + } + + public boolean isStopped() { + return stopped; + } + + public boolean isFastMode() { + return fastMode; + } + + public void slew(double degreePerSec) throws IOException { + boolean changeDirectionToCw = (degreePerSec > 0 && currentDegree < 0); + boolean changeDirectionToCcw = (degreePerSec < 0 && currentDegree > 0); + boolean changeDirectionNeeded = (changeDirectionToCw || changeDirectionToCcw); + double degreeAbs = Math.abs(degreePerSec); + int number = (int) ((getTimerInterruptFreq() * 360.0f) / degreeAbs / 865050.0f); + if (currentSpeed != 0 && currentSpeed == number && !changeDirectionNeeded) { + return; + } + currentDegree = degreePerSec; + currentSpeed = number; + if (degreeAbs > 0.37037037037037) { + // slow mode to fast mode + if (stopped) { + int direction = degreePerSec > 0 ? 0 : 1; + client.sendMessage(":G" + index + "3" + direction + "\r"); + } + number = 32 * number; + String command = String.format(":I%d%02X%02X%02X\r", index, (number & 0xFF), ((number >> 8) & 0xFF), ((number >> 16) & 0xFF)); + client.sendMessage(command); + client.sendMessage(":J" + index + "\r"); + fastMode = true; + } else { + if (stopped) { + int direction = degreePerSec > 0 ? 0 : 1; + client.sendMessage(":G" + index + "1" + direction + "\r"); + } + String command = String.format(":I%d%02X%02X%02X\r", index, (number & 0xFF), ((number >> 8) & 0xFF), ((number >> 16) & 0xFF)); + client.sendMessage(command); + if (stopped) { + // switch from fast to slow mode require full stop. thus start motor command + client.sendMessage(":J" + index + "\r"); + } + fastMode = false; + } + stopped = false; + } + + public boolean isStopRequired(double degree) throws IOException { + if (stopped) { + return false; + } + // transition from slow to fast + if (!fastMode && Math.abs(degree) > 0.37037037037037) { + return true; + } + if (fastMode) { + // transition from fast to slow + if (Math.abs(degree) < 0.37037037037037) { + return true; + } + // only when required speed is actually changed + int number = (int) ((getTimerInterruptFreq() * 360.0f) / Math.abs(degree) / 865050.0f); + if (currentSpeed != number) { + return true; + } + } + boolean changeDirectionToCw = (degree > 0 && currentDegree < 0); + boolean changeDirectionToCcw = (degree < 0 && currentDegree > 0); + if (changeDirectionToCw || changeDirectionToCcw) { + return true; + } + + return false; + } + + private int getTimerInterruptFreq() throws IOException { + if (timerInterruptFreq == 0) { + timerInterruptFreq = convertInteger(client.sendMessage(":b1\r")); + } + return timerInterruptFreq; + } + +} From 6250179bc4c86e05c86a6ee4e160ea3c824f16f5 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sat, 28 Sep 2024 22:45:33 +0100 Subject: [PATCH 03/25] some test to calculate az/el difference for different stale TLE --- .../CalculateAzElErrorForStaleTle.java | 124 ++++++++++++++++++ src/test/resources/39444_historical.txt | 104 +++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 src/test/java/ru/r2cloud/CalculateAzElErrorForStaleTle.java create mode 100644 src/test/resources/39444_historical.txt diff --git a/src/test/java/ru/r2cloud/CalculateAzElErrorForStaleTle.java b/src/test/java/ru/r2cloud/CalculateAzElErrorForStaleTle.java new file mode 100644 index 00000000..8285235b --- /dev/null +++ b/src/test/java/ru/r2cloud/CalculateAzElErrorForStaleTle.java @@ -0,0 +1,124 @@ +package ru.r2cloud; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.InputStreamReader; +import java.nio.file.FileSystems; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TimeZone; + +import org.hipparchus.util.FastMath; +import org.orekit.bodies.GeodeticPoint; +import org.orekit.frames.TopocentricFrame; +import org.orekit.propagation.analytical.tle.TLEPropagator; +import org.orekit.time.TimeScalesFactory; + +import ru.r2cloud.predict.PredictOreKit; +import ru.r2cloud.rotctrld.Position; +import ru.r2cloud.util.Configuration; + +public class CalculateAzElErrorForStaleTle { + + public static void main(String[] args) throws Exception { + + Configuration config = new Configuration(CalculateAzElErrorForStaleTle.class.getClassLoader().getResourceAsStream("config-dev.properties"), System.getProperty("user.home") + File.separator + ".r2cloud", "config-common-test.properties", FileSystems.getDefault()); + config.setProperty("locaiton.lat", "51.721"); + config.setProperty("locaiton.lon", "5.030"); + config.setProperty("scheduler.orekit.path", "./src/test/resources/data/orekit-data"); + + Double lat = config.getDouble("locaiton.lat"); + Double lon = config.getDouble("locaiton.lon"); + + PredictOreKit predict = new PredictOreKit(config); + TopocentricFrame groundStation = predict.getPosition(new GeodeticPoint(FastMath.toRadians(lat), FastMath.toRadians(lon), 0.0)); + + long reqStart = 1727055832679L; + long reqEnd = 1727056477595L; + + Calendar start = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.UK); + start.setTimeInMillis(reqStart); + + List calculateForDays = new ArrayList<>(); + calculateForDays.add(-2); + calculateForDays.add(-4); + calculateForDays.add(-8); + calculateForDays.add(-10); + calculateForDays.add(-20); + + List theMostRecent = new ArrayList<>(); + + Map> listOfCoordinates = new HashMap<>(); + try (BufferedReader r = new BufferedReader(new InputStreamReader(CalculateAzElErrorForStaleTle.class.getClassLoader().getResourceAsStream("39444_historical.txt")))) { + String line = null; + while ((line = r.readLine()) != null) { + org.orekit.propagation.analytical.tle.TLE tle = new org.orekit.propagation.analytical.tle.TLE(line, r.readLine()); + Date tleEpoch = tle.getDate().toDate(TimeScalesFactory.getUTC()); + int daysDiff = (int) ((tleEpoch.getTime() - reqStart) / (24 * 60 * 60 * 1000)); + if (!calculateForDays.contains(daysDiff) && daysDiff != -1) { + // skip not interesting day + continue; + } + System.out.println("processing TLE " + (-daysDiff) + " days ago: " + tleEpoch); + calculateForDays.remove(Integer.valueOf(daysDiff)); + TLEPropagator tlePropagator = TLEPropagator.selectExtrapolator(tle); + List current = new ArrayList<>(); + for (long curStart = reqStart; curStart < reqEnd; curStart += 1000) { + current.add(predict.getSatellitePosition(curStart, groundStation, tlePropagator)); + } + if (daysDiff == -1 && theMostRecent.isEmpty()) { + theMostRecent.addAll(current); + } else { + listOfCoordinates.put(daysDiff, current); + } + } + } + + System.out.println("saving the most recent trajectory"); + + // not necessary. just for testing purposes + try (BufferedWriter w = new BufferedWriter(new FileWriter("observation.csv"))) { + w.append("az,el\n"); + for (Position cur : theMostRecent) { + w.append(String.valueOf(cur.getAzimuth())).append(',').append(String.valueOf(cur.getElevation())).append('\n'); + } + } + + System.out.println("calculating difference..."); + + try (BufferedWriter w = new BufferedWriter(new FileWriter("diff.csv"))) { + for (int j = 0; j < theMostRecent.size(); j++) { + if (j == 0) { + int i = 0; + for (Entry> cur : listOfCoordinates.entrySet()) { + String daysAgo = String.valueOf(Math.abs(cur.getKey())); + if (i != 0) { + w.append(','); + } + w.append("az").append(daysAgo).append(",el").append(daysAgo); + i++; + } + w.append('\n'); + } + int i = 0; + for (Entry> cur : listOfCoordinates.entrySet()) { + if (i != 0) { + w.append(','); + } + w.append(String.valueOf(theMostRecent.get(j).getAzimuth() - cur.getValue().get(j).getAzimuth())).append(',').append(String.valueOf(theMostRecent.get(j).getElevation() - cur.getValue().get(j).getElevation())); + i++; + } + w.append('\n'); + } + } + } + +} diff --git a/src/test/resources/39444_historical.txt b/src/test/resources/39444_historical.txt new file mode 100644 index 00000000..60c9a8e2 --- /dev/null +++ b/src/test/resources/39444_historical.txt @@ -0,0 +1,104 @@ +1 39444U 13066AE 24245.62306633 .00017224 00000-0 15917-2 0 9997 +2 39444 97.7242 203.2987 0044266 244.9983 114.6636 14.93893000581513 +1 39444U 13066AE 24245.89098938 .00016203 00000-0 14977-2 0 9993 +2 39444 97.7245 203.5620 0044233 244.0412 115.6247 14.93900762581552 +1 39444U 13066AE 24245.89098938 .00016203 00000-0 14977-2 0 9993 +2 39444 97.7245 203.5620 0044233 244.0412 115.6247 14.93900762582148 +1 39444U 13066AE 24246.42683146 .00014887 00000-0 13762-2 0 9991 +2 39444 97.7247 204.0890 0044220 242.1416 117.5321 14.93915076581635 +1 39444U 13066AE 24246.89568804 .00016677 00000-0 15400-2 0 9995 +2 39444 97.7251 204.5501 0044219 240.4887 119.1920 14.93931901581701 +1 39444U 13066AE 24246.89568804 .00016677 00000-0 15400-2 0 9995 +2 39444 97.7251 204.5501 0044219 240.4887 119.1920 14.93931901582296 +1 39444U 13066AE 24247.43151926 .00015845 00000-0 14631-2 0 9990 +2 39444 97.7254 205.0772 0044222 238.6036 121.0855 14.93946711581781 +1 39444U 13066AE 24247.83338799 .00018328 00000-0 16902-2 0 9991 +2 39444 97.7257 205.4724 0044221 237.1883 122.5074 14.93962995581846 +1 39444U 13066AE 24248.43618491 .00019978 00000-0 18404-2 0 9999 +2 39444 97.7261 206.0654 0044237 235.0663 124.6398 14.93984263581934 +1 39444U 13066AE 24248.77106769 .00023196 00000-0 21341-2 0 9997 +2 39444 97.7263 206.3947 0044272 233.8750 125.8369 14.94000201581984 +1 39444U 13066AE 24248.83804369 .00020086 00000-0 18495-2 0 9996 +2 39444 97.7263 206.4607 0044278 233.6660 126.0469 14.93999755581997 +1 39444U 13066AE 24249.84267448 .00018884 00000-0 17378-2 0 9991 +2 39444 97.7268 207.4491 0044299 230.2006 129.5311 14.94036113582147 +1 39444U 13066AE 24249.84267448 .00018884 00000-0 17378-2 0 9991 +2 39444 97.7268 207.4491 0044299 230.2006 129.5311 14.94036113582732 +1 39444U 13066AE 24250.44544075 .00017831 00000-0 16405-2 0 9994 +2 39444 97.7269 208.0422 0044342 228.1083 131.6352 14.94057523582236 +1 39444U 13066AE 24250.84728001 .00018234 00000-0 16766-2 0 9999 +2 39444 97.7271 208.4375 0044398 226.7414 133.0098 14.94072791582881 +1 39444U 13066AE 24251.31608746 .00018745 00000-0 17225-2 0 9993 +2 39444 97.7271 208.8987 0044445 225.1572 134.6033 14.94090408582369 +1 39444U 13066AE 24251.85186098 .00019310 00000-0 17731-2 0 9994 +2 39444 97.7272 209.4259 0044511 223.3414 136.4303 14.94110860582442 +1 39444U 13066AE 24251.85186098 .00019310 00000-0 17731-2 0 9994 +2 39444 97.7272 209.4259 0044511 223.3414 136.4303 14.94110860583038 +1 39444U 13066AE 24252.45459817 .00018672 00000-0 17138-2 0 9994 +2 39444 97.7273 210.0191 0044599 221.3129 138.4715 14.94131535582534 +1 39444U 13066AE 24252.85641854 .00017161 00000-0 15753-2 0 9995 +2 39444 97.7272 210.4144 0044653 219.9455 139.8477 14.94143781582593 +1 39444U 13066AE 24252.85641854 .00017161 00000-0 15753-2 0 9995 +2 39444 97.7272 210.4144 0044653 219.9455 139.8477 14.94143781583189 +1 39444U 13066AE 24253.45914186 .00016904 00000-0 15509-2 0 9996 +2 39444 97.7271 211.0075 0044753 217.9024 141.9043 14.94164191582684 +1 39444U 13066AE 24253.86095283 .00017544 00000-0 16086-2 0 9991 +2 39444 97.7270 211.4031 0044784 216.5307 143.2856 14.94179150582747 +1 39444U 13066AE 24254.12882473 .00018092 00000-0 16581-2 0 9995 +2 39444 97.7268 211.6668 0044820 215.6127 144.2101 14.94188594583379 +1 39444U 13066AE 24254.86546390 .00016325 00000-0 14960-2 0 9997 +2 39444 97.7266 212.3918 0044927 213.1126 146.7281 14.94212650582896 +1 39444U 13066AE 24255.46816071 .00014373 00000-0 13174-2 0 9997 +2 39444 97.7263 212.9849 0045030 211.0875 148.7680 14.94229023582983 +1 39444U 13066AE 24255.46816071 .00014373 00000-0 13174-2 0 9997 +2 39444 97.7263 212.9849 0045030 211.0875 148.7680 14.94229023583579 +1 39444U 13066AE 24255.86995437 .00016192 00000-0 14825-2 0 9990 +2 39444 97.7263 213.3804 0045106 209.7557 150.1097 14.94243549583040 +1 39444U 13066AE 24256.60656588 .00035585 00000-0 32387-2 0 9994 +2 39444 97.7261 214.1054 0045230 207.2355 152.6493 14.94290083583155 +1 39444U 13066AE 24256.87442215 .00023562 00000-0 21500-2 0 9990 +2 39444 97.7261 214.3690 0045248 206.3163 153.5758 14.94286259583197 +1 39444U 13066AE 24256.87442215 .00023562 00000-0 21500-2 0 9990 +2 39444 97.7261 214.3690 0045248 206.3163 153.5758 14.94286259583782 +1 39444U 13066AE 24258.41454928 .00024116 00000-0 21961-2 0 9993 +2 39444 97.7257 215.8854 0045498 200.9288 159.0071 14.94350418583429 +1 39444U 13066AE 24258.81631366 .00014492 00000-0 13235-2 0 9991 +2 39444 97.7260 216.2808 0045476 199.5570 160.3908 14.94353883584075 +1 39444U 13066AE 24258.88327388 .00014597 00000-0 13330-2 0 9996 +2 39444 97.7261 216.3467 0045481 199.3412 160.6084 14.94356285583499 +1 39444U 13066AE 24259.61983314 .00015337 00000-0 13993-2 0 9993 +2 39444 97.7265 217.0718 0045548 196.7810 163.1906 14.94376715583601 +1 39444U 13066AE 24260.15550661 .00016186 00000-0 14757-2 0 9991 +2 39444 97.7267 217.5992 0045579 194.8890 165.0992 14.94391699584271 +1 39444U 13066AE 24260.35638314 .00013390 00000-0 12221-2 0 9993 +2 39444 97.7267 217.7970 0045608 194.1960 165.7982 14.94393697583717 +1 39444U 13066AE 24260.82509164 .00013952 00000-0 12726-2 0 9999 +2 39444 97.7271 218.2582 0045635 192.5425 167.4663 14.94407066583783 +1 39444U 13066AE 24261.36075201 .00034920 00000-0 31646-2 0 9999 +2 39444 97.7275 218.7857 0045682 190.7113 169.3139 14.94447063583862 +1 39444U 13066AE 24261.36075201 .00034920 00000-0 31646-2 0 9999 +2 39444 97.7275 218.7857 0045682 190.7113 169.3139 14.94447063584458 +1 39444U 13066AE 24261.82945186 .00017781 00000-0 16179-2 0 9998 +2 39444 97.7278 219.2469 0045719 189.0579 170.9821 14.94441435583939 +1 39444U 13066AE 24261.82945186 .00017781 00000-0 16179-2 0 9998 +2 39444 97.7278 219.2469 0045719 189.0579 170.9821 14.94441435584525 +1 39444U 13066AE 24262.43205808 .00015256 00000-0 13888-2 0 9995 +2 39444 97.7281 219.8402 0045763 186.9869 173.0719 14.94457338584023 +1 39444U 13066AE 24262.76683703 .00012048 00000-0 10982-2 0 9993 +2 39444 97.7284 220.1699 0045799 185.8412 174.2280 14.94462480584660 +1 39444U 13066AE 24262.83379279 .00010591 00000-0 96623-3 0 9999 +2 39444 97.7284 220.2358 0045811 185.6082 174.4631 14.94462353584086 +1 39444U 13066AE 24263.36943547 .00011283 00000-0 10286-2 0 9992 +2 39444 97.7286 220.7632 0045855 183.7564 176.3318 14.94474483584169 +1 39444U 13066AE 24263.83811865 .00013486 00000-0 12275-2 0 9998 +2 39444 97.7291 221.2249 0045893 182.1715 177.9314 14.94488037584239 +1 39444U 13066AE 24263.83811865 .00013486 00000-0 12275-2 0 9998 +2 39444 97.7291 221.2249 0045893 182.1715 177.9314 14.94488037584824 +1 39444U 13066AE 24264.64156851 .00010796 00000-0 98364-3 0 9990 +2 39444 97.7296 222.0162 0045975 179.4514 180.6765 14.94502714584357 +1 39444U 13066AE 24264.64156851 .00010796 00000-0 98364-3 0 9990 +2 39444 97.7296 222.0162 0045975 179.4514 180.6765 14.94502714584942 +1 39444U 13066AE 24265.44500922 .00011343 00000-0 10326-2 0 9991 +2 39444 97.7299 222.8075 0046046 176.7978 183.3546 14.94520285584478 +1 39444U 13066AE 24265.84672676 .00010048 00000-0 91532-3 0 9991 +2 39444 97.7299 223.2032 0046081 175.4958 184.6686 14.94526402585128 \ No newline at end of file From 04a21fb9774be3cd384fe0f81aba841b23bea4c1 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sun, 29 Sep 2024 09:53:39 +0100 Subject: [PATCH 04/25] comment out flaky test + make AzEl calculation output more user-friendly --- .../java/ru/r2cloud/CalculateAzElErrorForStaleTle.java | 9 ++++++--- .../java/ru/r2cloud/satellite/PredictOreKitTest.java | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/test/java/ru/r2cloud/CalculateAzElErrorForStaleTle.java b/src/test/java/ru/r2cloud/CalculateAzElErrorForStaleTle.java index 8285235b..f30ec828 100644 --- a/src/test/java/ru/r2cloud/CalculateAzElErrorForStaleTle.java +++ b/src/test/java/ru/r2cloud/CalculateAzElErrorForStaleTle.java @@ -67,11 +67,14 @@ public static void main(String[] args) throws Exception { // skip not interesting day continue; } + if (daysDiff == -1 && !theMostRecent.isEmpty()) { + continue; + } System.out.println("processing TLE " + (-daysDiff) + " days ago: " + tleEpoch); calculateForDays.remove(Integer.valueOf(daysDiff)); TLEPropagator tlePropagator = TLEPropagator.selectExtrapolator(tle); List current = new ArrayList<>(); - for (long curStart = reqStart; curStart < reqEnd; curStart += 1000) { + for (long curStart = reqStart; curStart < reqEnd; curStart += 10000) { current.add(predict.getSatellitePosition(curStart, groundStation, tlePropagator)); } if (daysDiff == -1 && theMostRecent.isEmpty()) { @@ -99,7 +102,7 @@ public static void main(String[] args) throws Exception { if (j == 0) { int i = 0; for (Entry> cur : listOfCoordinates.entrySet()) { - String daysAgo = String.valueOf(Math.abs(cur.getKey())); + String daysAgo = String.valueOf(Math.abs(cur.getKey()) - 1); if (i != 0) { w.append(','); } @@ -113,7 +116,7 @@ public static void main(String[] args) throws Exception { if (i != 0) { w.append(','); } - w.append(String.valueOf(theMostRecent.get(j).getAzimuth() - cur.getValue().get(j).getAzimuth())).append(',').append(String.valueOf(theMostRecent.get(j).getElevation() - cur.getValue().get(j).getElevation())); + w.append(String.format("%.2f", theMostRecent.get(j).getAzimuth() - cur.getValue().get(j).getAzimuth())).append(',').append(String.format("%.2f", theMostRecent.get(j).getElevation() - cur.getValue().get(j).getElevation())); i++; } w.append('\n'); diff --git a/src/test/java/ru/r2cloud/satellite/PredictOreKitTest.java b/src/test/java/ru/r2cloud/satellite/PredictOreKitTest.java index da4f2f5c..2535880d 100644 --- a/src/test/java/ru/r2cloud/satellite/PredictOreKitTest.java +++ b/src/test/java/ru/r2cloud/satellite/PredictOreKitTest.java @@ -82,7 +82,8 @@ public void testFixedDirectionalAntenna() throws Exception { assertPosition("00:29:32", "00:31:55", schedule.get(3)); } - @Test + //flaky test. sometimes returns empty, sometimes list of invalid passes. there is an issue somewhere in orekit +// @Test public void testStaleTle() throws Exception { config.setProperty("locaiton.lat", "51.82"); config.setProperty("locaiton.lon", "-0.05"); From f821d0cca4cbd1a8683a046f0104e9edace98e08 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sun, 29 Sep 2024 18:30:47 +0100 Subject: [PATCH 05/25] create expected user if it doesn't exist in recent raspbian images pi user doesn't exist by default --- src/main/deb/postinst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/deb/postinst b/src/main/deb/postinst index 7e589096..779e71ec 100644 --- a/src/main/deb/postinst +++ b/src/main/deb/postinst @@ -1,6 +1,20 @@ chmod 755 ${config.installDir}/lib/download.sh ${config.installDir}/lib/download.sh ${config.installDir}/lib/ /usr/share/java/r2cloud/ +if id "${config.user}" &>/dev/null; then + echo "User '${config.user}' already exists." +else + useradd -m ${config.user} + echo "User '${config.user}' created." + + if ! getent group ${config.group} > /dev/null; then + groupadd ${config.group} + echo "Group '${config.group}' created." + fi + + usermod -aG dialout,plugdev ${config.user} +fi + chown -R ${config.user}:${config.group} ${config.installDir} /usr/share/java/r2cloud/*.jar chmod 640 ${config.installDir}/lib/*.jar /usr/share/java/r2cloud/*.jar From b8c0111e91409a030a5f4546ad88100e5f2344a6 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sun, 29 Sep 2024 19:00:22 +0100 Subject: [PATCH 06/25] better installation scripts --- src/main/deb/postinst | 25 ++++++++++++++++--------- src/main/deb/prerm | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/deb/postinst b/src/main/deb/postinst index 779e71ec..a2c97f82 100644 --- a/src/main/deb/postinst +++ b/src/main/deb/postinst @@ -6,15 +6,26 @@ if id "${config.user}" &>/dev/null; then else useradd -m ${config.user} echo "User '${config.user}' created." +fi + +if ! getent group ${config.group} > /dev/null; then + groupadd ${config.group} + echo "Group '${config.group}' created." +fi - if ! getent group ${config.group} > /dev/null; then - groupadd ${config.group} - echo "Group '${config.group}' created." - fi +if [ $(getent group dialout) ]; then + usermod -a -G dialout ${config.user} +fi - usermod -aG dialout,plugdev ${config.user} +if [ $(getent group plugdev) ]; then + usermod -a -G plugdev ${config.user} fi +if [ $(getent group systemd-journal) ]; then + usermod -a -G systemd-journal ${config.user} +fi + + chown -R ${config.user}:${config.group} ${config.installDir} /usr/share/java/r2cloud/*.jar chmod 640 ${config.installDir}/lib/*.jar /usr/share/java/r2cloud/*.jar @@ -58,10 +69,6 @@ if [ -f /etc/sudoers.d/nginx ]; then chmod 440 /etc/sudoers.d/nginx fi -if [ $(getent group systemd-journal) ]; then - usermod -a -G systemd-journal ${config.user} -fi - if [ ! -d /var/log/journal ]; then echo "enable persistent journald logs" mkdir -p /var/log/journal diff --git a/src/main/deb/prerm b/src/main/deb/prerm index 9c0db991..104b090f 100644 --- a/src/main/deb/prerm +++ b/src/main/deb/prerm @@ -1,2 +1,2 @@ echo "removing libs..." -rm ${config.installDir}/lib/*.jar \ No newline at end of file +rm -f ${config.installDir}/lib/*.jar \ No newline at end of file From b2ea8247a69f93b356b8ac410b06debb5cdd9007 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Fri, 4 Oct 2024 21:20:35 +0100 Subject: [PATCH 07/25] remove deprecation warning while using r2cloud.gpg --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 26b74ba6..e1df25ff 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ Please check recommended [bill of materials](https://github.com/dernasherbrezon/ - Login via SSH and create ```r2cloud.txt``` file in /boot directory. This file should contain any random string. This string is a login token. This token will be used during initial setup. - Execute the following commands: ``` -sudo apt-get install dirmngr lsb-release -sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A5A70917 -sudo bash -c "echo 'deb http://apt.leosatdata.com $(lsb_release --codename --short) main' > /etc/apt/sources.list.d/r2cloud.list" +sudo apt-get install curl lsb-release +curl -fsSL https://leosatdata.com/r2cloud.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/r2cloud.gpg +sudo bash -c "echo 'deb [signed-by=/usr/share/keyrings/r2cloud.gpg] http://apt.leosatdata.com $(lsb_release --codename --short) main' > /etc/apt/sources.list.d/r2cloud.list" sudo apt-get update sudo apt-get install r2cloud ``` From 34c12c3a51aaa399ba4f8e4b7c51913e57f68a2b Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sat, 5 Oct 2024 08:26:22 +0100 Subject: [PATCH 08/25] disable lrpt on meteor-m 2 --- src/main/resources/satellites.json | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/main/resources/satellites.json b/src/main/resources/satellites.json index 78c1dcae..1487ba32 100644 --- a/src/main/resources/satellites.json +++ b/src/main/resources/satellites.json @@ -5107,24 +5107,6 @@ } ] }, - { - "id": "R2CLOUD4", - "name": "METEOR-M 2", - "noradId": "40069", - "enabled": true, - "transmitters": [ - { - "modulation": "QPSK", - "framing": "LRPT", - "frequency": 137100000, - "bandwidth": 140000, - "baudRates": [ - 72000 - ], - "status": "ENABLED" - } - ] - }, { "id": "R2CLOUD3", "name": "NOAA 19", From 9e759c0fba34d6d95cb84a7175099cf84c9dc4ce Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sat, 5 Oct 2024 08:42:32 +0100 Subject: [PATCH 09/25] fixed unit test --- src/test/java/ru/r2cloud/tle/HousekeepingTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/ru/r2cloud/tle/HousekeepingTest.java b/src/test/java/ru/r2cloud/tle/HousekeepingTest.java index 75ee15a9..14efa4f1 100644 --- a/src/test/java/ru/r2cloud/tle/HousekeepingTest.java +++ b/src/test/java/ru/r2cloud/tle/HousekeepingTest.java @@ -156,6 +156,7 @@ public void start() throws Exception { config = new TestConfiguration(tempFolder, FileSystems.getDefault()); config.setProperty("tle.cacheFileLocation", new File(tempFolder.getRoot(), "tle.json").getAbsolutePath()); + config.setProperty("satellites.meta.location", "./src/test/resources/satellites-test.json"); config.setProperty("satellites.leosatdata.location", new File(tempFolder.getRoot(), "leosatdata.json").getAbsolutePath()); config.setProperty("satellites.leosatdata.new.location", new File(tempFolder.getRoot(), "leosatdata.new.json").getAbsolutePath()); config.setProperty("satellites.satnogs.location", new File(tempFolder.getRoot(), "satnogs.json").getAbsolutePath()); From 159bcc618b3a263769e939f2d8273dc9ac6ef634 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sat, 5 Oct 2024 15:16:31 +0100 Subject: [PATCH 10/25] manually test rotator pointing error + generic rotctrld + custom skywatcher allview --- .../ru/r2cloud/MeasureRotatorPrecision.java | 208 ++++++++++++++++++ src/test/java/ru/r2cloud/OffsetClock.java | 17 ++ .../rotator/allview/AllviewClient.java | 7 + .../r2cloud/rotator/allview/AllviewMotor.java | 8 +- .../allview/AllviewRotatorService.java | 174 +++++++++++++++ 5 files changed, 410 insertions(+), 4 deletions(-) create mode 100644 src/test/java/ru/r2cloud/MeasureRotatorPrecision.java create mode 100644 src/test/java/ru/r2cloud/OffsetClock.java create mode 100644 src/test/java/ru/r2cloud/rotator/allview/AllviewRotatorService.java diff --git a/src/test/java/ru/r2cloud/MeasureRotatorPrecision.java b/src/test/java/ru/r2cloud/MeasureRotatorPrecision.java new file mode 100644 index 00000000..4c2c9e14 --- /dev/null +++ b/src/test/java/ru/r2cloud/MeasureRotatorPrecision.java @@ -0,0 +1,208 @@ +package ru.r2cloud; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.TimeZone; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.regex.Pattern; + +import org.hipparchus.util.FastMath; +import org.orekit.bodies.GeodeticPoint; +import org.orekit.frames.TopocentricFrame; +import org.orekit.propagation.analytical.tle.TLEPropagator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ru.r2cloud.lora.loraat.JSerial; +import ru.r2cloud.model.ObservationRequest; +import ru.r2cloud.model.RotatorConfiguration; +import ru.r2cloud.model.Tle; +import ru.r2cloud.predict.PredictOreKit; +import ru.r2cloud.rotator.allview.AllviewClient; +import ru.r2cloud.rotator.allview.AllviewRotatorService; +import ru.r2cloud.rotctrld.Position; +import ru.r2cloud.rotctrld.RotCtrlException; +import ru.r2cloud.rotctrld.RotctrldClient; +import ru.r2cloud.satellite.RotatorService; +import ru.r2cloud.util.Configuration; +import ru.r2cloud.util.ThreadPoolFactoryImpl; + +public class MeasureRotatorPrecision { + + private static final Logger LOG = LoggerFactory.getLogger(MeasureRotatorPrecision.class); + + private static final int ROTCTRLD_PORT = 8000; + private static final String ALLVIEW_DEVICE_FILE = "/dev/cu.usbserial-A10M67FQ"; + + public static void main(String[] args) throws Exception { + Configuration config = new Configuration(MeasureRotatorPrecision.class.getClassLoader().getResourceAsStream("config-dev.properties"), System.getProperty("user.home") + File.separator + ".r2cloud", "config-common-test.properties", FileSystems.getDefault()); + config.setProperty("locaiton.lat", "51.721"); + config.setProperty("locaiton.lon", "5.030"); + config.setProperty("scheduler.orekit.path", "./src/test/resources/data/orekit-data"); + PredictOreKit predict = new PredictOreKit(config); + + Tle tle = new Tle(new String[] { "funcube-1", "1 39444U 13066AE 20157.75071106 .00000221 00000-0 33451-4 0 9997", "2 39444 97.5589 158.6491 0056696 309.6463 49.9756 14.82127945351637" }); + ObservationRequest req = new ObservationRequest(); + req.setTle(tle); + req.setStartTimeMillis(getTime("2020-06-06 05:50:29")); + req.setEndTimeMillis(getTime("2020-06-06 06:01:42")); + Double lat = config.getDouble("locaiton.lat"); + Double lon = config.getDouble("locaiton.lon"); + req.setGroundStation(new GeodeticPoint(FastMath.toRadians(lat), FastMath.toRadians(lon), 0.0)); + + OffsetClock clock = new OffsetClock(req.getStartTimeMillis()); + RotatorConfiguration rotatorConfig = createValidConfig(); + + // choose which measure to take by uncommeting to the function +// measureRotatorService(predict, req, clock, rotatorConfig); +// measureAllviewService(predict, req, clock, rotatorConfig); + } + + private static void measureAllviewService(PredictOreKit predict, ObservationRequest req, OffsetClock clock, RotatorConfiguration rotatorConfig) throws InterruptedException, IOException, FileNotFoundException { + CountDownLatch latch = new CountDownLatch(1); + final File result = new File("allview.csv"); + AllviewClient client = new AllviewClient(ALLVIEW_DEVICE_FILE, 5000, new JSerial()); + try { + client.start(); + } catch (Exception e1) { + LOG.error("unable to start allview client", e1); + return; + } + Thread monitorThread = new Thread(new Runnable() { + @Override + public void run() { + try (BufferedWriter w = new BufferedWriter(new FileWriter(result))) { + while (!Thread.currentThread().isInterrupted()) { + Position current = client.getPosition(); + long currentMillis = clock.millis(); + w.append(String.valueOf(current.getAzimuth())).append(",").append(String.valueOf(current.getElevation())).append(",").append(String.valueOf(currentMillis)).append("\n"); + if (currentMillis > req.getEndTimeMillis()) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + LOG.info("measurement complete. results at: {}", result.getAbsolutePath()); + } catch (IOException e) { + LOG.error("cannot create file for metrics", e); + } finally { + latch.countDown(); + } + } + }, "monitor-thread"); + monitorThread.start(); + + AllviewRotatorService service = new AllviewRotatorService(client, rotatorConfig, predict, new ThreadPoolFactoryImpl(10000), clock); + service.start(); + Future observationFuture = service.schedule(req, req.getStartTimeMillis(), null); + latch.await(); + observationFuture.cancel(true); + service.stop(); + + enrichWithExpectedPosition(predict, req, result); + } + + private static void measureRotatorService(PredictOreKit predict, ObservationRequest req, OffsetClock clock, RotatorConfiguration rotatorConfig) throws InterruptedException, IOException, FileNotFoundException { + CountDownLatch latch = new CountDownLatch(1); + final File result = new File("coarse.csv"); + Thread monitorThread = new Thread(new Runnable() { + @Override + public void run() { + RotctrldClient client = new RotctrldClient(rotatorConfig.getHostname(), rotatorConfig.getPort(), rotatorConfig.getTimeout()); + try (BufferedWriter w = new BufferedWriter(new FileWriter(result))) { + client.start(); + while (!Thread.currentThread().isInterrupted()) { + Position current; + try { + current = client.getPosition(); + } catch (RotCtrlException e1) { + LOG.error("can't get position", e1); + break; + } + long currentMillis = clock.millis(); + w.append(String.valueOf(current.getAzimuth())).append(",").append(String.valueOf(current.getElevation())).append(",").append(String.valueOf(currentMillis)).append("\n"); + if (currentMillis > req.getEndTimeMillis()) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + LOG.info("measurement complete. results at: {}", result.getAbsolutePath()); + } catch (IOException e) { + LOG.error("cannot create file for metrics", e); + } finally { + latch.countDown(); + } + } + }, "monitor-thread"); + monitorThread.start(); + + RotatorService service = new RotatorService(rotatorConfig, predict, new ThreadPoolFactoryImpl(10000), clock); + service.start(); + Future observationFuture = service.schedule(req, req.getStartTimeMillis(), null); + latch.await(); + observationFuture.cancel(true); + service.stop(); + + enrichWithExpectedPosition(predict, req, result); + } + + private static void enrichWithExpectedPosition(PredictOreKit predict, ObservationRequest req, final File result) throws IOException, FileNotFoundException { + if (!result.exists()) { + return; + } + try (BufferedReader r = new BufferedReader(new FileReader(result)); BufferedWriter w = new BufferedWriter(new FileWriter(result.getName() + "_enriched.csv"))) { + String curLine = null; + Pattern p = Pattern.compile(","); + TopocentricFrame groundStation = predict.getPosition(req.getGroundStation()); + TLEPropagator tlePropagator = TLEPropagator.selectExtrapolator(new org.orekit.propagation.analytical.tle.TLE(req.getTle().getRaw()[1], req.getTle().getRaw()[2])); + while ((curLine = r.readLine()) != null) { + String[] parts = p.split(curLine); + long time = Long.valueOf(parts[2]); + Position satPosition = predict.getSatellitePosition(time, groundStation, tlePropagator); + w.append(parts[0]).append(',').append(parts[1]).append(',').append(parts[2]).append(',').append(String.valueOf(satPosition.getAzimuth())).append(',').append(String.valueOf(satPosition.getElevation())).append('\n'); + } + } + } + + private static RotatorConfiguration createValidConfig() { + RotatorConfiguration config = new RotatorConfiguration(); + config.setId(UUID.randomUUID().toString()); + config.setHostname("127.0.0.1"); + config.setPort(ROTCTRLD_PORT); + config.setCycleMillis(1000); + config.setTimeout(10000); + config.setTolerance(5); + return config; + } + + private static long getTime(String str) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.UK); + sdf.setTimeZone(TimeZone.getTimeZone("GMT")); + try { + return sdf.parse(str).getTime(); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/test/java/ru/r2cloud/OffsetClock.java b/src/test/java/ru/r2cloud/OffsetClock.java new file mode 100644 index 00000000..d437c972 --- /dev/null +++ b/src/test/java/ru/r2cloud/OffsetClock.java @@ -0,0 +1,17 @@ +package ru.r2cloud; + +import ru.r2cloud.util.Clock; + +public class OffsetClock implements Clock { + + private final long offset; + + public OffsetClock(long initialTime) { + this.offset = System.currentTimeMillis() - initialTime; + } + + @Override + public long millis() { + return System.currentTimeMillis() - offset; + } +} diff --git a/src/test/java/ru/r2cloud/rotator/allview/AllviewClient.java b/src/test/java/ru/r2cloud/rotator/allview/AllviewClient.java index 3ed0aa0b..f01cfd04 100644 --- a/src/test/java/ru/r2cloud/rotator/allview/AllviewClient.java +++ b/src/test/java/ru/r2cloud/rotator/allview/AllviewClient.java @@ -48,6 +48,13 @@ public void slew(Position nextPosition, Position previousPosition) throws IOExce } catch (IOException e) { currentAzimuth = previousPosition.getAzimuth(); } + // slightly overrun. do not go back. just skip the cycle + if (nextPosition.getAzimuth() > previousPosition.getAzimuth() && nextPosition.getAzimuth() < currentAzimuth) { + currentAzimuth = nextPosition.getAzimuth(); + } + if (nextPosition.getAzimuth() < previousPosition.getAzimuth() && nextPosition.getAzimuth() > currentAzimuth) { + currentAzimuth = nextPosition.getAzimuth(); + } } else { currentAzimuth = previousPosition.getAzimuth(); } diff --git a/src/test/java/ru/r2cloud/rotator/allview/AllviewMotor.java b/src/test/java/ru/r2cloud/rotator/allview/AllviewMotor.java index 1243c769..7b5cf22e 100644 --- a/src/test/java/ru/r2cloud/rotator/allview/AllviewMotor.java +++ b/src/test/java/ru/r2cloud/rotator/allview/AllviewMotor.java @@ -41,7 +41,7 @@ private static int convertInteger(String inputStr) { return ((input & 0x0000FF) << 16) | (input & 0x00FF00) | ((input & 0xFF0000) >> 16); } - public static double convertAngle(String inputStr) { + private double convertAngle(String inputStr) { int converted = convertInteger(inputStr); boolean positive = true; if (converted > 0x800000) { @@ -50,7 +50,7 @@ public static double convertAngle(String inputStr) { converted = 0x800000 - converted; positive = false; } - double ticks_per_angle = (double) 865050 / 360.0; + double ticks_per_angle = (double) cpr / 360.0; double result = converted / ticks_per_angle; if (!positive) { return 360.0 - result; @@ -91,7 +91,7 @@ public void slew(double degreePerSec) throws IOException { boolean changeDirectionToCcw = (degreePerSec < 0 && currentDegree > 0); boolean changeDirectionNeeded = (changeDirectionToCw || changeDirectionToCcw); double degreeAbs = Math.abs(degreePerSec); - int number = (int) ((getTimerInterruptFreq() * 360.0f) / degreeAbs / 865050.0f); + int number = (int) ((getTimerInterruptFreq() * 360.0f) / degreeAbs / cpr); if (currentSpeed != 0 && currentSpeed == number && !changeDirectionNeeded) { return; } @@ -138,7 +138,7 @@ public boolean isStopRequired(double degree) throws IOException { return true; } // only when required speed is actually changed - int number = (int) ((getTimerInterruptFreq() * 360.0f) / Math.abs(degree) / 865050.0f); + int number = (int) ((getTimerInterruptFreq() * 360.0f) / Math.abs(degree) / cpr); if (currentSpeed != number) { return true; } diff --git a/src/test/java/ru/r2cloud/rotator/allview/AllviewRotatorService.java b/src/test/java/ru/r2cloud/rotator/allview/AllviewRotatorService.java new file mode 100644 index 00000000..dd4ef8fa --- /dev/null +++ b/src/test/java/ru/r2cloud/rotator/allview/AllviewRotatorService.java @@ -0,0 +1,174 @@ +package ru.r2cloud.rotator.allview; + +import java.io.IOException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.orekit.frames.TopocentricFrame; +import org.orekit.propagation.analytical.tle.TLEPropagator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ru.r2cloud.Lifecycle; +import ru.r2cloud.model.DeviceConnectionStatus; +import ru.r2cloud.model.ObservationRequest; +import ru.r2cloud.model.RotatorConfiguration; +import ru.r2cloud.model.RotatorStatus; +import ru.r2cloud.predict.PredictOreKit; +import ru.r2cloud.rotctrld.Position; +import ru.r2cloud.util.Clock; +import ru.r2cloud.util.NamingThreadFactory; +import ru.r2cloud.util.ThreadPoolFactory; +import ru.r2cloud.util.Util; + +public class AllviewRotatorService implements Lifecycle { + + private static final Logger LOG = LoggerFactory.getLogger(AllviewRotatorService.class); + + private ScheduledExecutorService executor = null; + + private final RotatorStatus status = new RotatorStatus(); + private final RotatorConfiguration config; + private final PredictOreKit predict; + private final ThreadPoolFactory threadpoolFactory; + private final Clock clock; + private final AllviewClient client; + + public AllviewRotatorService(AllviewClient client, RotatorConfiguration config, PredictOreKit predict, ThreadPoolFactory threadpoolFactory, Clock clock) { + this.predict = predict; + this.threadpoolFactory = threadpoolFactory; + this.clock = clock; + this.config = config; + this.client = client; + } + + @Override + public synchronized void start() { + LOG.info("[{}] starting rotator on: {}:{}", config.getId(), config.getHostname(), config.getPort()); + status.setHostport(config.getHostname() + ":" + config.getPort()); + executor = threadpoolFactory.newScheduledThreadPool(1, new NamingThreadFactory("rotator")); + } + + @Override + public synchronized void stop() { + if (executor != null) { + Util.shutdown(executor, threadpoolFactory.getThreadPoolShutdownMillis()); + } + client.stop(); + LOG.info("[{}] stopped", config.getId()); + } + + public Future schedule(ObservationRequest req, long current, Future startFuture) { + if (executor == null) { + return null; + } + TLEPropagator tlePropagator = TLEPropagator.selectExtrapolator(new org.orekit.propagation.analytical.tle.TLE(req.getTle().getRaw()[1], req.getTle().getRaw()[2])); + TopocentricFrame groundStation = predict.getPosition(req.getGroundStation()); + Future result = executor.scheduleAtFixedRate(new Runnable() { + + private Position previousPosition; + private boolean log = true; + + @Override + public void run() { + long current = clock.millis(); + if (current > req.getEndTimeMillis()) { + LOG.info("[{}] observation time passed. cancelling rotation", req.getId()); + client.stop(); + throw new RuntimeException("observation time passed"); + } + if (startFuture != null && startFuture.isDone()) { + LOG.info("[{}] observation stopped. cancelling rotation", req.getId()); + client.stop(); + throw new RuntimeException("observation stopped"); + } + + if (!ensureClientConnected(req.getId(), log)) { + log = false; + // try reconnect on the next iteration + return; + } + + Position nextPosition; + if (previousPosition == null) { + + try { + client.stopMotors(); + + previousPosition = predict.getSatellitePosition(current, groundStation, tlePropagator); + + client.waitMotorStop(); + + client.setPosition(previousPosition); + + client.waitMotorStop(); + + } catch (IOException e) { + LOG.error("move to initial position", e); + client.stop(); + // force reconnect on the next iteration + status.setStatus(DeviceConnectionStatus.FAILED); + status.setFailureMessage(e.getMessage()); + previousPosition = null; + return; + } + } + + nextPosition = predict.getSatellitePosition(current + config.getCycleMillis(), groundStation, tlePropagator); + if (nextPosition.getElevation() < 0.0) { + LOG.info("[{}] negative elevation requested. most likely stale or invalid tle. cancelling rotation", req.getId()); + throw new RuntimeException("negative elevation"); + } + + try { + client.slew(nextPosition, previousPosition); + previousPosition = nextPosition; + } catch (IOException e) { + LOG.error("can't slew", e); + client.stop(); + // force reconnect on the next iteration + status.setStatus(DeviceConnectionStatus.FAILED); + status.setFailureMessage(e.getMessage()); + previousPosition = null; + return; + } + + } + + }, req.getStartTimeMillis() - current, config.getCycleMillis(), TimeUnit.MILLISECONDS); + return result; + } + + private synchronized boolean ensureClientConnected(String id, boolean shouldLogError) { + if (status.getStatus().equals(DeviceConnectionStatus.CONNECTED)) { + return true; + } + try { + client.start(); + status.setStatus(DeviceConnectionStatus.CONNECTED); + long start = clock.millis(); + String model = client.getMotorBoardVersion(); + long latency = clock.millis() - start; + status.setModel(model); + LOG.info("[{}] initialized for model: {}. communication latency: {} ms", config.getId(), status.getModel(), latency); + return true; + } catch (Exception e) { + status.setStatus(DeviceConnectionStatus.FAILED); + status.setFailureMessage(e.getMessage()); + if (shouldLogError) { + if (id != null) { + Util.logIOException(LOG, "[" + id + "] unable to connect to rotctrld", e); + } else { + Util.logIOException(LOG, "unable to connect to rotctrld", e); + } + } + return false; + } + } + + public RotatorStatus getStatus() { + return status; + } + +} \ No newline at end of file From 9e4428dd2443fa76a10ede8270cc317c79a0d9a5 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sun, 13 Oct 2024 08:44:48 +0100 Subject: [PATCH 11/25] new satellites and modulations --- src/main/resources/satellites.json | 519 ++++++++++++++++++++++++++--- 1 file changed, 479 insertions(+), 40 deletions(-) diff --git a/src/main/resources/satellites.json b/src/main/resources/satellites.json index 1487ba32..7474117d 100644 --- a/src/main/resources/satellites.json +++ b/src/main/resources/satellites.json @@ -1,4 +1,108 @@ [ + { + "id": "R2CLOUD430", + "name": "PCSAT (NO-44)", + "noradId": "26931", + "enabled": false, + "transmitters": [ + { + "modulation": "AFSK", + "framing": "AX25", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 145826800, + "bandwidth": 20000, + "baudRates": [ + 1200 + ], + "deviation": 600, + "afCarrier": 1700, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD429", + "name": "ISS (ZARYA)", + "noradId": "25544", + "enabled": false, + "transmitters": [ + { + "modulation": "AFSK", + "framing": "AX25", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 145825000, + "bandwidth": 20000, + "baudRates": [ + 1200 + ], + "deviation": 600, + "afCarrier": 1700, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD428", + "name": "EOS-7", + "noradId": "55562", + "enabled": false, + "transmitters": [ + { + "modulation": "AFSK", + "framing": "AX25", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 145825000, + "bandwidth": 20000, + "baudRates": [ + 1200 + ], + "deviation": 600, + "afCarrier": 1700, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD427", + "name": "SPACEMOBILE-005", + "noradId": "61046", + "enabled": false, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "CC11XX", + "beaconClass": "ru.r2cloud.jradio.RawBeacon", + "frequency": 430500000, + "bandwidth": 20000, + "baudRates": [ + 2400 + ], + "deviation": 600, + "status": "ENABLED", + "syncword": "10010011000010110101000111011110" + } + ] + }, + { + "id": "R2CLOUD426", + "name": "ROBUSTA-3A", + "noradId": "60243", + "enabled": false, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 436750000, + "bandwidth": 20000, + "baudRates": [ + 9600 + ], + "deviation": 2400, + "status": "ENABLED" + } + ] + }, { "id": "R2CLOUD425", "name": "SR-0 DemoSAT", @@ -22,6 +126,344 @@ } ] }, + { + "id": "R2CLOUD424", + "name": "TORO", + "noradId": "60530", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 435410000, + "bandwidth": 20000, + "baudRates": [ + 4800 + ], + "deviation": 5000, + "assistedHeader": "AAABAAAAAAEAAAEBAAEAAQAAAAEAAQEAAAABAQABAAEAAAEAAAAAAQAAAAAAAAAAAAAAAAABAQEAAAAAAQEAAQAAAAABAQABAAAAAAABAQAAAQABAAEAAQAAAAABAAABAAABAQAAAAEBAAAAAAEBAQEBAAAAAAAAAAAAAAEBAQE=", + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD423", + "name": "BRO-14", + "noradId": "60551", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX100", + "beaconClass": "ru.r2cloud.jradio.csp.CspBeacon", + "frequency": 401735000, + "bandwidth": 20000, + "baudRates": [ + 9600 + ], + "deviation": 5000, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD422", + "name": "ION SCV-012", + "noradId": "60573", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX100", + "beaconClass": "ru.r2cloud.jradio.csp.CspBeacon", + "frequency": 401415000, + "bandwidth": 20000, + "baudRates": [ + 1200 + ], + "deviation": 5000, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD420", + "name": "NIGHTJAR", + "noradId": "60535", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 435350000, + "bandwidth": 20000, + "baudRates": [ + 9600 + ], + "deviation": 5000, + "assistedHeader": "AAEBAAAAAAEAAQAAAAEAAQAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAABAQEAAAAAAQEAAQAAAAABAQABAAAAAAABAQAAAQABAAEAAQAAAAABAAABAAABAQAAAAEBAAAAAAEBAQEBAAAAAAAAAAAAAAEBAQE=", + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD419", + "name": "SATORO-T2", + "noradId": "60477", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "BPSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 400425000, + "bandwidth": 10000, + "baudRates": [ + 2400 + ], + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD418", + "name": "ERNST", + "noradId": "60528", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX100", + "beaconClass": "ru.r2cloud.jradio.csp.CspBeacon", + "frequency": 401100000, + "bandwidth": 20000, + "baudRates": [ + 4800 + ], + "deviation": 5000, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD417", + "name": "ORESAT0.5", + "noradId": "60525", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 436500000, + "bandwidth": 20000, + "baudRates": [ + 9600 + ], + "deviation": 5000, + "assistedHeader": "AAEBAAABAAEAAAAAAAEAAQABAAAAAAABAAEBAAAAAAEAAQABAAAAAQAAAAAAAAEAAAAAAAABAQAAAQEAAQAAAQAAAQABAAABAAEBAQABAQAAAQEAAAEAAQABAAAAAAABAAAAAQABAAEBAQEAAQEBAQEBAAAAAAAAAAAAAAEBAQE=", + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD416", + "name": "CUAVA-2", + "noradId": "60527", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "BPSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 400650000, + "bandwidth": 10000, + "baudRates": [ + 1200 + ], + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD415", + "name": "Iperdrone.0", + "noradId": "60520", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 401110000, + "bandwidth": 20000, + "baudRates": [ + 9600 + ], + "deviation": 5000, + "assistedHeader": "AAEAAAEAAAEAAAAAAAEAAQABAAEAAAABAAABAQAAAAEAAAABAQAAAQAAAAEAAQABAAAAAAAAAAAAAQAAAQAAAQAAAAAAAQABAAEAAQAAAAEAAAEBAAAAAQAAAAEBAAABAAAAAQABAAEBAAAAAAAAAAEBAAAAAAAAAAABAQAAAQE=", + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD414", + "name": "ConnectaIoT-4", + "noradId": "60524", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 401540000, + "bandwidth": 20000, + "baudRates": [ + 4800 + ], + "deviation": 5000, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD413", + "name": "ConnectaIoT-3", + "noradId": "60475", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 401530000, + "bandwidth": 20000, + "baudRates": [ + 4800 + ], + "deviation": 5000, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD412", + "name": "ConnectaIoT-2", + "noradId": "60522", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 401520000, + "bandwidth": 20000, + "baudRates": [ + 4800 + ], + "deviation": 5000, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD411", + "name": "ConnectaIoT-1", + "noradId": "60472", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 401510000, + "bandwidth": 20000, + "baudRates": [ + 4800 + ], + "deviation": 5000, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD410", + "name": "Waratah Seed-1", + "noradId": "60469", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "BPSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 400650000, + "bandwidth": 10000, + "baudRates": [ + 1200 + ], + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD409", + "name": "QUBE", + "noradId": "60476", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 435600000, + "bandwidth": 20000, + "baudRates": [ + 9600 + ], + "deviation": 5000, + "assistedHeader": "AAAAAQAAAAEAAQEBAQAAAQAAAAAAAQEAAAABAAEBAAEAAAEBAAAAAQAAAAEAAQABAAAAAAABAQAAAAABAAAAAQAAAAAAAQABAAABAAABAQAAAQAAAAEAAQABAAEAAQABAAAAAAAAAAABAAAAAAEBAAEBAAAAAAAAAAAAAAEBAQE=", + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD408", + "name": "PTD-R", + "noradId": "60523", + "enabled": false, + "start": 1723834560000, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 401205000, + "bandwidth": 20000, + "baudRates": [ + 9600 + ], + "deviation": 5000, + "status": "ENABLED" + } + ] + }, { "id": "R2CLOUD407", "name": "SITRO-AIS-52", @@ -365,6 +807,18 @@ "noradId": "60237", "enabled": true, "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 145935000, + "bandwidth": 20000, + "baudRates": [ + 9600 + ], + "deviation": 5000, + "status": "ENABLED" + }, { "modulation": "GFSK", "framing": "AX25G3RUH", @@ -594,6 +1048,19 @@ "enabled": false, "start": 1709586240000, "transmitters": [ + { + "modulation": "AFSK", + "framing": "AX25", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 145825000, + "bandwidth": 20000, + "baudRates": [ + 1200 + ], + "deviation": 600, + "afCarrier": 1700, + "status": "ENABLED" + }, { "modulation": "GFSK", "framing": "AX25G3RUH", @@ -3083,6 +3550,18 @@ "enabled": true, "start": 1641168000000, "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX25G3RUH", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 145850000, + "bandwidth": 20000, + "baudRates": [ + 9600 + ], + "deviation": 5000, + "status": "ENABLED" + }, { "modulation": "GFSK", "framing": "AX25G3RUH", @@ -3511,46 +3990,6 @@ } ] }, - { - "id": "R2CLOUD169", - "name": "UTMN (RS23S)", - "noradId": "53376", - "enabled": false, - "transmitters": [ - { - "modulation": "GFSK", - "framing": "USP", - "beaconClass": "ru.r2cloud.jradio.usp.UspBeacon", - "frequency": 435670000, - "bandwidth": 20000, - "baudRates": [ - 2400, - 4800, - 9600 - ], - "status": "ENABLED" - } - ] - }, - { - "id": "R2CLOUD165", - "name": "RS26S (KAI-1)", - "noradId": "53378", - "enabled": false, - "transmitters": [ - { - "modulation": "AFSK", - "framing": "AX25", - "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", - "frequency": 435665000, - "bandwidth": 20000, - "baudRates": [ - 1200 - ], - "status": "ENABLED" - } - ] - }, { "id": "R2CLOUD158", "name": "MTCUBE 2 (ROBUSTA 1F)", From f49028c42cd120c1b505eb2a1da34c66eda1b1f4 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sun, 13 Oct 2024 11:31:14 +0100 Subject: [PATCH 12/25] use gzip to download sigmf data + do not re-encode if the source file is already gzipped --- .../api/observation/ObservationSigMfData.java | 8 ++-- src/test/java/ru/r2cloud/TestUtil.java | 48 ++++++++++++++++++- .../ru/r2cloud/it/ObservationLoadTest.java | 8 +++- .../java/ru/r2cloud/it/util/RestClient.java | 31 +++++++++++- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/src/main/java/ru/r2cloud/web/api/observation/ObservationSigMfData.java b/src/main/java/ru/r2cloud/web/api/observation/ObservationSigMfData.java index af673aae..bb81cdf7 100644 --- a/src/main/java/ru/r2cloud/web/api/observation/ObservationSigMfData.java +++ b/src/main/java/ru/r2cloud/web/api/observation/ObservationSigMfData.java @@ -8,7 +8,6 @@ import java.util.Date; import java.util.Locale; import java.util.TimeZone; -import java.util.zip.GZIPInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +19,6 @@ import ru.r2cloud.satellite.IObservationDao; import ru.r2cloud.util.Configuration; import ru.r2cloud.util.SignedURL; -import ru.r2cloud.util.Util; import ru.r2cloud.web.AbstractHttpController; import ru.r2cloud.web.BadRequest; import ru.r2cloud.web.ModelAndView; @@ -78,12 +76,12 @@ public ModelAndView doGet(IHTTPSession session) { if (ifModifiedSince != null && ifModifiedSince >= entity.getRawPath().lastModified() / 1000) { response = NanoHTTPD.newFixedLengthResponse(fi.iki.elonen.NanoHTTPD.Response.Status.NOT_MODIFIED, "application/octet-stream", null); } else { - Long totalBytes = Util.readTotalBytes(entity.getRawPath().toPath()); InputStream is = new BufferedInputStream(new FileInputStream(entity.getRawPath())); + response = NanoHTTPD.newFixedLengthResponse(fi.iki.elonen.NanoHTTPD.Response.Status.OK, "application/octet-stream", is, entity.getRawPath().length()); if (entity.getRawPath().toString().endsWith(".gz")) { - is = new GZIPInputStream(is); + // do not re-encode in NanoHTTPD + response.addHeader("Content-Encoding", "gzip"); } - response = NanoHTTPD.newFixedLengthResponse(fi.iki.elonen.NanoHTTPD.Response.Status.OK, "application/octet-stream", is, totalBytes); // convert to seconds response.addHeader("Cache-Control", "private, max-age=" + ((int) (config.getLong("server.static.signed.validMillis") / 1000))); } diff --git a/src/test/java/ru/r2cloud/TestUtil.java b/src/test/java/ru/r2cloud/TestUtil.java index a5d88374..4c902eed 100644 --- a/src/test/java/ru/r2cloud/TestUtil.java +++ b/src/test/java/ru/r2cloud/TestUtil.java @@ -1,5 +1,6 @@ package ru.r2cloud; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -21,13 +22,14 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.UUID; +import java.util.zip.GZIPInputStream; import javax.imageio.ImageIO; -import com.eclipsesource.json.JsonArray; import org.junit.rules.TemporaryFolder; import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; @@ -209,4 +211,48 @@ private TestUtil() { // do nothing } + public static void assertFile(File actual, File expected) { + InputStream actualIs = null; + InputStream expectedIs = null; + try { + actualIs = new BufferedInputStream(new FileInputStream(actual)); + if (actual.getName().endsWith("gz")) { + actualIs = new GZIPInputStream(actualIs); + } + expectedIs = new BufferedInputStream(new FileInputStream(expected)); + if (expected.getName().endsWith("gz")) { + expectedIs = new GZIPInputStream(expectedIs); + } + byte[] actualBuf = new byte[1024]; + byte[] expectedBuf = new byte[1024]; + while (!Thread.currentThread().isInterrupted()) { + int actualBytes = actualIs.read(actualBuf); + int expectedBytes; + if (actualBytes == -1) { + expectedBytes = expectedIs.read(expectedBuf); + } else { + expectedBytes = expectedIs.readNBytes(expectedBuf, 0, actualBytes); + } + assertEquals(expectedBytes, actualBytes); + if (actualBytes == -1) { + break; + } + assertArrayEquals(expectedBuf, actualBuf); + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + Util.closeQuietly(actualIs); + Util.closeQuietly(expectedIs); + } + +// try (InputStream actualIs = new FileInputStream(actual); InputStream expectedIs = new FileInputStream(expected)) { +// if (actual.getName().endsWith("gz")) { +// actualIs = new GZIPInputStream(actualIs); +// } +// } catch (IOException e) { +// throw new RuntimeException(e); +// } + } + } diff --git a/src/test/java/ru/r2cloud/it/ObservationLoadTest.java b/src/test/java/ru/r2cloud/it/ObservationLoadTest.java index 40a674cc..2e040cea 100644 --- a/src/test/java/ru/r2cloud/it/ObservationLoadTest.java +++ b/src/test/java/ru/r2cloud/it/ObservationLoadTest.java @@ -20,11 +20,15 @@ public void testLoadAausat4() throws Exception { File basepath = new File(config.getProperty("satellites.basepath.location") + File.separator + "41460" + File.separator + "data" + File.separator + "1559942730784"); TestUtil.copy("aausat4Observation/1559942730784.json", new File(basepath, "meta.json")); TestUtil.copy("aausat4Observation/data.bin", new File(basepath, "data.bin")); - TestUtil.copy("data/aausat.raw.gz", new File(basepath, "output.raw.gz")); + File expectedIqFile = new File(basepath, "output.raw.gz"); + TestUtil.copy("data/aausat.raw.gz", expectedIqFile); JsonObject observation = client.getObservation("41460", "1559942730784"); TestUtil.assertJson("aausat4Observation/expected.json", observation); - JsonObject sigmf = client.getSigMf(observation.getString("sigmfMetaURL", null)); + JsonObject sigmf = client.getSigMfMeta(observation.getString("sigmfMetaURL", null)); TestUtil.assertJson("aausat4Observation/1559942730784.sigmf-meta.json", sigmf); + File actual = new File(tempFolder.getRoot(), "actual.raw"); + client.downloadSigMfData(observation.getString("sigmfDataURL", null), actual); + TestUtil.assertFile(actual, expectedIqFile); } @Test diff --git a/src/test/java/ru/r2cloud/it/util/RestClient.java b/src/test/java/ru/r2cloud/it/util/RestClient.java index 9223c2ca..bddfd940 100644 --- a/src/test/java/ru/r2cloud/it/util/RestClient.java +++ b/src/test/java/ru/r2cloud/it/util/RestClient.java @@ -1,6 +1,11 @@ package ru.r2cloud.it.util; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.Socket; import java.net.URI; import java.net.URLEncoder; @@ -24,6 +29,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; +import java.util.zip.GZIPInputStream; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -41,6 +48,7 @@ import ru.r2cloud.model.GeneralConfiguration; import ru.r2cloud.model.IntegrationConfiguration; import ru.r2cloud.model.Page; +import ru.r2cloud.util.Util; public class RestClient { @@ -651,7 +659,7 @@ public JsonObject awaitObservation(String satelliteId, String observationId, boo return null; } - public JsonObject getSigMf(String url) { + public JsonObject getSigMfMeta(String url) { if (url == null) { return null; } @@ -671,6 +679,27 @@ public JsonObject getSigMf(String url) { } } + public void downloadSigMfData(String url, File actual) { + HttpRequest request = createAuthRequest(url).GET().build(); + try (OutputStream os = new BufferedOutputStream(new FileOutputStream(actual))) { + HttpResponse response = httpclient.send(request, BodyHandlers.ofInputStream()); + if (response.statusCode() != 200) { + throw new RuntimeException("invalid status code: " + response.statusCode()); + } + Optional val = response.headers().firstValue("content-encoding"); + InputStream is = response.body(); + if (val.isPresent() && val.get().equals("gzip")) { + is = new GZIPInputStream(is); + } + Util.copy(is, os); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("interrupted"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public JsonObject getObservationPresentation(String satelliteId, String observationId) { HttpResponse response = getObservationResponse("/api/v1/observation/load", satelliteId, observationId); if (response.statusCode() == 404) { From 3f877369b0840a768a11a7b2e02521129607d265 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Tue, 29 Oct 2024 20:01:57 +0000 Subject: [PATCH 13/25] switch to the default standard jre headless old OSes continue to use r2cloud-jdk --- .github/workflows/build.yml | 12 +++++++++--- pom.xml | 17 +++-------------- src/main/deb/lib/systemd/system/r2cloud.service | 2 +- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bc0e82ae..206a1db8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,11 +72,17 @@ jobs: run: | cd ./target/ [[ -f ~/.m2/repository/apt-cli-1.7.jar ]] || wget --no-clobber --quiet -O ~/.m2/repository/apt-cli-1.7.jar https://github.com/dernasherbrezon/apt-cli/releases/download/apt-cli-1.7/apt-cli.jar - java -jar ~/.m2/repository/apt-cli-1.7.jar --url s3://r2cloud --component main --codename r2cloud --gpg-keyname F2DCBFDCA5A70917 save --patterns ./*.deb - java -jar ~/.m2/repository/apt-cli-1.7.jar --url s3://r2cloud --component main --codename stretch --gpg-keyname F2DCBFDCA5A70917 save --patterns ./*.deb - java -jar ~/.m2/repository/apt-cli-1.7.jar --url s3://r2cloud --component main --codename buster --gpg-keyname F2DCBFDCA5A70917 save --patterns ./*.deb java -jar ~/.m2/repository/apt-cli-1.7.jar --url s3://r2cloud --component main --codename bullseye --gpg-keyname F2DCBFDCA5A70917 save --patterns ./*.deb java -jar ~/.m2/repository/apt-cli-1.7.jar --url s3://r2cloud --component main --codename bookworm --gpg-keyname F2DCBFDCA5A70917 save --patterns ./*.deb java -jar ~/.m2/repository/apt-cli-1.7.jar --url s3://r2cloud --component main --codename bionic --gpg-keyname F2DCBFDCA5A70917 save --patterns ./*.deb java -jar ~/.m2/repository/apt-cli-1.7.jar --url s3://r2cloud --component main --codename focal --gpg-keyname F2DCBFDCA5A70917 save --patterns ./*.deb java -jar ~/.m2/repository/apt-cli-1.7.jar --url s3://r2cloud --component main --codename jammy --gpg-keyname F2DCBFDCA5A70917 save --patterns ./*.deb + echo "building for old OS" + cd .. + mvn clean + mvn -Dmaven.test.skip=true -Djdk.version="r2cloud-jdk (>=17.0.13-1)" package + cd ./target/ + java -jar ~/.m2/repository/apt-cli-1.7.jar --url s3://r2cloud --component main --codename stretch --gpg-keyname F2DCBFDCA5A70917 save --patterns ./*.deb + java -jar ~/.m2/repository/apt-cli-1.7.jar --url s3://r2cloud --component main --codename buster --gpg-keyname F2DCBFDCA5A70917 save --patterns ./*.deb + + diff --git a/pom.xml b/pom.xml index c7ff27fc..7b55183b 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ UTF-8 + openjdk-17-jre-headless @@ -277,7 +278,7 @@ com.aerse.maven deb-maven-plugin - 1.16 + 1.19 package @@ -292,19 +293,7 @@ pi all
embedded
- - >=20240303125106 - >=17.0.5-1 - >=1.10.3 - >=0.6.4 - - >=8.26 - - >=2.3.2 - >=14.4.1 - - - + r2cloud-ui (>=20240303125106), ${jdk.version}, nginx-light(>=1.10.3), rtl-sdr(>=0.6.4), coreutils(>=8.26), libxft2(>=2.3.2), sox(>=14.4.1), wget false diff --git a/src/main/deb/lib/systemd/system/r2cloud.service b/src/main/deb/lib/systemd/system/r2cloud.service index 20d6279d..1dc2878f 100644 --- a/src/main/deb/lib/systemd/system/r2cloud.service +++ b/src/main/deb/lib/systemd/system/r2cloud.service @@ -4,7 +4,7 @@ After=time-sync.target [Service] WorkingDirectory=/home/pi/r2cloud/ -ExecStart=/home/pi/r2cloud-jdk/bin/java -cp /home/pi/r2cloud/etc:/home/pi/r2cloud/lib/*:/usr/share/java/r2cloud/* -Djava.library.path=/usr/lib/jni -Djradio.metrics.enabled=true -Duser.timezone=UTC -Djava.util.logging.config.file=/home/pi/r2cloud/etc/logging-prod.properties ru.r2cloud.R2Cloud etc/config-prod.properties +ExecStart=/usr/bin/java -cp /home/pi/r2cloud/etc:/home/pi/r2cloud/lib/*:/usr/share/java/r2cloud/* -Djava.library.path=/usr/lib/jni -Djradio.metrics.enabled=true -Duser.timezone=UTC -Djava.util.logging.config.file=/home/pi/r2cloud/etc/logging-prod.properties ru.r2cloud.R2Cloud etc/config-prod.properties SyslogIdentifier=r2cloud SuccessExitStatus=143 Restart=always From 8ad9a85671020f2260ff5a6c8ab189d145601fd1 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sat, 2 Nov 2024 00:58:24 +0000 Subject: [PATCH 14/25] support for new satellites --- pom.xml | 2 +- .../r2cloud/satellite/decoder/Decoders.java | 7 ++- .../satellite/decoder/GeoscanDecoder.java | 11 +++-- .../decoder/StratosatTk1Decoder.java | 4 +- src/main/resources/satellites.json | 43 ++++++++++++++++++- 5 files changed, 59 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 7b55183b..1324f8f5 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ ru.r2cloud jradio - 1.101 + 1.103 org.nanohttpd diff --git a/src/main/java/ru/r2cloud/satellite/decoder/Decoders.java b/src/main/java/ru/r2cloud/satellite/decoder/Decoders.java index 7e9cfe08..ff9f2af2 100644 --- a/src/main/java/ru/r2cloud/satellite/decoder/Decoders.java +++ b/src/main/java/ru/r2cloud/satellite/decoder/Decoders.java @@ -11,6 +11,7 @@ import ru.r2cloud.jradio.fox.Fox1BBeacon; import ru.r2cloud.jradio.fox.Fox1CBeacon; import ru.r2cloud.jradio.fox.Fox1DBeacon; +import ru.r2cloud.jradio.geoscan.GeoscanBeacon; import ru.r2cloud.jradio.mrc100.Mrc100Beacon; import ru.r2cloud.jradio.usp.UspBeacon; import ru.r2cloud.model.DecoderKey; @@ -64,7 +65,6 @@ public Decoders(PredictOreKit predict, Configuration props, ProcessFactory proce index("47963", "47963-0", new Diy1Decoder(predict, props)); index("47964", "47964-0", new Smog1Decoder(predict, props)); index("51439", "51439-0", new GaspacsDecoder(predict, props)); - index("53385", "53385-0", new GeoscanDecoder(predict, props)); index("53108", "53108-0", new CcsdsDecoder(predict, props, TransferFrame.class)); index("56993", "56993-0", new Mrc100Decoder(predict, props, Mrc100Beacon.class)); index("56211", "56211-1", new InspireSat7SpinoDecoder(predict, props)); @@ -72,6 +72,11 @@ public Decoders(PredictOreKit predict, Configuration props, ProcessFactory proce index("55104", "55104-0", new Sharjahsat1Decoder(predict, props)); index("56212", "56212-0", new RoseyDecoder(predict, props)); index("53384", "53384-0", new SputnixDecoder(predict, props)); + index("R2CLOUD433", "R2CLOUD433-0", new GeoscanDecoder(predict, props, GeoscanBeacon.class, 74)); + index("R2CLOUD434", "R2CLOUD434-0", new GeoscanDecoder(predict, props, GeoscanBeacon.class, 74)); + index("R2CLOUD435", "R2CLOUD435-0", new GeoscanDecoder(predict, props, GeoscanBeacon.class, 74)); + index("R2CLOUD438", "R2CLOUD438-0", new GeoscanDecoder(predict, props, GeoscanBeacon.class, 74)); + index("R2CLOUD439", "R2CLOUD439-0", new GeoscanDecoder(predict, props, GeoscanBeacon.class, 74)); } public Decoder findByTransmitter(Transmitter transmitter) { diff --git a/src/main/java/ru/r2cloud/satellite/decoder/GeoscanDecoder.java b/src/main/java/ru/r2cloud/satellite/decoder/GeoscanDecoder.java index 1018ab48..16583259 100644 --- a/src/main/java/ru/r2cloud/satellite/decoder/GeoscanDecoder.java +++ b/src/main/java/ru/r2cloud/satellite/decoder/GeoscanDecoder.java @@ -15,8 +15,13 @@ public class GeoscanDecoder extends TelemetryDecoder { - public GeoscanDecoder(PredictOreKit predict, Configuration config) { + private final Class beacon; + private final int beaconSizeBytes; + + public GeoscanDecoder(PredictOreKit predict, Configuration config, Class beacon, int beaconSizeBytes) { super(predict, config); + this.beacon = beacon; + this.beaconSizeBytes = beaconSizeBytes; } @Override @@ -35,12 +40,12 @@ protected BufferedImage decodeImage(List beacons) { @Override public BeaconSource createBeaconSource(ByteInput source, Observation req) { - return new Geoscan(source); + return new Geoscan<>(source, beacon, beaconSizeBytes); } @Override public Class getBeaconClass() { - return GeoscanBeacon.class; + return beacon; } } diff --git a/src/main/java/ru/r2cloud/satellite/decoder/StratosatTk1Decoder.java b/src/main/java/ru/r2cloud/satellite/decoder/StratosatTk1Decoder.java index f3503066..48aa39cd 100644 --- a/src/main/java/ru/r2cloud/satellite/decoder/StratosatTk1Decoder.java +++ b/src/main/java/ru/r2cloud/satellite/decoder/StratosatTk1Decoder.java @@ -6,7 +6,7 @@ import ru.r2cloud.jradio.Beacon; import ru.r2cloud.jradio.BeaconSource; import ru.r2cloud.jradio.ByteInput; -import ru.r2cloud.jradio.sstk1.StratosatTk1; +import ru.r2cloud.jradio.geoscan.Geoscan; import ru.r2cloud.jradio.sstk1.StratosatTk1Beacon; import ru.r2cloud.jradio.sstk1.StratosatTk1PictureDecoder; import ru.r2cloud.model.Observation; @@ -35,7 +35,7 @@ protected BufferedImage decodeImage(List beacons) { @Override public BeaconSource createBeaconSource(ByteInput demodulator, Observation req) { - return new StratosatTk1(demodulator); + return new Geoscan<>(demodulator, StratosatTk1Beacon.class); } @Override diff --git a/src/main/resources/satellites.json b/src/main/resources/satellites.json index 7474117d..cec1f684 100644 --- a/src/main/resources/satellites.json +++ b/src/main/resources/satellites.json @@ -1,4 +1,45 @@ [ + { + "id": "R2CLOUD432", + "name": "SAKURA", + "noradId": "60954", + "enabled": false, + "transmitters": [ + { + "modulation": "AFSK", + "framing": "AX25", + "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", + "frequency": 145825000, + "bandwidth": 20000, + "baudRates": [ + 1200 + ], + "deviation": 600, + "afCarrier": 1700, + "status": "ENABLED" + } + ] + }, + { + "id": "R2CLOUD431", + "name": "Changshagaoxin", + "noradId": "43665", + "enabled": false, + "transmitters": [ + { + "modulation": "GFSK", + "framing": "AX100", + "beaconClass": "ru.r2cloud.jradio.csp.CspBeacon", + "frequency": 401606000, + "bandwidth": 20000, + "baudRates": [ + 4800 + ], + "deviation": 5000, + "status": "ENABLED" + } + ] + }, { "id": "R2CLOUD430", "name": "PCSAT (NO-44)", @@ -4680,7 +4721,7 @@ "baudRates": [ 2400 ], - "status": "ENABLED" + "status": "WEAK" } ] }, From 086aae8865db2207036f0ed54d7aba36742caded Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sat, 2 Nov 2024 15:24:13 +0000 Subject: [PATCH 15/25] switch to standard user www-data for serving web content remove unused acme folder --- pom.xml | 6 +----- src/main/deb/html/.well-known/acme-challenge/EMPTY | 0 src/main/resources/r2cloud-prod.conf | 7 +------ 3 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 src/main/deb/html/.well-known/acme-challenge/EMPTY diff --git a/pom.xml b/pom.xml index 1324f8f5..a7149788 100644 --- a/pom.xml +++ b/pom.xml @@ -293,7 +293,7 @@ pi all
embedded
- r2cloud-ui (>=20240303125106), ${jdk.version}, nginx-light(>=1.10.3), rtl-sdr(>=0.6.4), coreutils(>=8.26), libxft2(>=2.3.2), sox(>=14.4.1), wget + r2cloud-ui (>=20241102151628), ${jdk.version}, nginx-light(>=1.10.3), rtl-sdr(>=0.6.4), coreutils(>=8.26), libxft2(>=2.3.2), sox(>=14.4.1), wget false @@ -304,10 +304,6 @@ ${basedir}/src/main/deb/lib /lib - - ${basedir}/src/main/deb/html - html - ${basedir}/src/main/resources/.wxtoimglic /home/pi/.wxtoimglic diff --git a/src/main/deb/html/.well-known/acme-challenge/EMPTY b/src/main/deb/html/.well-known/acme-challenge/EMPTY deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main/resources/r2cloud-prod.conf b/src/main/resources/r2cloud-prod.conf index b5a80311..79b13391 100644 --- a/src/main/resources/r2cloud-prod.conf +++ b/src/main/resources/r2cloud-prod.conf @@ -1,4 +1,4 @@ -user pi; +user www-data; worker_processes 1; events { @@ -63,11 +63,6 @@ http { add_header Cache-Control "public, max-age=86400"; } - location ~ /.well-known { - root /home/pi/r2cloud/html; - allow all; - } - location /api/v1/ { proxy_pass http://backend; proxy_set_header Host $http_host; From c112a134b36b6946cae1a8aef9399a5befa96112 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sat, 2 Nov 2024 19:21:29 +0000 Subject: [PATCH 16/25] store and monitor total data size --- .../java/ru/r2cloud/cloud/InfluxDBClient.java | 5 +++++ .../java/ru/r2cloud/model/DecoderResult.java | 9 +++++++++ src/main/java/ru/r2cloud/model/Observation.java | 16 ++++++++++++++++ .../satellite/decoder/DecoderService.java | 1 + .../r2cloud/satellite/decoder/LoraDecoder.java | 5 ++++- .../satellite/decoder/TelemetryDecoder.java | 3 +++ 6 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/r2cloud/cloud/InfluxDBClient.java b/src/main/java/ru/r2cloud/cloud/InfluxDBClient.java index 03df9e59..45e581eb 100644 --- a/src/main/java/ru/r2cloud/cloud/InfluxDBClient.java +++ b/src/main/java/ru/r2cloud/cloud/InfluxDBClient.java @@ -94,6 +94,11 @@ public void send(Observation obs, Satellite satellite) { frames = 0L; } metric.append(",numberOfDecodedPackets=").append(frames); + Long totalSize = obs.getTotalSize(); + if (totalSize == null) { + totalSize = 0L; + } + metric.append(",totalSize=").append(totalSize); metric.append(",duration=").append(obs.getEndTimeMillis() - obs.getStartTimeMillis()); metric.append(" ").append(obs.getStartTimeMillis()).append("000000"); // nanoseconds diff --git a/src/main/java/ru/r2cloud/model/DecoderResult.java b/src/main/java/ru/r2cloud/model/DecoderResult.java index 92eaea49..8c38bfde 100644 --- a/src/main/java/ru/r2cloud/model/DecoderResult.java +++ b/src/main/java/ru/r2cloud/model/DecoderResult.java @@ -9,9 +9,18 @@ public class DecoderResult { private String channelA; private String channelB; private Long numberOfDecodedPackets = 0L; + private Long totalSize = 0L; private File imagePath; private File dataPath; + + public Long getTotalSize() { + return totalSize; + } + + public void setTotalSize(Long totalSize) { + this.totalSize = totalSize; + } public File getRawPath() { return rawPath; diff --git a/src/main/java/ru/r2cloud/model/Observation.java b/src/main/java/ru/r2cloud/model/Observation.java index ae79ee60..a66c5751 100644 --- a/src/main/java/ru/r2cloud/model/Observation.java +++ b/src/main/java/ru/r2cloud/model/Observation.java @@ -30,6 +30,7 @@ public class Observation { private String channelA; private String channelB; private Long numberOfDecodedPackets; + private Long totalSize = 0L; private String rawURL; private File rawPath; @@ -203,6 +204,14 @@ public void setChannelB(String channelB) { this.channelB = channelB; } + public Long getTotalSize() { + return totalSize; + } + + public void setTotalSize(Long totalSize) { + this.totalSize = totalSize; + } + public Long getNumberOfDecodedPackets() { return numberOfDecodedPackets; } @@ -307,6 +316,10 @@ public static Observation fromJson(JsonObject meta) { if (numberOfDecodedPackets != null) { result.setNumberOfDecodedPackets(numberOfDecodedPackets.asLong()); } + JsonValue totalSize = meta.get("totalSize"); + if (totalSize != null) { + result.setTotalSize(totalSize.asLong()); + } result.setRawURL(meta.getString("rawURL", null)); result.setaURL(meta.getString("aURL", null)); result.setSpectogramURL(meta.getString("spectogramURL", null)); @@ -352,6 +365,9 @@ public JsonObject toJson(SignedURL signed) { if (getNumberOfDecodedPackets() != null) { json.add("numberOfDecodedPackets", getNumberOfDecodedPackets()); } + if (totalSize != null) { + json.add("totalSize", getTotalSize()); + } addNullable("rawURL", getRawURL(), signed, json); addNullable("aURL", getaURL(), signed, json); addNullable("spectogramURL", getSpectogramURL(), signed, json); diff --git a/src/main/java/ru/r2cloud/satellite/decoder/DecoderService.java b/src/main/java/ru/r2cloud/satellite/decoder/DecoderService.java index 9c471857..6f1edc1e 100644 --- a/src/main/java/ru/r2cloud/satellite/decoder/DecoderService.java +++ b/src/main/java/ru/r2cloud/satellite/decoder/DecoderService.java @@ -150,6 +150,7 @@ private boolean decodeInternally(File rawFile, Observation observation) { observation.setChannelA(result.getChannelA()); observation.setChannelB(result.getChannelB()); observation.setNumberOfDecodedPackets(result.getNumberOfDecodedPackets()); + observation.setTotalSize(result.getTotalSize()); observation.setImagePath(result.getImagePath()); observation.setDataPath(result.getDataPath()); observation.setStatus(ObservationStatus.DECODED); diff --git a/src/main/java/ru/r2cloud/satellite/decoder/LoraDecoder.java b/src/main/java/ru/r2cloud/satellite/decoder/LoraDecoder.java index c9025137..c5f01288 100644 --- a/src/main/java/ru/r2cloud/satellite/decoder/LoraDecoder.java +++ b/src/main/java/ru/r2cloud/satellite/decoder/LoraDecoder.java @@ -31,11 +31,14 @@ public DecoderResult decode(File rawFile, Observation request, final Transmitter result.setRawPath(null); long numberOfDecodedPackets = 0; try (BeaconInputStream bis = new BeaconInputStream<>(new BufferedInputStream(new FileInputStream(rawFile)), beacon)) { + long totalSize = 0; while (bis.hasNext()) { - bis.next(); + Beacon beacon = bis.next(); numberOfDecodedPackets++; + totalSize += beacon.getRawData().length; } result.setNumberOfDecodedPackets(numberOfDecodedPackets); + result.setTotalSize(totalSize); } catch (IOException e) { LOG.error("unable to read lora beacons", e); diff --git a/src/main/java/ru/r2cloud/satellite/decoder/TelemetryDecoder.java b/src/main/java/ru/r2cloud/satellite/decoder/TelemetryDecoder.java index aaa90c45..a1259bc2 100644 --- a/src/main/java/ru/r2cloud/satellite/decoder/TelemetryDecoder.java +++ b/src/main/java/ru/r2cloud/satellite/decoder/TelemetryDecoder.java @@ -56,6 +56,7 @@ public DecoderResult decode(File rawIq, Observation req, final Transmitter trans } long numberOfDecodedPackets = 0; + long totalSize = 0; float sampleRate = req.getSampleRate(); File binFile = new File(config.getTempDirectory(), req.getId() + ".bin"); List> input = null; @@ -79,6 +80,7 @@ public DecoderResult decode(File rawIq, Observation req, final Transmitter trans next.setRxMeta(meta); beacons.add(next); numberOfDecodedPackets++; + totalSize += next.getRawData().length; } } finally { Util.closeQuietly(currentInput); @@ -108,6 +110,7 @@ public DecoderResult decode(File rawIq, Observation req, final Transmitter trans return result; } result.setNumberOfDecodedPackets(numberOfDecodedPackets); + result.setTotalSize(totalSize); if (numberOfDecodedPackets <= 0) { Util.deleteQuietly(binFile); } else { From fe8c6ebe65a6ed4d6e58f47e24a13adfd2e60e31 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sat, 2 Nov 2024 19:25:06 +0000 Subject: [PATCH 17/25] fix unit test --- src/test/java/ru/r2cloud/cloud/InfluxDBClientTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/ru/r2cloud/cloud/InfluxDBClientTest.java b/src/test/java/ru/r2cloud/cloud/InfluxDBClientTest.java index f1a31c9e..842185ad 100644 --- a/src/test/java/ru/r2cloud/cloud/InfluxDBClientTest.java +++ b/src/test/java/ru/r2cloud/cloud/InfluxDBClientTest.java @@ -52,6 +52,7 @@ public void testSendObservation() throws Exception { observation.setStartTimeMillis(1601173648133L); observation.setEndTimeMillis(1601173678133L); observation.setNumberOfDecodedPackets(10L); + observation.setTotalSize(1024L); Satellite satellite = new Satellite(); satellite.setName("CAS-5A (FO-118)"); @@ -60,7 +61,7 @@ public void testSendObservation() throws Exception { List metrics = server.getMetricsByDatabase().get("r2cloud"); assertNotNull(metrics); assertEquals(1, metrics.size()); - assertEquals("observation,satellite=CAS-5A(FO-118),deviceId=rtlsdr.0,antennaType=DIRECTIONAL,hostname=test.local tleUpdateLatency=30000,tleEpochLatency=30000,numberOfDecodedPackets=10,duration=30000 1601173648133000000", metrics.get(0)); + assertEquals("observation,satellite=CAS-5A(FO-118),deviceId=rtlsdr.0,antennaType=DIRECTIONAL,hostname=test.local tleUpdateLatency=30000,tleEpochLatency=30000,numberOfDecodedPackets=10,totalSize=1024,duration=30000 1601173648133000000", metrics.get(0)); } @Test From 235352c2a618907f358207a9c38a66ccd548cb50 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sun, 3 Nov 2024 15:57:55 +0000 Subject: [PATCH 18/25] make sure nginx cache is owned by www-data --- src/main/deb/postinst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/deb/postinst b/src/main/deb/postinst index a2c97f82..3834d998 100644 --- a/src/main/deb/postinst +++ b/src/main/deb/postinst @@ -25,6 +25,9 @@ if [ $(getent group systemd-journal) ]; then usermod -a -G systemd-journal ${config.user} fi +if [ -d /var/lib/nginx/proxy ]; then + chown -R www-data:www-data /var/lib/nginx/proxy +fi chown -R ${config.user}:${config.group} ${config.installDir} /usr/share/java/r2cloud/*.jar chmod 640 ${config.installDir}/lib/*.jar /usr/share/java/r2cloud/*.jar From 29c37564bb466eca291cadf816b384ddf15af2e0 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Wed, 13 Nov 2024 20:02:52 +0000 Subject: [PATCH 19/25] show only upcoming/ongoing observations --- src/main/java/ru/r2cloud/R2Cloud.java | 2 +- .../java/ru/r2cloud/web/api/device/DeviceSchedule.java | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/r2cloud/R2Cloud.java b/src/main/java/ru/r2cloud/R2Cloud.java index 9b6c2c21..0ea3faf9 100644 --- a/src/main/java/ru/r2cloud/R2Cloud.java +++ b/src/main/java/ru/r2cloud/R2Cloud.java @@ -284,7 +284,7 @@ public R2Cloud(Configuration props, Clock clock) { index(new DeviceConfigList(deviceManager)); index(new DeviceConfigDelete(props, deviceManager)); index(new Restart()); - index(new DeviceSchedule(deviceManager, satelliteDao)); + index(new DeviceSchedule(deviceManager, satelliteDao, clock)); webServer = new WebServer(props, controllers, auth, signed); } diff --git a/src/main/java/ru/r2cloud/web/api/device/DeviceSchedule.java b/src/main/java/ru/r2cloud/web/api/device/DeviceSchedule.java index 8e8c353c..4ea903df 100644 --- a/src/main/java/ru/r2cloud/web/api/device/DeviceSchedule.java +++ b/src/main/java/ru/r2cloud/web/api/device/DeviceSchedule.java @@ -13,6 +13,7 @@ import ru.r2cloud.model.Satellite; import ru.r2cloud.satellite.ObservationRequestComparator; import ru.r2cloud.satellite.SatelliteDao; +import ru.r2cloud.util.Clock; import ru.r2cloud.web.AbstractHttpController; import ru.r2cloud.web.ModelAndView; import ru.r2cloud.web.NotFound; @@ -22,10 +23,12 @@ public class DeviceSchedule extends AbstractHttpController { private final DeviceManager deviceManager; private final SatelliteDao satelliteDao; + private final Clock clock; - public DeviceSchedule(DeviceManager deviceManager, SatelliteDao satelliteDao) { + public DeviceSchedule(DeviceManager deviceManager, SatelliteDao satelliteDao, Clock clock) { this.deviceManager = deviceManager; this.satelliteDao = satelliteDao; + this.clock = clock; } @Override @@ -38,7 +41,11 @@ public ModelAndView doGet(IHTTPSession session) { JsonArray result = new JsonArray(); List request = device.findScheduledObservations(); Collections.sort(request, ObservationRequestComparator.INSTANCE); + long currentTime = clock.millis(); for (ObservationRequest cur : request) { + if (cur.getEndTimeMillis() < currentTime) { + continue; + } Satellite satellite = satelliteDao.findById(cur.getSatelliteId()); if (satellite == null) { continue; From 1b51a20c62390d795f53a5677e0b1245998ed5f3 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Sun, 17 Nov 2024 21:42:12 +0000 Subject: [PATCH 20/25] load satellite by id and store satellite info update time --- src/main/java/ru/r2cloud/R2Cloud.java | 2 + .../ru/r2cloud/cloud/LeoSatDataClient.java | 33 +++++++++++++-- src/main/java/ru/r2cloud/model/Satellite.java | 16 +++++++- .../ru/r2cloud/satellite/SatelliteDao.java | 12 +++--- .../web/api/schedule/SatelliteLoad.java | 40 +++++++++++++++++++ .../web/api/schedule/ScheduleList.java | 1 + .../java/ru/r2cloud/it/SatelliteLoadTest.java | 15 +++++++ .../java/ru/r2cloud/it/util/RestClient.java | 17 ++++++++ src/test/resources/expected/satellite.json | 25 ++++++++++++ 9 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 src/main/java/ru/r2cloud/web/api/schedule/SatelliteLoad.java create mode 100644 src/test/java/ru/r2cloud/it/SatelliteLoadTest.java create mode 100644 src/test/resources/expected/satellite.json diff --git a/src/main/java/ru/r2cloud/R2Cloud.java b/src/main/java/ru/r2cloud/R2Cloud.java index 0ea3faf9..4e490eef 100644 --- a/src/main/java/ru/r2cloud/R2Cloud.java +++ b/src/main/java/ru/r2cloud/R2Cloud.java @@ -95,6 +95,7 @@ import ru.r2cloud.web.api.observation.ObservationSigMfData; import ru.r2cloud.web.api.observation.ObservationSigMfMeta; import ru.r2cloud.web.api.observation.ObservationSpectrogram; +import ru.r2cloud.web.api.schedule.SatelliteLoad; import ru.r2cloud.web.api.schedule.ScheduleComplete; import ru.r2cloud.web.api.schedule.ScheduleFull; import ru.r2cloud.web.api.schedule.ScheduleList; @@ -285,6 +286,7 @@ public R2Cloud(Configuration props, Clock clock) { index(new DeviceConfigDelete(props, deviceManager)); index(new Restart()); index(new DeviceSchedule(deviceManager, satelliteDao, clock)); + index(new SatelliteLoad(satelliteDao)); webServer = new WebServer(props, controllers, auth, signed); } diff --git a/src/main/java/ru/r2cloud/cloud/LeoSatDataClient.java b/src/main/java/ru/r2cloud/cloud/LeoSatDataClient.java index 8e8d29c7..b9564e75 100644 --- a/src/main/java/ru/r2cloud/cloud/LeoSatDataClient.java +++ b/src/main/java/ru/r2cloud/cloud/LeoSatDataClient.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Optional; import java.util.TimeZone; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -105,7 +106,18 @@ public List loadNewLaunches(long lastModified) throws NotModifiedExce } return Collections.emptyList(); } - List result = readNewLaunches(response.body()); + Optional lastModifiedOnServer = response.headers().firstValue("Last-Modified"); + lastModified = 0; + if (lastModifiedOnServer.isPresent()) { + SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + try { + lastModified = sdf.parse(lastModifiedOnServer.get()).getTime(); + } catch (java.text.ParseException e) { + LOG.error("invalid date provided: {}", lastModifiedOnServer.get(), e); + } + } + List result = readNewLaunches(response.body(), lastModified); LOG.info("new satellites from leosatdata were loaded: {}", result.size()); return result; } catch (IOException e) { @@ -141,7 +153,18 @@ public List loadSatellites(long lastModified, Boolean all) throws Not } return Collections.emptyList(); } - List result = readSatellites(response.body()); + Optional lastModifiedOnServer = response.headers().firstValue("Last-Modified"); + lastModified = 0; + if (lastModifiedOnServer.isPresent()) { + SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + try { + lastModified = sdf.parse(lastModifiedOnServer.get()).getTime(); + } catch (java.text.ParseException e) { + LOG.error("invalid date provided: {}", lastModifiedOnServer.get(), e); + } + } + List result = readSatellites(response.body(), lastModified); LOG.info("satellites from leosatdata were loaded: {}", result.size()); return result; } catch (IOException e) { @@ -227,7 +250,7 @@ private HttpResponse sendWithRetry(HttpRequest request, HttpResponse.Bod } } - private static List readSatellites(String body) { + private static List readSatellites(String body, long lastModified) { JsonValue parsedJson; try { parsedJson = Json.parse(body); @@ -258,12 +281,13 @@ private static List readSatellites(String body) { continue; } cur.setSource(SatelliteSource.LEOSATDATA); + cur.setLastUpdateTime(lastModified); result.add(cur); } return result; } - private List readNewLaunches(String body) { + private List readNewLaunches(String body, long lastModified) { JsonValue parsedJson; try { parsedJson = Json.parse(body); @@ -302,6 +326,7 @@ private List readNewLaunches(String body) { newLaunch.setPriority(Priority.HIGH); newLaunch.setTle(readTle(asObject.get("tle"))); newLaunch.setSource(SatelliteSource.LEOSATDATA); + newLaunch.setLastUpdateTime(lastModified); result.add(newLaunch); } return result; diff --git a/src/main/java/ru/r2cloud/model/Satellite.java b/src/main/java/ru/r2cloud/model/Satellite.java index e64b243b..f1105ac6 100644 --- a/src/main/java/ru/r2cloud/model/Satellite.java +++ b/src/main/java/ru/r2cloud/model/Satellite.java @@ -23,11 +23,20 @@ public class Satellite { private Date end; private List transmitters; private SatelliteSource source; - + private long lastUpdateTime; + + public long getLastUpdateTime() { + return lastUpdateTime; + } + + public void setLastUpdateTime(long lastUpdateTime) { + this.lastUpdateTime = lastUpdateTime; + } + public int getPriorityIndex() { return priorityIndex; } - + public void setPriorityIndex(int priorityIndex) { this.priorityIndex = priorityIndex; } @@ -214,6 +223,9 @@ public JsonObject toJson() { transmittersJson.add(cur.toJson()); } result.add("transmitters", transmittersJson); + if (lastUpdateTime != 0) { + result.add("lastUpdateTime", lastUpdateTime); + } return result; } diff --git a/src/main/java/ru/r2cloud/satellite/SatelliteDao.java b/src/main/java/ru/r2cloud/satellite/SatelliteDao.java index 4f0f3ccd..3fdf8fd9 100644 --- a/src/main/java/ru/r2cloud/satellite/SatelliteDao.java +++ b/src/main/java/ru/r2cloud/satellite/SatelliteDao.java @@ -84,21 +84,22 @@ public synchronized void saveSatnogs(List loadSatellites, long curren private void loadFromDisk() { satnogsLastUpdateTime = getLastModifiedTimeSafely(config.getPathFromProperty(SATNOGS_LOCATION)); - satnogs.addAll(loadFromConfig(config.getPathFromProperty(SATNOGS_LOCATION), SatelliteSource.SATNOGS)); + satnogs.addAll(loadFromConfig(config.getPathFromProperty(SATNOGS_LOCATION), SatelliteSource.SATNOGS, satnogsLastUpdateTime)); // default from config String metaLocation = config.getProperty("satellites.meta.location"); if (metaLocation.startsWith("classpath:")) { staticSatellites.addAll(loadFromClasspathConfig(metaLocation.substring(CLASSPATH_PREFIX.length()), SatelliteSource.CONFIG)); } else { - staticSatellites.addAll(loadFromConfig(config.getPathFromProperty("satellites.meta.location"), SatelliteSource.CONFIG)); + long staticLastUpdateTime = getLastModifiedTimeSafely(config.getPathFromProperty("satellites.meta.location")); + staticSatellites.addAll(loadFromConfig(config.getPathFromProperty("satellites.meta.location"), SatelliteSource.CONFIG, staticLastUpdateTime)); } leosatdataLastUpdateTime = getLastModifiedTimeSafely(config.getPathFromProperty(LEOSATDATA_LOCATION)); - leosatdata.addAll(loadFromConfig(config.getPathFromProperty(LEOSATDATA_LOCATION), SatelliteSource.LEOSATDATA)); + leosatdata.addAll(loadFromConfig(config.getPathFromProperty(LEOSATDATA_LOCATION), SatelliteSource.LEOSATDATA, leosatdataLastUpdateTime)); leosatdataNewLastUpdateTime = getLastModifiedTimeSafely(config.getPathFromProperty(LEOSATDATA_NEW_LOCATION)); - leosatDataNewLaunches.addAll(loadFromConfig(config.getPathFromProperty(LEOSATDATA_NEW_LOCATION), SatelliteSource.LEOSATDATA)); + leosatDataNewLaunches.addAll(loadFromConfig(config.getPathFromProperty(LEOSATDATA_NEW_LOCATION), SatelliteSource.LEOSATDATA, leosatdataNewLastUpdateTime)); } public synchronized void reindex() { @@ -237,7 +238,7 @@ private static List loadFromClasspathConfig(String metaLocation, Sate return result; } - private static List loadFromConfig(Path metaLocation, SatelliteSource source) { + private static List loadFromConfig(Path metaLocation, SatelliteSource source, long lastUpdateTime) { List result = new ArrayList<>(); if (!Files.exists(metaLocation)) { return result; @@ -255,6 +256,7 @@ private static List loadFromConfig(Path metaLocation, SatelliteSource continue; } cur.setSource(source); + cur.setLastUpdateTime(lastUpdateTime); result.add(cur); } return result; diff --git a/src/main/java/ru/r2cloud/web/api/schedule/SatelliteLoad.java b/src/main/java/ru/r2cloud/web/api/schedule/SatelliteLoad.java new file mode 100644 index 00000000..8d926b66 --- /dev/null +++ b/src/main/java/ru/r2cloud/web/api/schedule/SatelliteLoad.java @@ -0,0 +1,40 @@ +package ru.r2cloud.web.api.schedule; + +import com.eclipsesource.json.JsonObject; + +import fi.iki.elonen.NanoHTTPD.IHTTPSession; +import ru.r2cloud.model.Satellite; +import ru.r2cloud.satellite.SatelliteDao; +import ru.r2cloud.web.AbstractHttpController; +import ru.r2cloud.web.ModelAndView; +import ru.r2cloud.web.NotFound; +import ru.r2cloud.web.WebServer; + +public class SatelliteLoad extends AbstractHttpController { + + private final SatelliteDao dao; + + public SatelliteLoad(SatelliteDao dao) { + this.dao = dao; + } + + @Override + public ModelAndView doGet(IHTTPSession session) { + String id = WebServer.getParameter(session, "id"); + Satellite satellite = dao.findById(id); + if (satellite == null || satellite.getTle() == null) { + return new NotFound(); + } + JsonObject json = satellite.toJson(); + json.add("tle", satellite.getTle().toJson()); + json.add("source", satellite.getSource().name()); + ModelAndView result = new ModelAndView(); + result.setData(json); + return result; + } + + @Override + public String getRequestMappingURL() { + return "/api/v1/admin/satellite/load"; + } +} diff --git a/src/main/java/ru/r2cloud/web/api/schedule/ScheduleList.java b/src/main/java/ru/r2cloud/web/api/schedule/ScheduleList.java index 1b0407fe..1797692f 100644 --- a/src/main/java/ru/r2cloud/web/api/schedule/ScheduleList.java +++ b/src/main/java/ru/r2cloud/web/api/schedule/ScheduleList.java @@ -31,6 +31,7 @@ public ModelAndView doGet(IHTTPSession session) { curSatellite.add("name", cur.getName()); curSatellite.add("enabled", cur.isEnabled()); curSatellite.add("status", cur.getOverallStatus().name()); + curSatellite.add("source", cur.getSource().name()); Transmitter transmitter = null; for (Transmitter curTransmitter : cur.getTransmitters()) { ObservationRequest nextObservation = deviceManager.findFirstByTransmitter(curTransmitter); diff --git a/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java b/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java new file mode 100644 index 00000000..78eb9644 --- /dev/null +++ b/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java @@ -0,0 +1,15 @@ +package ru.r2cloud.it; + +import org.junit.Test; + +import ru.r2cloud.TestUtil; +import ru.r2cloud.it.util.RegisteredTest; + +public class SatelliteLoadTest extends RegisteredTest { + + @Test + public void testLoad() { + TestUtil.assertJson("expected/satellite.json", client.getSatellite("25338")); + } + +} diff --git a/src/test/java/ru/r2cloud/it/util/RestClient.java b/src/test/java/ru/r2cloud/it/util/RestClient.java index bddfd940..a174826e 100644 --- a/src/test/java/ru/r2cloud/it/util/RestClient.java +++ b/src/test/java/ru/r2cloud/it/util/RestClient.java @@ -411,6 +411,23 @@ public JsonArray getSchedule() { } } + public JsonObject getSatellite(String id) { + HttpRequest request = createAuthRequest("/api/v1/admin/satellite/load?id=" + id).GET().build(); + try { + HttpResponse response = httpclient.send(request, BodyHandlers.ofString()); + if (response.statusCode() != 200) { + LOG.info("response: {}", response.body()); + throw new RuntimeException("invalid status code: " + response.statusCode()); + } + return (JsonObject) Json.parse(response.body()); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("unable to send request"); + } + } + public HttpResponse updateScheduleWithResponse(String id, boolean enabled) { JsonObject entity = new JsonObject(); if (id != null) { diff --git a/src/test/resources/expected/satellite.json b/src/test/resources/expected/satellite.json new file mode 100644 index 00000000..b5193f54 --- /dev/null +++ b/src/test/resources/expected/satellite.json @@ -0,0 +1,25 @@ +{ + "name": "NOAA 15", + "noradId": "25338", + "priority": "NORMAL", + "enabled": true, + "transmitters": [ + { + "framing": "APT", + "frequency": 137620000, + "bandwidth": 34000, + "baudRates": [ + ], + "status": "ENABLED" + } + ], + "lastUpdateTime": 1719525695573, + "tle": { + "line1": "eedfe24e-6fa6-4466-ac16-f6086d1a0333", + "line2": "1 25338U 98030A 17288.49529043 .00000057 00000-0 23836-4 0 9991", + "line3": "2 25338 98.7843 299.6383 0010164 216.8147 143.2938 14.25820166 10144", + "updated": 1559942730784, + "source": "localhost" + }, + "source": "CONFIG" +} From 0b287e556d622a573393cee7fe68aa1c7b2ba469 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Mon, 18 Nov 2024 08:43:51 +0000 Subject: [PATCH 21/25] save tle from satnogs satellites + propagate last update time --- .../java/ru/r2cloud/cloud/SatnogsClient.java | 16 ++- src/main/java/ru/r2cloud/model/Satellite.java | 4 + .../java/ru/r2cloud/tle/Housekeeping.java | 2 + .../ru/r2cloud/cloud/SatnogsClientTest.java | 25 ++-- .../resources/expected/satnogsSatellites.json | 107 +++--------------- 5 files changed, 42 insertions(+), 112 deletions(-) diff --git a/src/main/java/ru/r2cloud/cloud/SatnogsClient.java b/src/main/java/ru/r2cloud/cloud/SatnogsClient.java index 3629e800..d3fb83e5 100644 --- a/src/main/java/ru/r2cloud/cloud/SatnogsClient.java +++ b/src/main/java/ru/r2cloud/cloud/SatnogsClient.java @@ -84,10 +84,6 @@ public List loadSatellites() { List result = loadAllSatellites(groupBySatelliteId); for (Satellite cur : result) { dedupTransmittersByKey(cur); - if (!cur.getPriority().equals(Priority.HIGH)) { - continue; - } - cur.setTle(loadTleBySatelliteId(cur.getId())); } LOG.info("satellites from satnogs were loaded: {}", result.size()); return result; @@ -180,6 +176,8 @@ private List readSatellites(String body, Map result = new ArrayList<>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); sdf.setTimeZone(TimeZone.getTimeZone("GMT")); + SimpleDateFormat updatedFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + updatedFormat.setTimeZone(TimeZone.getTimeZone("GMT")); for (int i = 0; i < parsedArray.size(); i++) { JsonValue jsonValue = parsedArray.get(i); if (!jsonValue.isObject()) { @@ -212,6 +210,16 @@ private List readSatellites(String body, Map satellite = client.loadSatellites(); assertEquals(1, satellite.size()); assertNull(satellite.get(0).getTle()); - + server.setTransmittersMock(new JsonHttpResponse("satnogs/transmitters.json", 200)); server.setTleMockDirectory("satnogs"); @@ -65,7 +57,7 @@ public void testInvalidResponse() throws Exception { assertTrue(client.loadSatellites().isEmpty()); server.setSatellitesMock("[ [1,2,3] ]", 200); assertTrue(client.loadSatellites().isEmpty()); - + server.setSatellitesMock(new JsonHttpResponse("satnogs/satellites.json", 200)); server.setTransmittersMock("not a json", 200); assertTrue(client.loadSatellites().isEmpty()); @@ -81,12 +73,11 @@ public void testSuccess() { server.setTransmittersMock(new JsonHttpResponse("satnogs/transmitters.json", 200)); server.setTleMockDirectory("satnogs"); List actual = client.loadSatellites(); - Gson GSON = new GsonBuilder().registerTypeAdapter(Date.class, new DateAdapter()).registerTypeAdapter(new TypeToken>() { - }.getType(), new ClassAdapter()).create(); - Type listOfMyClassObject = new TypeToken>() { - }.getType(); - String serialized = GSON.toJson(actual, listOfMyClassObject); - TestUtil.assertJson("expected/satnogsSatellites.json", Json.parse(serialized).asArray()); + JsonArray array = new JsonArray(); + for (Satellite cur : actual) { + array.add(cur.toJson()); + } + TestUtil.assertJson("expected/satnogsSatellites.json", array); } @Before diff --git a/src/test/resources/expected/satnogsSatellites.json b/src/test/resources/expected/satnogsSatellites.json index 32b9bae4..d46cb312 100644 --- a/src/test/resources/expected/satnogsSatellites.json +++ b/src/test/resources/expected/satnogsSatellites.json @@ -1,44 +1,29 @@ [ { - "id": "53464", "name": "TUMnanoSAT", - "enabled": false, + "noradId": "53464", "priority": "NORMAL", + "enabled": false, "transmitters": [ { "modulation": "GFSK", "framing": "AX25", "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", - "beaconSizeBytes": 0, "frequency": 436680000, "bandwidth": 20000, "baudRates": [ 9600 ], - "bpskDifferential": false, - "bpskCenterFrequency": 0.0, - "deviation": 5000, - "afCarrier": 0, - "transitionWidth": 2000.0, - "loraBandwidth": 0, - "loraSpreadFactor": 0, - "loraCodingRate": 0, - "loraSyncword": 0, - "loraPreambleLength": 0, - "loraLdro": 0, - "status": "ENABLED", - "updated": 1660259232836, - "enabled": false, - "satelliteId": "EUEU-0112-5297-2105-4789" + "status": "ENABLED" } ], - "source": "SATNOGS" + "lastUpdateTime": 1660566222623 }, { - "id": "43908", "name": "LUME-1", - "enabled": false, + "noradId": "43908", "priority": "NORMAL", + "enabled": false, "transmitters": [ { "modulation": "GFSK", @@ -51,110 +36,50 @@ 9600, 4800 ], - "bpskDifferential": false, - "bpskCenterFrequency": 0.0, - "deviation": 5000, - "afCarrier": 0, - "transitionWidth": 2000.0, - "loraBandwidth": 0, - "loraSpreadFactor": 0, - "loraCodingRate": 0, - "loraSyncword": 0, - "loraPreambleLength": 0, - "loraLdro": 0, - "status": "ENABLED", - "updated": 1616533349387, - "enabled": false, - "satelliteId": "QXDQ-9755-0160-9983-1765" + "status": "ENABLED" } ], - "source": "SATNOGS" + "lastUpdateTime": 1650904308036 }, { - "id": "52950", "name": "CTIM", - "enabled": false, + "noradId": "52950", "priority": "NORMAL", + "enabled": false, "transmitters": [ { "modulation": "GFSK", "framing": "AX25G3RUH", "beaconClass": "ru.r2cloud.jradio.ax25.Ax25Beacon", - "beaconSizeBytes": 0, "frequency": 437396000, "bandwidth": 20000, "baudRates": [ 9600 ], - "bpskDifferential": false, - "bpskCenterFrequency": 0.0, - "deviation": 5000, - "afCarrier": 0, - "transitionWidth": 2000.0, - "loraBandwidth": 0, - "loraSpreadFactor": 0, - "loraCodingRate": 0, - "loraSyncword": 0, - "loraPreambleLength": 0, - "loraLdro": 0, - "status": "ENABLED", - "updated": 1659891135340, - "enabled": false, - "satelliteId": "CSGC-5605-1718-3109-5402" + "status": "ENABLED" } ], - "source": "SATNOGS" + "lastUpdateTime": 1659986870989 }, { - "id": "BDML-1083-7096-5526-2883", "name": "Skoltech B2", - "enabled": false, - "tle": { - "raw": [ - "0 OBJECT L", - "1 53380U 22096L 22229.57175693 .00005563 00000-0 22547-3 0 9990", - "2 53380 97.4353 130.9847 0005660 72.7520 287.4337 15.25041840 1267" - ], - "lastUpdateTime": 1660816317365 - }, + "noradId": "BDML-1083-7096-5526-2883", "priority": "HIGH", + "enabled": false, "start": 1660816307365, "transmitters": [ { "modulation": "GFSK", "framing": "USP", "beaconClass": "ru.r2cloud.jradio.usp.UspBeacon", - "beaconSizeBytes": 0, "frequency": 435598000, "bandwidth": 20000, "baudRates": [ 9600 ], - "bpskDifferential": false, - "bpskCenterFrequency": 0.0, - "deviation": 5000, - "afCarrier": 0, - "transitionWidth": 2000.0, - "loraBandwidth": 0, - "loraSpreadFactor": 0, - "loraCodingRate": 0, - "loraSyncword": 0, - "loraPreambleLength": 0, - "loraLdro": 0, - "status": "ENABLED", - "updated": 1660748306832, - "enabled": false, - "satelliteId": "BDML-1083-7096-5526-2883", - "tle": { - "raw": [ - "0 OBJECT L", - "1 53380U 22096L 22229.57175693 .00005563 00000-0 22547-3 0 9990", - "2 53380 97.4353 130.9847 0005660 72.7520 287.4337 15.25041840 1267" - ], - "lastUpdateTime": 1660816317365 - } + "status": "ENABLED" } ], - "source": "SATNOGS" + "lastUpdateTime": 1660152501110 } ] From 1b12b69c4da051c1bf735d0b84b3ed201be68896 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Mon, 18 Nov 2024 08:45:35 +0000 Subject: [PATCH 22/25] override with default last update time if not available on server --- src/main/java/ru/r2cloud/cloud/LeoSatDataClient.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/r2cloud/cloud/LeoSatDataClient.java b/src/main/java/ru/r2cloud/cloud/LeoSatDataClient.java index b9564e75..1164c3fd 100644 --- a/src/main/java/ru/r2cloud/cloud/LeoSatDataClient.java +++ b/src/main/java/ru/r2cloud/cloud/LeoSatDataClient.java @@ -281,7 +281,9 @@ private static List readSatellites(String body, long lastModified) { continue; } cur.setSource(SatelliteSource.LEOSATDATA); - cur.setLastUpdateTime(lastModified); + if (cur.getLastUpdateTime() == 0) { + cur.setLastUpdateTime(lastModified); + } result.add(cur); } return result; @@ -326,7 +328,9 @@ private List readNewLaunches(String body, long lastModified) { newLaunch.setPriority(Priority.HIGH); newLaunch.setTle(readTle(asObject.get("tle"))); newLaunch.setSource(SatelliteSource.LEOSATDATA); - newLaunch.setLastUpdateTime(lastModified); + if (newLaunch.getLastUpdateTime() == 0) { + newLaunch.setLastUpdateTime(lastModified); + } result.add(newLaunch); } return result; From a0b8bdaee37d5993e79f41f9b52ea150ac44751e Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Mon, 18 Nov 2024 11:32:45 +0000 Subject: [PATCH 23/25] fix unit test --- src/test/java/ru/r2cloud/it/SatelliteLoadTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java b/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java index 78eb9644..11fa07d7 100644 --- a/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java +++ b/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java @@ -1,5 +1,10 @@ package ru.r2cloud.it; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.util.concurrent.TimeUnit; + import org.junit.Test; import ru.r2cloud.TestUtil; @@ -8,7 +13,9 @@ public class SatelliteLoadTest extends RegisteredTest { @Test - public void testLoad() { + public void testLoad() throws Exception { + Path p = config.getPathFromProperty("satellites.meta.location"); + Files.setLastModifiedTime(p, FileTime.from(1719525695573L, TimeUnit.MILLISECONDS)); TestUtil.assertJson("expected/satellite.json", client.getSatellite("25338")); } From dd3c700f44b37e55976063dc38ad66789305d285 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Mon, 18 Nov 2024 11:35:36 +0000 Subject: [PATCH 24/25] fix unit test --- src/test/java/ru/r2cloud/it/SatelliteLoadTest.java | 7 ------- src/test/java/ru/r2cloud/it/util/BaseTest.java | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java b/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java index 11fa07d7..587b03aa 100644 --- a/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java +++ b/src/test/java/ru/r2cloud/it/SatelliteLoadTest.java @@ -1,10 +1,5 @@ package ru.r2cloud.it; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.util.concurrent.TimeUnit; - import org.junit.Test; import ru.r2cloud.TestUtil; @@ -14,8 +9,6 @@ public class SatelliteLoadTest extends RegisteredTest { @Test public void testLoad() throws Exception { - Path p = config.getPathFromProperty("satellites.meta.location"); - Files.setLastModifiedTime(p, FileTime.from(1719525695573L, TimeUnit.MILLISECONDS)); TestUtil.assertJson("expected/satellite.json", client.getSatellite("25338")); } diff --git a/src/test/java/ru/r2cloud/it/util/BaseTest.java b/src/test/java/ru/r2cloud/it/util/BaseTest.java index fbe56265..1e601cec 100644 --- a/src/test/java/ru/r2cloud/it/util/BaseTest.java +++ b/src/test/java/ru/r2cloud/it/util/BaseTest.java @@ -14,9 +14,13 @@ import java.net.InetSocketAddress; import java.net.http.HttpResponse; import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; import java.util.Properties; import java.util.Random; import java.util.UUID; +import java.util.concurrent.TimeUnit; import java.util.logging.LogManager; import org.junit.After; @@ -172,6 +176,8 @@ protected Configuration prepareConfiguration() throws IOException { w.append("ittests"); } config.setProperty("server.keyword.location", setupKeyword.getAbsolutePath()); + Path p = config.getPathFromProperty("satellites.meta.location"); + Files.setLastModifiedTime(p, FileTime.from(1719525695573L, TimeUnit.MILLISECONDS)); return config; } From e545d05c170c955446f682e6086ffa7ad09a2062 Mon Sep 17 00:00:00 2001 From: dernasherbrezon Date: Mon, 18 Nov 2024 11:50:10 +0000 Subject: [PATCH 25/25] load last update time from disk if any --- src/main/java/ru/r2cloud/satellite/SatelliteDao.java | 4 +++- src/test/java/ru/r2cloud/it/util/BaseTest.java | 6 ------ src/test/resources/satellites-test.json | 3 ++- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/ru/r2cloud/satellite/SatelliteDao.java b/src/main/java/ru/r2cloud/satellite/SatelliteDao.java index 3fdf8fd9..2e62d409 100644 --- a/src/main/java/ru/r2cloud/satellite/SatelliteDao.java +++ b/src/main/java/ru/r2cloud/satellite/SatelliteDao.java @@ -256,7 +256,9 @@ private static List loadFromConfig(Path metaLocation, SatelliteSource continue; } cur.setSource(source); - cur.setLastUpdateTime(lastUpdateTime); + if (cur.getLastUpdateTime() == 0) { + cur.setLastUpdateTime(lastUpdateTime); + } result.add(cur); } return result; diff --git a/src/test/java/ru/r2cloud/it/util/BaseTest.java b/src/test/java/ru/r2cloud/it/util/BaseTest.java index 1e601cec..fbe56265 100644 --- a/src/test/java/ru/r2cloud/it/util/BaseTest.java +++ b/src/test/java/ru/r2cloud/it/util/BaseTest.java @@ -14,13 +14,9 @@ import java.net.InetSocketAddress; import java.net.http.HttpResponse; import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; import java.util.Properties; import java.util.Random; import java.util.UUID; -import java.util.concurrent.TimeUnit; import java.util.logging.LogManager; import org.junit.After; @@ -176,8 +172,6 @@ protected Configuration prepareConfiguration() throws IOException { w.append("ittests"); } config.setProperty("server.keyword.location", setupKeyword.getAbsolutePath()); - Path p = config.getPathFromProperty("satellites.meta.location"); - Files.setLastModifiedTime(p, FileTime.from(1719525695573L, TimeUnit.MILLISECONDS)); return config; } diff --git a/src/test/resources/satellites-test.json b/src/test/resources/satellites-test.json index 65fd6ad6..f13420d7 100644 --- a/src/test/resources/satellites-test.json +++ b/src/test/resources/satellites-test.json @@ -1490,7 +1490,8 @@ "frequency": 137620000, "bandwidth": 34000 } - ] + ], + "lastUpdateTime": 1719525695573 }, { "name": "SWAMPSAT-2",