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

org.yamcs.tse.InstrumentDriver Maven / Gradle / Ivy

package org.yamcs.tse;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;

import org.yamcs.YConfiguration;
import org.yamcs.tse.api.TseCommand;
import org.yamcs.utils.YObjectLoader;

public abstract class InstrumentDriver {

    private static final int DEFAULT_POLLING_INTERVAL = 20;

    protected String instrument;

    protected String commandSeparation;
    protected String responseTermination;
    protected int responseTimeout = 3000;
    protected List interceptors = new ArrayList<>();

    private int pollingInterval;

    protected Charset encoding = StandardCharsets.US_ASCII;

    public void init(String name, YConfiguration config) {
        this.instrument = name;

        if (config.containsKey("commandSeparation")) {
            commandSeparation = config.getString("commandSeparation");
        }
        if (config.containsKey("responseTermination")) {
            responseTermination = config.getString("responseTermination");
        }
        if (config.containsKey("responseTimeout")) {
            responseTimeout = config.getInt("responseTimeout");
        }

        String requestTermination = config.getString("requestTermination", getDefaultRequestTermination());
        if (requestTermination != null) {
            Map interceptorConfig = new HashMap<>();
            interceptorConfig.put(RequestTerminator.CONFIG_TERMINATION, requestTermination);
            interceptors.add(new RequestTerminator(YConfiguration.wrap(interceptorConfig)));
        }
        if (config.containsKey("interceptors")) {
            for (YConfiguration interceptorConfig : config.getConfigList("interceptors")) {
                try {
                    Interceptor interceptor = YObjectLoader.loadObject(interceptorConfig.toMap());
                    interceptors.add(interceptor);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
        }

        pollingInterval = Math.min(DEFAULT_POLLING_INTERVAL, responseTimeout);
    }

    public String getName() {
        return instrument;
    }

    public String getCommandSeparation() {
        return commandSeparation;
    }

    public String getResponseTermination() {
        return responseTermination;
    }

    public int getResponseTimeout() {
        return responseTimeout;
    }

    public List command(String command, TseCommand metadata, boolean expectResponse)
            throws IOException, TimeoutException {
        try {
            connect();
            byte[] bytes = command.getBytes();
            for (Interceptor interceptor : interceptors) {
                bytes = interceptor.interceptCommand(metadata, bytes, encoding);
            }
            write(bytes);
            if (expectResponse) {
                ResponseBuffer responseBuffer = new ResponseBuffer(getResponseTermination(), isFragmented());
                if (commandSeparation == null) {
                    byte[] response = readSingleResponse(responseBuffer);
                    if (response != null) {
                        for (Interceptor interceptor : interceptors) {
                            response = interceptor.interceptResponse(metadata, response, encoding);
                        }
                        return Arrays.asList(new String(response, encoding));
                    }
                } else { // Compound command where distinct responses are sent
                    String[] parts = command.split(commandSeparation);
                    List responses = new ArrayList<>();
                    for (String part : parts) {
                        if (part.contains("?") || part.contains("!")) {
                            byte[] response = readSingleResponse(responseBuffer);
                            if (response != null) {
                                for (Interceptor interceptor : interceptors) {
                                    response = interceptor.interceptResponse(metadata, response, encoding);
                                }
                                responses.add(new String(response, encoding));
                            }
                        }
                    }
                    return responses;
                }
            }

            return Collections.emptyList();
        } catch (Exception e) {
            disconnect();
            throw e;
        }
    }

    /**
     * Attemps to read a full delimited TSE response by triggering repeated read polls on the underlying transport,
     * until either a full response was assembled, or the global response timeout has been reached.
     */
    private byte[] readSingleResponse(ResponseBuffer responseBuffer) throws IOException, TimeoutException {
        byte[] response = responseBuffer.readSingleResponse();
        if (response != null) {
            return response;
        }

        long time = System.currentTimeMillis();
        long timeoutTime = time + responseTimeout;

        while (System.currentTimeMillis() < timeoutTime) {
            readAvailable(responseBuffer, pollingInterval);

            response = responseBuffer.readSingleResponse();
            if (response != null) {
                return response;
            }
        }

        // Timed out. Return whatever we have.
        response = responseBuffer.readSingleResponse(true);
        if (getResponseTermination() == null) {
            return response;
        } else {
            throw new TimeoutException(response != null
                    ? "Unterminated response: " + new String(response, encoding)
                    : null);
        }
    }

    public abstract void connect() throws IOException;

    public abstract void disconnect() throws IOException;

    public abstract void write(byte[] cmd) throws IOException;

    public abstract void readAvailable(ResponseBuffer buffer, int timeout) throws IOException;

    /**
     * Returns whether this driver may require reassembly of multiple received fragments in order to obtain a full
     * response.
     * 

* Setting this to false, will allow to have a quick response, even if there is no response termination characters. * That is, without needing to wait on timeouts. */ public abstract boolean isFragmented(); /** * Returns the driver-specific default pattern for terminating requests. This is the termination that gets used if * the user does not explicitly configure anything. *

* Return {@code null} to do no request termination. */ public abstract String getDefaultRequestTermination(); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy