From 9b1850c8e26d9d4302ab8c2b6c9baac0760fbe84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Mindlin?= Date: Mon, 23 Feb 2026 19:14:49 -0500 Subject: [PATCH 1/8] feat: Updates from Assembly Test (#43) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * assembly test * feat: FC Acks * feat: command completion * Update xtce-generator (#42) * Refactor: Split xtce-generator output Split generator output into two seperate files for telemetry and commanding * Feat: Implement CommandLevel * Implement: Raw Units Final --------- Co-authored-by: Léo Mindlin * remvoe raw units * update sheet * Latest updates * update radios link --------- Co-authored-by: Samuel Baker --- Tiltfile | 8 +- .../java/org/yamcs/mrt/AstraCommandLink.java | 38 +- .../java/org/yamcs/mrt/astra/RadiosLink.java | 121 +- apps/backend/src/main/yamcs/mdb/commands.xml | 136 +- apps/backend/src/main/yamcs/mdb/radio.xml | 62 + apps/backend/src/main/yamcs/mdb/rocket.xml | 712 +++++----- .../src/cards/command-button/index.tsx | 44 +- .../src/cards/command-history/card.tsx | 4 +- .../src/cards/command-history/utils.ts | 4 +- apps/frontend/src/cards/parameter-table.tsx | 94 +- apps/frontend/src/pages/dashboard.tsx | 5 - apps/xtce-generator/converter.py | 632 --------- apps/xtce-generator/requirements.txt | 2 +- apps/xtce-generator/src/commanding_system.py | 142 ++ apps/xtce-generator/src/commands.xml | 188 +++ apps/xtce-generator/src/flight_system.py | 118 ++ apps/xtce-generator/src/rocket.xml | 1171 +++++++++++++++++ apps/xtce-generator/src/telemetry_system.py | 440 +++++++ apps/xtce-generator/src/xtce_generator.py | 53 + packages/yamcs-effect/src/http/mdb.ts | 13 + packages/yamcs-effect/src/schema.ts | 15 +- 21 files changed, 2709 insertions(+), 1293 deletions(-) create mode 100644 apps/backend/src/main/yamcs/mdb/radio.xml delete mode 100644 apps/xtce-generator/converter.py create mode 100644 apps/xtce-generator/src/commanding_system.py create mode 100644 apps/xtce-generator/src/commands.xml create mode 100644 apps/xtce-generator/src/flight_system.py create mode 100644 apps/xtce-generator/src/rocket.xml create mode 100644 apps/xtce-generator/src/telemetry_system.py create mode 100644 apps/xtce-generator/src/xtce_generator.py diff --git a/Tiltfile b/Tiltfile index 79acd40..6f09ba4 100644 --- a/Tiltfile +++ b/Tiltfile @@ -14,7 +14,6 @@ local_resource( resource_deps=['backend', 'yamcs-effect', 'yamcs-atom'] ) - local_resource( 'backend', serve_cmd="cd apps/backend && mvn yamcs:run", @@ -49,7 +48,7 @@ local_resource( } .\\venv\\Scripts\\pip install -r requirements.txt - .\\venv\\Scripts\\python converter.py --output "../backend/src/main/yamcs/mdb/rocket.xml" + .\\venv\\Scripts\\python ./src/xtce_generator.py --output-telemetry-xml "../../backend/src/main/yamcs/mdb/rocket.xml" --output-commanding-xml "../../backend/src/main/yamcs/mdb/commands.xml" ''' or ''' set -e @@ -60,15 +59,14 @@ local_resource( fi ./venv/bin/pip install -r requirements.txt - ./venv/bin/python converter.py --output "../backend/src/main/yamcs/mdb/rocket.xml" + ./venv/bin/python ./src/xtce_generator.py --output-telemetry-xml "../backend/src/main/yamcs/mdb/rocket.xml" --output-commanding-xml "../backend/src/main/yamcs/mdb/commands.xml" ''', deps=[ "requirements.txt", - "converter.py", + "xtce_generator.py", ] ) - local_resource( 'yamcs-effect', serve_cmd="pnpm turbo dev --filter @mrt/yamcs-effect", diff --git a/apps/backend/src/main/java/org/yamcs/mrt/AstraCommandLink.java b/apps/backend/src/main/java/org/yamcs/mrt/AstraCommandLink.java index 26afa59..7f9e9b1 100644 --- a/apps/backend/src/main/java/org/yamcs/mrt/AstraCommandLink.java +++ b/apps/backend/src/main/java/org/yamcs/mrt/AstraCommandLink.java @@ -10,6 +10,7 @@ import org.yamcs.YConfiguration; import org.yamcs.YamcsServer; import org.yamcs.CommandOption.CommandOptionType; +import org.yamcs.cmdhistory.CommandHistoryPublisher; import org.yamcs.cmdhistory.CommandHistoryPublisher.AckStatus; import org.yamcs.commanding.Acknowledgment; import org.yamcs.commanding.ActiveCommand; @@ -61,7 +62,7 @@ public class AstraCommandLink extends AbstractTcDataLink { // time. We store each command and the devices it was sent to. private final AtomicInteger currentCommandId = new AtomicInteger(1); - private Map> commandToDeviceMap = new HashMap<>(); + private Map> commandToDeviceMap = new HashMap<>(); private Map commandToPreparedMap = new HashMap<>(); @Override @@ -201,6 +202,8 @@ public boolean sendCommand(PreparedCommand preparedCommand) { this.commandHistoryPublisher.publish(preparedCommand.getCommandId(), "TX_Devices", String.join(",", devices)); + this.commandToDeviceMap.put(seqNum, devices); + for (var device : devices) { try { client.publish(device + "/commands", msg, null, new IMqttActionListener() { @@ -257,16 +260,33 @@ private void handleMetadata(String deviceName, MqttMessage message) { } - // FIX: Its possible that if there are two radios on the same - // frequency (i.e. pad & cs) then the ack will be sent twice. - public void handleFCAck(int cmd_id, String frequency) { + // FIX: Currently the only way we reigster the FC ack is if the same + // radio that sent it recieved it. Reciving FC acks should be + // radio agnostic, but we don't do that right now + public void handleFCAck(int cmd_id, String frequency, String deviceName) { PreparedCommand command = commandToPreparedMap.get(cmd_id); + Collection devices = commandToDeviceMap.get(cmd_id); + + if (devices.contains(deviceName)) { + commandHistoryPublisher.publishAck( + command.getCommandId(), + "fc_" + frequency, + timeService.getMissionTime(), + AckStatus.OK); + + devices.remove(deviceName); + } + + // If both FCs have ack'd then the command + // is complete + if (devices.size() == 0) { + commandHistoryPublisher.publishAck( + command.getCommandId(), + CommandHistoryPublisher.CommandComplete_KEY, + timeService.getMissionTime(), + AckStatus.OK); + } - commandHistoryPublisher.publishAck( - command.getCommandId(), - "fc_" + frequency, - timeService.getMissionTime(), - AckStatus.OK); } private void handleAck(String deviceName, MqttMessage message) { diff --git a/apps/backend/src/main/java/org/yamcs/mrt/astra/RadiosLink.java b/apps/backend/src/main/java/org/yamcs/mrt/astra/RadiosLink.java index aa0c80a..a58eeb7 100644 --- a/apps/backend/src/main/java/org/yamcs/mrt/astra/RadiosLink.java +++ b/apps/backend/src/main/java/org/yamcs/mrt/astra/RadiosLink.java @@ -1,31 +1,16 @@ package org.yamcs.mrt.astra; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.yamcs.ConfigurationException; import org.yamcs.YConfiguration; import org.yamcs.YamcsServer; -import org.yamcs.client.Acknowledgment; -import org.yamcs.cmdhistory.Attribute; -import org.yamcs.cmdhistory.CommandHistoryPublisher; -import org.yamcs.cmdhistory.CommandHistoryPublisher.AckStatus; -import org.yamcs.commanding.PreparedCommand; import org.yamcs.mrt.AstraCommandLink; import org.yamcs.mrt.DefaultMqttToTmPacketConverter; import org.yamcs.mrt.MqttToTmPacketConverter; -import org.yamcs.parameter.UInt32Value; -import org.yamcs.protobuf.YamcsInstance; import org.yamcs.tctm.Link; -import org.yamcs.protobuf.Commanding.CommandId; public class RadiosLink extends AstraSubLink { MqttToTmPacketConverter tmConverter; @@ -49,111 +34,12 @@ public void init(String yamcsInstance, String linkName, YConfiguration config) tmConverter.init(yamcsInstance, linkName, config); } - // @Override - // public boolean sendCommand(PreparedCommand preparedCommand) { - // String cmdId = preparedCommand.getMetaCommand().getShortDescription(); - // - // int seqNum = commandCountCounter.getAndIncrement(); - // if (seqNum > 255) { - // commandCountCounter.set(1); - // seqNum = 1; - // } - // - // this.commandHistoryPublisher.publish(preparedCommand.getCommandId(), - // "Command_Id", cmdId); - // this.commandHistoryPublisher.publish(preparedCommand.getCommandId(), - // "Sequence_Count", seqNum); - // - // pendingCommands.put(seqNum, preparedCommand); - // - // String cmdPayload = seqNum + "," + cmdId; - // - // MqttMessage msg = new MqttMessage(cmdPayload.getBytes()); - // - // try { - // client.publish(this.deviceName + "/commands", msg); - // - // long missionTime = this.timeService.getMissionTime(); - // - // this.commandHistoryPublisher.publishAck( - // preparedCommand.getCommandId(), - // Acknowledgment.SENT, - // missionTime, - // AckStatus.OK); - // - // this.commandHistoryPublisher.publishAck( - // preparedCommand.getCommandId(), - // "Acknowledge_Radio_RX", - // missionTime, - // AckStatus.PENDING); - // - // } catch (MqttException e) { - // eventProducer.sendDistress(e.getLocalizedMessage()); - // e.printStackTrace(); - // return false; - // } - // - // return true; - // } - // - // @Override - // public void handleAck(Number cmdId, String status) { - // System.out.println("GOT ACK FOR cmdId: " + cmdId + " status: " + status); - // - // String ackType = status.substring(0, 2); - // long missionTime = this.timeService.getMissionTime(); - // - // PreparedCommand preparedCommand = pendingCommands.get(cmdId.intValue()); - // if (preparedCommand != null) { - // AckStatus ackStatus; - // if (status.contains("OK")) { - // ackStatus = AckStatus.OK; - // } else if (status.contains("BAD")) { - // ackStatus = AckStatus.NOK; - // } else { - // ackStatus = AckStatus.CANCELLED; - // } - // - // this.commandHistoryPublisher.publishAck( - // preparedCommand.getCommandId(), - // "Acknowledge_Radio_" + ackType, - // missionTime, - // ackStatus); - // - // if (ackType == "RX" && ackStatus == AckStatus.OK) { - // this.commandHistoryPublisher.publishAck( - // preparedCommand.getCommandId(), - // "Acknowledge_Radio_TX", - // missionTime, - // AckStatus.PENDING); - // } - // } else { - // System.out.println("No pending command found for cmdId: " + cmdId); - // } - // } - // - // private void handleFCAck(int commandAckId) { - // PreparedCommand command = pendingCommands.get(commandAckId); - // if (command == null) { - // eventProducer.sendCritical( - // "Recieved ACK for id \"" + commandAckId + "\" but no such command was found - // in the ground station."); - // } - // - // this.commandHistoryPublisher.publishAck( - // command.getCommandId(), - // CommandHistoryPublisher.CommandComplete_KEY, - // this.timeService.getMissionTime(), - // AckStatus.OK); - // } - @Override public void handleMqttMessage(MqttMessage message) { dataIn(1, message.getPayload().length); - // my friend encoded this in cpp, and for him - // he set the flag at bit index #1 (2nd from the right) - // can you extract it as a boolean + // This is the only point where we can access the binary packet + // In order to pass acks along to toe AstraCommandLinkClass byte flags = message.getPayload()[2]; boolean ackFlag = ((flags >> 1) & 1) == 1; if (ackFlag) { @@ -163,13 +49,12 @@ public void handleMqttMessage(MqttMessage message) { var links = YamcsServer.getServer().getInstance(this.getYamcsInstance()).getLinkManager().getLinks(); for (Link link : links) { if (link instanceof AstraCommandLink) { - ((AstraCommandLink) link).handleFCAck(commandAckId, deviceFrequency); + ((AstraCommandLink) link).handleFCAck(commandAckId, deviceFrequency, deviceName); } } } for (var tmPacket : tmConverter.convert(message)) { - tmPacket = packetPreprocessor.process(tmPacket); if (tmPacket != null) { super.processPacket(tmPacket); diff --git a/apps/backend/src/main/yamcs/mdb/commands.xml b/apps/backend/src/main/yamcs/mdb/commands.xml index 0012d57..8eac718 100644 --- a/apps/backend/src/main/yamcs/mdb/commands.xml +++ b/apps/backend/src/main/yamcs/mdb/commands.xml @@ -6,171 +6,181 @@ See https://github.com/yamcs/pymdb - - - - - - - - + ARM Recovery - + + + + + Umbilical High + + + + + DISARM Recovery - + - + + Drogue Ejection - + - + + Emergency Cancel - + - + + Emergency Stop - + - + + FDOV De-energize - + - + + FDOV Energize - + + Landed - + + Launch - + - + + Main Ejection - + + MOV Arming - + + MOV Disarming - + + Propulsion Off - + + Propulsion On - + + + + + Radio Normal TX Speed + + + + - - - - - - - - - 0 - - - + + Radio Slow TX Speed + + - + - - + + Resets the FC + - + - + + Reset Prop Valve States - + - - - - - - - - - 0 - - - + + Umbilical Low + + - + - + + Vent Valve De-energize - + - + + Vent Valve Energize - + diff --git a/apps/backend/src/main/yamcs/mdb/radio.xml b/apps/backend/src/main/yamcs/mdb/radio.xml new file mode 100644 index 0000000..5e85e73 --- /dev/null +++ b/apps/backend/src/main/yamcs/mdb/radio.xml @@ -0,0 +1,62 @@ + + + + + + dBm + + + + + + -2 + / + + + + + + + dB + + + + + + 4 + / + + + + + + + + Relative Signal Strength Indicator of the ground radio + + + + + + + + + + + + 0 + + + + + 0 + + + + + + + diff --git a/apps/backend/src/main/yamcs/mdb/rocket.xml b/apps/backend/src/main/yamcs/mdb/rocket.xml index 450dcdc..1925022 100644 --- a/apps/backend/src/main/yamcs/mdb/rocket.xml +++ b/apps/backend/src/main/yamcs/mdb/rocket.xml @@ -6,65 +6,85 @@ See https://github.com/yamcs/pymdb - + + + g + - 100 + 1000 / - - + + + + g + - 100 + 1000 / - - + + + + g + - 100 + 1000 / - - - - - - - - - - - - - - - - + + ft + + - - + + + ft + + - - + + + ft + + - - + + + mA + + + + + + V + + + + + + 10 + / + + + @@ -76,26 +96,62 @@ See https://github.com/yamcs/pymdb - + psi - - - + + + + + + + + + + + + + + + + + + + + + + bar + + - 2 - + - 400 + 1000 + / + + + + + + + dBm + + + + + + -2 / - - + + + + dB + @@ -105,7 +161,21 @@ See https://github.com/yamcs/pymdb - + + + + °C + + + + + + 100 + / + + + + @@ -149,59 +219,137 @@ See https://github.com/yamcs/pymdb - + + m + + + + + + 1000 + / + + + - + + deg + + + + + + 10000000 + / + + + - + + deg + + + + + + 10000000 + / + + + - + + s + + - + + + deg/s + + + + + + 1000 + / + + + + + + + deg/s + + + + + + 1000 + / + + + + + + + deg/s + + + + + + 1000 + / + + + + + - + - + - + - + - + - + - - - - + - + - + - + - + - + + + + @@ -211,42 +359,24 @@ See https://github.com/yamcs/pymdb - - - - - - - - - - - - - - - - - - - + psi - - + + °C - + @@ -265,7 +395,7 @@ See https://github.com/yamcs/pymdb - + °C @@ -278,79 +408,98 @@ See https://github.com/yamcs/pymdb - + - + + ft/s + + - + - + - + - - Calculated on FC + + Calculated on the FC in order to determine ejection. - - Calculated on FC + + Initalized by the flight computer when it started or restarted. - + - + - + + Current voltage of the FC battery - + - + + Calculated from sensor voltage. - + - + - + - - Calculated from sensor voltage. + + + + - + - + + Barometric pressure measured by the FC - + + Relative Signal Strength Indicator of FC radio - + - + + Temperature measured by the FC - + - + - + + + + + + + + + + @@ -372,87 +521,78 @@ See https://github.com/yamcs/pymdb The current stage the FC believes it is in. - - - - + - + - + - + - + - + - + - + - + - - Calculated from sensor voltage. + - - A.S.T.R.A. Packet Padding - - - + - + - + - - - - + - + - + - + - + - + - + + Calculated from hall state sensor voltage. - + + A.S.T.R.A. Packet Padding - + - + - + @@ -490,7 +630,7 @@ See https://github.com/yamcs/pymdb - + @@ -567,32 +707,27 @@ See https://github.com/yamcs/pymdb 0 - - - 0 - - - + 0 - + 0 - + 0 - + 0 - + 0 @@ -637,17 +772,17 @@ See https://github.com/yamcs/pymdb 0 - + 0 - + 0 - + 0 @@ -662,6 +797,16 @@ See https://github.com/yamcs/pymdb 0 + + + 0 + + + + + 0 + + @@ -813,62 +958,62 @@ See https://github.com/yamcs/pymdb 11 - + 10 - + 9 - + 8 - + 23 - + 22 - + 21 - + 20 - + 19 - + 18 - + 17 - + 16 - + 31 @@ -912,228 +1057,5 @@ See https://github.com/yamcs/pymdb - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - == - True - - - - - - - - == - True - - - - - - - - - - - - - - - - - == - True - - - - - - - - == - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/apps/frontend/src/cards/command-button/index.tsx b/apps/frontend/src/cards/command-button/index.tsx index 7d8a709..5deaa14 100644 --- a/apps/frontend/src/cards/command-button/index.tsx +++ b/apps/frontend/src/cards/command-button/index.tsx @@ -5,7 +5,12 @@ import { DataGridRow, } from "@/components/ui/data-grid"; import { makeCard } from "@/lib/cards"; -import { Result, useAtomSet, useAtomValue } from "@effect-atom/atom-react"; +import { + Result, + useAtomSet, + useAtomSuspense, + useAtomValue, +} from "@effect-atom/atom-react"; import { YamcsAtomClient } from "@mrt/yamcs-atom"; import { Cause, Schema } from "effect"; @@ -13,29 +18,6 @@ type Command = { name: string; }; -const commands: Command[] = [ - { name: "FlightComputer/arm_recovery" }, - { name: "FlightComputer/disarm_recovery" }, - { name: "FlightComputer/drogue_ejection" }, - { name: "FlightComputer/emergency_cancel" }, - { name: "FlightComputer/emergency_stop" }, - { name: "FlightComputer/fdov_de-energize" }, - { name: "FlightComputer/fdov_energize" }, - { name: "FlightComputer/landed" }, - { name: "FlightComputer/launch" }, - { name: "FlightComputer/main_ejection" }, - { name: "FlightComputer/mov_arming" }, - { name: "FlightComputer/mov_disarming" }, - { name: "FlightComputer/propulsion_off" }, - { name: "FlightComputer/propulsion_on" }, - { name: "FlightComputer/radio_set_transmit" }, - { name: "FlightComputer/reset_from_start" }, - { name: "FlightComputer/reset_prop_boards_valve_state" }, - { name: "FlightComputer/umbilical" }, - { name: "FlightComputer/vent_valve_de-energize" }, - { name: "FlightComputer/vent_valve_energize" }, -]; - export const CommandButtonCard = makeCard({ id: "command-button", name: "Command Button Card", @@ -68,6 +50,13 @@ function CommandButtonTable() { YamcsAtomClient.mutation("command", "issueCommand"), ); + const { commands } = useAtomSuspense( + YamcsAtomClient.query("mdb", "listCommands", { + path: { instance: "ground_station" }, + urlParams: {}, + }), + ).value; + return (
@@ -79,14 +68,17 @@ function CommandButtonTable() { {commands.map((command) => ( -
{command.name.split("/")[1]}
+
+ {command.longDescription ?? command.qualifiedName}{" "} + {command.shortDescription && `(${command.shortDescription})`} +