All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yamcs.cascading.YamcsTcLink Maven / Gradle / Ivy

There is a newer version: 5.10.7
Show newest version
package org.yamcs.cascading;

import static org.yamcs.cmdhistory.CommandHistoryPublisher.AcknowledgeSent_KEY;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

import org.yamcs.YConfiguration;
import org.yamcs.YamcsServer;
import org.yamcs.client.Command;
import org.yamcs.client.CommandListener;
import org.yamcs.client.CommandSubscription;
import org.yamcs.client.YamcsClient;
import org.yamcs.client.base.WebSocketClient;
import org.yamcs.client.mdb.MissionDatabaseClient;
import org.yamcs.client.processor.ProcessorClient;
import org.yamcs.client.processor.ProcessorClient.CommandBuilder;
import org.yamcs.cmdhistory.CommandHistoryPublisher;
import org.yamcs.cmdhistory.CommandHistoryPublisher.AckStatus;
import org.yamcs.commanding.ArgumentValue;
import org.yamcs.commanding.PreparedCommand;
import org.yamcs.protobuf.Commanding.CommandHistoryAttribute;
import org.yamcs.protobuf.Commanding.CommandHistoryEntry;
import org.yamcs.protobuf.Commanding.CommandId;
import org.yamcs.protobuf.Mdb.ArgumentAssignmentInfo;
import org.yamcs.protobuf.Mdb.ArgumentInfo;
import org.yamcs.protobuf.Mdb.CommandInfo;
import org.yamcs.protobuf.SubscribeCommandsRequest;
import org.yamcs.protobuf.Yamcs.Value;
import org.yamcs.tctm.AbstractTcDataLink;
import org.yamcs.tctm.AggregatedDataLink;
import org.yamcs.utils.ValueUtility;
import org.yamcs.xtce.Argument;

public class YamcsTcLink extends AbstractTcDataLink {
    YamcsLink parentLink;
    private CommandSubscription cmdSubscription;
    private ProcessorClient procClient;
    private MissionDatabaseClient mdbClient;
    private String cmdOrigin;
    private boolean failCommandIfNoMappingMatches;

    private Set keepUpstreamAcks = new HashSet<>();
    private ArrayList commandMapDataList = new ArrayList<>();
    Map sentCommands = new ConcurrentHashMap<>();
    Map upstreamCmdCache = new ConcurrentHashMap<>();
    private AtomicInteger tcCount = new AtomicInteger();

    public YamcsTcLink(YamcsLink parentLink) {
        this.parentLink = parentLink;
    }

    public void init(String instance, String name, YConfiguration config) {
        super.init(instance, name, config);
        this.cmdOrigin = YamcsServer.getServer().getServerId() + "-" + instance + "-" + this.linkName;
        List l;
        if (config.containsKey("keepUpstreamAcks")) {
            l = config.getList("keepUpstreamAcks");
        } else {
            l = List.of(CommandHistoryPublisher.CcsdsSeq_KEY);
        }
        if (config.containsKey("commandMapping")) {
            List commandMapConfigList = config.getConfigList("commandMapping");
            commandMapConfigList.forEach(conf -> commandMapDataList.add(new CommandMapData(conf)));
        } else {
            // add default direct command mapping.
            log.info("Using default config for Cascading command mapping");
            commandMapDataList.add(new CommandMapData());
        }
        failCommandIfNoMappingMatches = config.getBoolean("failCommandIfNoMappingMatches");

        keepUpstreamAcks = new HashSet<>(l);
    }

    @Override
    public boolean sendCommand(PreparedCommand pc) {
        for (CommandMapData data : commandMapDataList) {
            if (data.getCommandType() == CommandMapData.CommandType.DEFAULT) {
                return sendDirectCommand(pc, data);
            }

            String pcfqn = pc.getMetaCommand().getQualifiedName();
            String pcCommandPath;
            if (data.getLocalPath().endsWith("/")) {
                pcCommandPath = pcfqn.substring(0, pcfqn.lastIndexOf("/") + 1);
            } else {
                pcCommandPath = pcfqn;
            }
            if (pcCommandPath.startsWith(data.getLocalPath())) {
                switch (data.getCommandType()) {
                case DIRECT:
                    return sendDirectCommand(pc, data);
                case EMBEDDED_BINARY:
                    return sendEmbeddedBinaryCommand(pc, data);
                default:
                    throw new IllegalStateException();
                }
            }
        }

        if (failCommandIfNoMappingMatches) {
            failedCommand(pc.getCommandId(), "No command mapping matched the command");
            return true;
        } else {
            return false;
        }
    }

    private boolean sendEmbeddedBinaryCommand(PreparedCommand pc, CommandMapData data) {
        CommandInfo upstreamCmd = getUpstreamCmd(data.getUpstreamPath());
        if (upstreamCmd == null) {
            String msg = "Cannot send the command because upstream command '" + data.getUpstreamPath()
                    + "' is not available";
            failedCommand(pc.getCommandId(), msg);
            log.warn(msg);
            return true;
        }
        CommandBuilder cb = procClient.prepareCommand(data.getUpstreamPath());
        cb.withOrigin(cmdOrigin);
        long count = tcCount.getAndIncrement();
        cb.withSequenceNumber((int) count);

        sentCommands.put(cmdOrigin + "-" + count, pc);

        if (pc.getComment() != null) {
            cb.withComment(pc.getComment());
        }
        List args = getUpstreamArguments(upstreamCmd);
        boolean found = false;
        for (ArgumentInfo entry : args) {
            if (entry.getName().equals(data.getUpstreamArgumentName())) {
                found = true;
                cb.withArgument(data.getUpstreamArgumentName(), pc.getBinary());
            } else if (!entry.hasInitialValue()) {
                log.warn("The upstream command requires a value also for argument '{}'", entry.getName());
            }
        }
        if (!found) {
            String msg = "Cannot send the command because upstream argument '" + data.getUpstreamArgumentName()
                    + "' was not found";
            failedCommand(pc.getCommandId(), msg);
            log.warn(msg);
            return true;
        }

        // we take the time now because after the command is issued, the current time will be after the upstream
        // Queued/Released timestamps
        long time = getCurrentTime();
        var cf = cb.issue();
        var size = cb.getSizeOfTheLastCommandIssued();
        cf.whenComplete((c, t) -> {
            if (t != null) {
                log.warn("Error sending command ", t);
                failedCommand(pc.getCommandId(), t.getMessage());
            } else {
                dataOut(1, size);
                commandHistoryPublisher.publishAck(pc.getCommandId(), AcknowledgeSent_KEY, time, AckStatus.OK);
            }
        });
        return true;
    }

    private boolean sendDirectCommand(PreparedCommand pc, CommandMapData data) {
        String upstreamCmdName;
        String pcfqn = pc.getMetaCommand().getQualifiedName();


        if (data.getCommandType() == CommandMapData.CommandType.DEFAULT) {
            upstreamCmdName = pcfqn;
        } else {
            String upstreamCmdPath = data.getUpstreamPath();
            if (upstreamCmdPath.endsWith("/")) {
                upstreamCmdName = upstreamCmdPath + pcfqn.substring(data.getLocalPath().length());
            } else {
                upstreamCmdName = upstreamCmdPath;
            }
        }

        CommandInfo upstreamCmd = getUpstreamCmd(upstreamCmdName);

        if (upstreamCmd == null) {
            String msg = "Cannot send the command because upstream command definition is not available";
            failedCommand(pc.getCommandId(), msg);
            log.warn(msg);
            return true;
        }

        CommandBuilder cb = procClient.prepareCommand(upstreamCmd.getQualifiedName());
        cb.withOrigin(cmdOrigin);
        long count = tcCount.getAndIncrement();
        cb.withSequenceNumber((int) count);

        sentCommands.put(cmdOrigin + "-" + count, pc);

        if (pc.getComment() != null) {
            cb.withComment(pc.getComment());
        }
        List reqArgs = getUpstreamArguments(upstreamCmd);

        for (Entry entry : pc.getArgAssignment().entrySet()) {
            String argName = entry.getKey().getName();

            if (reqArgs.stream().anyMatch(ai -> argName.equals(ai.getName()))) {
                // TODO aggregates/arrays
                cb.withArgument(argName, toClientValue(entry.getValue()));
            }
        }

        // we take the time now because after the command is issued, the current time will be after the upstream
        // Queued/Released timestamps
        long time = getCurrentTime();
        cb.issue().whenComplete((c, t) -> {
            if (t != null) {
                log.warn("Error sending command ", t);
                failedCommand(pc.getCommandId(), t.getMessage());
            } else {
                commandHistoryPublisher.publishAck(pc.getCommandId(), AcknowledgeSent_KEY, time, AckStatus.OK);
            }
        });

        return true;
    }

    private Object toClientValue(ArgumentValue value) {
        return ValueUtility.getYarchValue(value.getEngValue());
    }

    // retrieve the arguments which can be sent in the upstream command
    private List getUpstreamArguments(CommandInfo upstreamCmd) {
        Set assignedArgs = new HashSet<>();
        CommandInfo ci = upstreamCmd;
        while (true) {
            for (ArgumentAssignmentInfo aai : ci.getArgumentAssignmentList()) {
                assignedArgs.add(aai.getName());
            }
            if (ci.hasBaseCommand()) {
                ci = ci.getBaseCommand();
            } else {
                break;
            }
        }

        ci = upstreamCmd;
        List reqArgs = new ArrayList<>();
        while (true) {
            for (ArgumentInfo ai : ci.getArgumentList()) {
                if (!assignedArgs.contains(ai.getName())) {
                    reqArgs.add(ai);
                }
            }
            if (ci.hasBaseCommand()) {
                ci = ci.getBaseCommand();
            } else {
                break;
            }
        }
        return reqArgs;
    }


    private CommandInfo getUpstreamCmd(String upstreamCmdName) {
        CommandInfo cinfo = upstreamCmdCache.get(upstreamCmdName);
        if (cinfo == null) {
            try {
                log.debug("Retrieving information about command {} from upstream", upstreamCmdName);
                cinfo = mdbClient.getCommand(upstreamCmdName).get();
            } catch (InterruptedException | ExecutionException e) {
                log.warn("Failed to retrieve command definition " + upstreamCmdName + " from upstream: " + e);
                return null;
            }
            upstreamCmdCache.put(upstreamCmdName, cinfo);
        }
        return cinfo;
    }

    @Override
    protected void doStart() {
        if (!isEffectivelyDisabled()) {
            doEnable();
        }
        notifyStarted();
    }

    @Override
    public void doDisable() {
        if (cmdSubscription != null) {
            cmdSubscription.cancel(true);
            cmdSubscription = null;
        }
    }

    /**
     * Called when a command history update is received from the upstream server
     * 
     * @param command
     * @param cmdHistEntry
     */
    void commandUpdated(Command command, CommandHistoryEntry che) {
        PreparedCommand pc = sentCommands.get(command.getOrigin() + "-" + command.getSequenceNumber());
        if (pc != null) {
            for (CommandHistoryAttribute cha : che.getAttrList()) {
                String name = transformCommandHistoryAttributeName(cha.getName());
                publishCmdHistory(pc.getCommandId(), name, cha.getValue());
            }

        } // else TODO: should we add to command history commands not sent by us?
    }

    private String transformCommandHistoryAttributeName(String name) {
        if (keepUpstreamAcks.contains(name)) {
            return name;
        } else {
            return "yamcs<" + parentLink.getUpstreamName() + ">_" + name;
        }
    }

    private void publishCmdHistory(CommandId cmdId, String name, Value value) {
        switch (value.getType()) {
        case SINT32:
            commandHistoryPublisher.publish(cmdId, name, value.getSint32Value());
            break;
        case UINT32:
            commandHistoryPublisher.publish(cmdId, name, value.getUint32Value());
            break;
        case UINT64:
            commandHistoryPublisher.publish(cmdId, name, value.getUint64Value());
            break;
        case SINT64:
            commandHistoryPublisher.publish(cmdId, name, value.getSint64Value());
            break;
        case STRING:
            commandHistoryPublisher.publish(cmdId, name, value.getStringValue());
            break;
        case TIMESTAMP:
            commandHistoryPublisher.publish(cmdId, name, value.getTimestampValue());
            break;
        case BINARY:
            commandHistoryPublisher.publish(cmdId, name, value.getBinaryValue().toByteArray());
            break;
        default:
            log.warn("Cannot publish command history attributes of type {}", value.getType());
        }
    }

    @Override
    public void doEnable() {
        if (cmdSubscription != null && !cmdSubscription.isDone()) {
            return;
        }
        WebSocketClient wsclient = parentLink.getClient().getWebSocketClient();
        if (wsclient.isConnected()) {
            subscribeCommanding();
        }
    }

    private void subscribeCommanding() {
        YamcsClient yclient = parentLink.getClient();

        procClient = yclient.createProcessorClient(parentLink.getUpstreamInstance(), parentLink.getUpstreamProcessor());
        mdbClient = yclient.createMissionDatabaseClient(parentLink.getUpstreamInstance());
        cmdSubscription = yclient.createCommandSubscription();
        cmdSubscription.addListener(new CommandListener() {

            @Override
            public void onUpdate(Command command, CommandHistoryEntry cmdHistEntry) {
                commandUpdated(command, cmdHistEntry);
            }

            public void onError(Throwable t) {
                eventProducer.sendWarning("Got error when subscribign to commanding: " + t);
            }

            @Override
            public void onUpdate(Command command) {
            }

        });
        cmdSubscription.sendMessage(SubscribeCommandsRequest
                .newBuilder().setInstance(parentLink.getUpstreamInstance())
                .setProcessor(parentLink.getUpstreamProcessor())
                .build());
    }

    @Override
    protected Status connectionStatus() {
        Status parentStatus = parentLink.connectionStatus();
        if (parentStatus == Status.OK) {
            boolean ok = cmdSubscription != null && !cmdSubscription.isDone();
            return ok ? Status.OK : Status.UNAVAIL;
        } else {
            return parentStatus;
        }
    }

    @Override
    protected void doStop() {
        if (!isDisabled()) {
            doDisable();
        }
        notifyStopped();
    }

    @Override
    public AggregatedDataLink getParent() {
        return parentLink;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy