package org.yamcs.web.websocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yamcs.ProcessorException; import org.yamcs.Processor; import org.yamcs.protobuf.SchemaYamcs; import org.yamcs.protobuf.Web.WebSocketServerMessage.WebSocketReplyData; import org.yamcs.protobuf.Yamcs.ProtoDataType; import org.yamcs.protobuf.Yamcs.TmPacketData; import org.yamcs.tctm.TmDataLinkInitialiser; import org.yamcs.yarch.Stream; import org.yamcs.yarch.StreamSubscriber; import org.yamcs.yarch.Tuple; import org.yamcs.yarch.YarchDatabase; import com.google.protobuf.ByteString; /** * Provides realtime event subscription via web. */ public class PacketResource extends AbstractWebSocketResource { private static final Logger log = LoggerFactory.getLogger(PacketResource.class); public static final String RESOURCE_NAME = "packets"; public static final String OP_subscribe = "subscribe"; public static final String OP_unsubscribe = "unsubscribe"; private String streamName; private Stream stream; private StreamSubscriber streamSubscriber; public PacketResource(WebSocketProcessorClient client) { super(client); } @Override public WebSocketReplyData processRequest(WebSocketDecodeContext ctx, WebSocketDecoder decoder) throws WebSocketException { String op = ctx.getOperation(); if(OP_unsubscribe.equals(op)) { return unsubscribe(ctx.getRequestId()); } if(op.startsWith(OP_subscribe)) { if(streamSubscriber!=null) { throw new WebSocketException(ctx.getRequestId(), "Already subscribed to a stream"); } String[] a = op.split("\\s+"); if(a.length!=2) { throw new WebSocketException(ctx.getRequestId(), "Invalid request. Use 'subscribe <stream_name>'"); } this.streamName = a[1]; YarchDatabase ydb = YarchDatabase.getInstance(processor.getInstance()); stream = ydb.getStream(streamName); if(stream==null) { throw new WebSocketException(ctx.getRequestId(), "Invalid request. No stream named '"+streamName+"'"); } return subscribe(ctx.getRequestId()); } throw new WebSocketException(ctx.getRequestId(), "Unsupported operation '"+ctx.getOperation()+"'"); } private WebSocketReplyData subscribe(int requestId) throws WebSocketException { doUnsubscribe(); // Only one subscription at a time doSubscribe(); return toAckReply(requestId); } @Override public void switchProcessor(Processor oldProcessor, Processor newProcessor) throws ProcessorException { doUnsubscribe(); super.switchProcessor(oldProcessor, newProcessor); YarchDatabase ydb = YarchDatabase.getInstance(processor.getInstance()); stream = ydb.getStream(streamName); doSubscribe(); } private WebSocketReplyData unsubscribe(int requestId) throws WebSocketException { doUnsubscribe(); return toAckReply(requestId); } @Override public void quit() { doUnsubscribe(); } private void doSubscribe() { if (stream != null) { streamSubscriber = new StreamSubscriber() { @Override public void onTuple(Stream stream, Tuple tuple) { try { byte[] pktData = (byte[]) tuple.getColumn(TmDataLinkInitialiser.PACKET_COLUMN); long genTime = (Long) tuple.getColumn(TmDataLinkInitialiser.GENTIME_COLUMN); long receptionTime = (Long) tuple.getColumn(TmDataLinkInitialiser.RECTIME_COLUMN); int seqNumber = (Integer)tuple.getColumn(TmDataLinkInitialiser.SEQNUM_COLUMN); TmPacketData tm = TmPacketData.newBuilder().setPacket(ByteString.copyFrom(pktData)).setGenerationTime(genTime) .setReceptionTime(receptionTime).setSequenceNumber(seqNumber).build(); wsHandler.sendData(ProtoDataType.TM_PACKET, tm, SchemaYamcs.TmPacketData.WRITE); } catch (Exception e) { log.warn("got error when sending event, quitting", e); quit(); } } @Override public void streamClosed(Stream stream) { } }; stream.addSubscriber(streamSubscriber); } } private void doUnsubscribe() { if (streamSubscriber != null) { stream.removeSubscriber(streamSubscriber); } streamSubscriber = null; } }