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

org.yamcs.tctm.UdpTcTmDataLink Maven / Gradle / Ivy

There is a newer version: 5.10.9
Show newest version
package org.yamcs.tctm;

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

import org.yamcs.Spec;
import org.yamcs.Spec.OptionType;
import org.yamcs.TmPacket;
import org.yamcs.YConfiguration;
import org.yamcs.actions.ActionResult;
import org.yamcs.commanding.PreparedCommand;

import com.google.gson.JsonObject;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;

/**
 * A UDP-based link that acts as a client: sending TC and receiving TM on the same socket pair.
 */
public class UdpTcTmDataLink extends AbstractTcTmParamLink {

    protected String host;
    protected int port;
    protected long initialDelay;

    private Channel channel;

    @Override
    public Spec getSpec() {
        var spec = getDefaultSpec();
        spec.addOption("host", OptionType.STRING).withRequired(true);
        spec.addOption("port", OptionType.INTEGER).withRequired(true);
        spec.addOption("initialDelay", OptionType.INTEGER);
        return spec;
    }

    @Override
    public void init(String instance, String name, YConfiguration config) {
        super.init(instance, name, config);
        host = config.getString("host");
        port = config.getInt("port");
        initialDelay = config.getLong("initialDelay", -1);
    }

    @Override
    public boolean sendCommand(PreparedCommand preparedCommand) {
        var binary = postprocess(preparedCommand);
        if (binary != null) {
            var address = (InetSocketAddress) channel.remoteAddress();
            var dgram = new DatagramPacket(Unpooled.wrappedBuffer(binary), address);
            channel.writeAndFlush(dgram);
            dataOut(1, binary.length);
            ackCommand(preparedCommand.getCommandId());
        }

        return true;
    }

    @Override
    protected Status connectionStatus() {
        if (channel == null || !channel.isActive()) {
            return Status.UNAVAIL;
        }
        return Status.OK;
    }

    @Override
    protected void doStart() {
        var eventLoopGroup = getEventLoop();
        eventLoopGroup.schedule(() -> createBootstrap(), initialDelay, TimeUnit.MILLISECONDS);
        addAction(new ChangeDestinationAction());
        notifyStarted();
    }

    @Override
    protected void doEnable() throws Exception {
        createBootstrap();
    }

    @Override
    protected void doDisable() throws Exception {
        if (channel != null) {
            channel.close();
        }
    }

    private void createBootstrap() {
        if (disabled.get()) {
            return;
        }
        if (channel != null && channel.isActive()) {
            return;
        }
        var eventLoopGroup = getEventLoop();
        var b = new Bootstrap()
                .group(eventLoopGroup)
                .channel(NioDatagramChannel.class)
                .handler(new ChannelInitializer() {

                    @Override
                    protected void initChannel(NioDatagramChannel ch) throws Exception {
                        var pipeline = ch.pipeline();
                        pipeline.addLast(new UdpTcTmDataLinkHandler(UdpTcTmDataLink.this));
                    }
                });
        var future = b.connect(host, port);
        future.addListener((ChannelFuture f) -> {
            if (f.isSuccess()) {
                log.info("Link established to {}:{}", host, port);
                channel = f.channel();
                channel.closeFuture().addListener(closeFuture -> {
                    if (isRunningAndEnabled()) {
                        log.warn("Link to {}:{} closed. Retrying in 10s", host, port);
                        eventLoopGroup.schedule(() -> createBootstrap(), 10, TimeUnit.SECONDS);
                    }
                });
            } else if (isRunningAndEnabled()) {
                log.info("Cannot establish link to {}:{}: {}. Retrying in 10s",
                        host, port, f.cause().getMessage());
                eventLoopGroup.schedule(() -> createBootstrap(), 10, TimeUnit.SECONDS);
            }
        });
    }

    @Override
    protected void doStop() {
        if (channel == null) {
            notifyStopped();
            return;
        }

        channel.close().addListener(f -> {
            if (f.isSuccess()) {
                notifyStopped();
            } else {
                notifyFailed(f.cause());
            }
        });
    }

    @Override
    public String getDetailedStatus() {
        if (isDisabled()) {
            return String.format("DISABLED (client to %s:%d)", host, port);
        } else {
            return String.format("OK, client to %s:%d", host, port);
        }
    }

    public void handleIncomingPacket(byte[] packet) {
        if (isRunningAndEnabled()) {
            var tmPacket = new TmPacket(timeService.getMissionTime(), packet);
            tmPacket.setEarthReceptionTime(timeService.getHresMissionTime());
            tmPacket = packetPreprocessor.process(tmPacket);
            if (tmPacket != null) {
                processPacket(tmPacket);
            }
            dataIn(1, packet.length);
        }
    }

    private class ChangeDestinationAction extends LinkAction {

        ChangeDestinationAction() {
            super("change-destination", "Change destination");
        }

        @Override
        public Spec getSpec() {
            var spec = new Spec();
            spec.addOption("host", OptionType.STRING)
                    .withRequired(true)
                    .withDefault(host);
            spec.addOption("port", OptionType.INTEGER)
                    .withRequired(true)
                    .withDefault(port);
            return spec;
        }

        @Override
        public void execute(Link link, JsonObject request, ActionResult result) {
            host = request.get("host").getAsString();
            port = request.get("port").getAsInt();
            log.info("Changing destination to {}:{}", host, port);

            if (isRunningAndEnabled()) {
                var ch = UdpTcTmDataLink.this.channel;
                disable();
                ch.close().addListener(f -> {
                    if (f.isSuccess()) {
                        enable();
                        result.complete();
                    } else {
                        result.completeExceptionally(f.cause());
                    }
                });
            } else {
                result.complete();
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy