001package jmri.jmrix.dccpp; 002 003import java.nio.charset.StandardCharsets; 004import org.slf4j.Logger; 005import org.slf4j.LoggerFactory; 006 007/** 008 * Converts Stream-based I/O to/from DCC++ messages. The "DCCppInterface" side 009 * sends/receives DCCppMessage objects. The connection to a DCCppPortController 010 * is via a pair of *Streams, which then carry sequences of characters for 011 * transmission. 012 * <p> 013 * Messages come to this via the main GUI thread, and are forwarded back to 014 * listeners in that same thread. Reception and transmission are handled in 015 * dedicated threads by RcvHandler and XmtHandler objects. Those are internal 016 * classes defined here. The thread priorities are: 017 * <ul> 018 * <li> RcvHandler - at highest available priority 019 * <li> XmtHandler - down one, which is assumed to be above the GUI 020 * <li> (everything else) 021 * </ul> 022 * 023 * @author Bob Jacobsen Copyright (C) 2001 024 * @author Mark Underwood Copyright (C) 2015 025 * 026 * Based on XNetPacketizer by Bob Jacobsen 027 */ 028public class DCCppPacketizer extends DCCppTrafficController { 029 030 public DCCppPacketizer(DCCppCommandStation pCommandStation) { 031 super(pCommandStation); 032 // The instance method (from DCCppTrafficController) is deprecated. 033 // But for the moment we need to make sure we set the static 034 // self variable, and the instance method does this for us in a 035 // static way (which makes spotbugs happy). 036 //instance(); 037 log.debug("DCCppPacketizer created"); 038 } 039 040// The methods to implement the DCCppInterface 041 042 /** 043 * Forward a preformatted DCCppMessage to the actual interface. 044 * 045 * The message is converted to a byte array and queue for transmission 046 * 047 * @param m Message to send 048 * @param reply Listener to notify when the reply to the message is received 049 */ 050 //TODO: Can this method be folded back up into the parent 051 // DCCppTrafficController class? 052 @Override 053 public void sendDCCppMessage(DCCppMessage m, DCCppListener reply) { 054 if (m.length() != 0) { 055 log.debug("Adding '{}' to send queue", m); 056 sendMessage(m, reply); 057 // why the next line? 058 // https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#yield-- 059 // states "It is rarely appropriate to use this method" 060 java.lang.Thread.yield(); 061 } 062 } 063 064 /** 065 * Add header to the outgoing byte stream. 066 * 067 * @param msg The output byte stream 068 * @param m ignored 069 * @return next location in the stream to fill 070 */ 071 //TODO: Can this method be folded back up into the parent 072 // DCCppTrafficController class? 073 @Override 074 protected int addHeaderToOutput(byte[] msg, jmri.jmrix.AbstractMRMessage m) { 075 if (log.isTraceEnabled()) { 076 log.trace("Appending '<' to start of outgoing message. msg length = {}", msg.length); 077 } 078 msg[0] = (byte) '<'; 079 return 1; 080 } 081 082 // public void startThreads() { 083 // Doesn't do anything generically. 084 // Most Packetizers won't do anything. The TCP 085 //} 086 /** 087 * Add trailer to the outgoing byte stream. This version adds the checksum 088 * to the last byte. 089 * 090 * @param msg The output byte stream 091 * @param offset the first byte not yet used 092 * @param m the message to check 093 */ 094 //TODO: Can this method be folded back up into the parent 095 // DCCppTrafficController class? 096 @Override 097 protected void addTrailerToOutput(byte[] msg, int offset, jmri.jmrix.AbstractMRMessage m) { 098 if (log.isTraceEnabled()) { 099 log.trace("aTTO offset = {} message = {} msg length = {}", offset, m, msg.length); 100 } 101 if (m.getNumDataElements() == 0) { 102 return; 103 } 104 //msg[offset - 1] = (byte) m.getElement(m.getNumDataElements() - 1); 105 msg[offset] = '>'; 106 if (log.isTraceEnabled()) { 107 log.trace("finished string = {}", new String(msg, StandardCharsets.UTF_8)); 108 } 109 } 110 111 /** 112 * Check to see if PortController object can be sent to. returns true if 113 * ready, false otherwise May throw an Exception. 114 */ 115 @Override 116 public boolean portReadyToSend(jmri.jmrix.AbstractPortController p) { 117 if (p instanceof DCCppPortController && ((DCCppPortController) p).okToSend()) { 118 ((DCCppPortController) p).setOutputBufferEmpty(false); 119 return true; 120 } else { 121 log.warn("DCC++ port not ready to send"); 122 return false; 123 } 124 } 125 126 /** 127 * Get characters from the input source, and file a message. 128 * <p> 129 * Returns only when the message is complete. 130 * <p> 131 * Only used in the Receive thread. 132 * 133 * @param msg message to fill 134 * @param istream character source. 135 * @throws java.io.IOException when presented by the input source. 136 */ 137 // TODO: Can this method be folded back up into the parent DCCppTrafficController class? 138 @Override 139 protected void loadChars(jmri.jmrix.AbstractMRReply msg, java.io.DataInputStream istream) throws java.io.IOException { 140 int i; 141 StringBuilder m = new StringBuilder(); 142 log.trace("loading characters from port"); 143 144 if (!(msg instanceof DCCppReply)) { 145 log.error("SerialDCCppPacketizer.loadChars called on non-DCCppReply msg!"); 146 return; 147 } 148 149 byte char1 = readByteProtected(istream); 150 while (char1 != '<') { 151 // Spin waiting for '<' 152 char1 = readByteProtected(istream); 153 if (char1 != '<') { 154 log.trace("skipping char: ({})", (char) char1); 155 } 156 } 157 log.trace("Message started"); 158 // Pick up the rest of the command 159 for (i = 0; i < msg.maxSize(); i++) { 160 char1 = readByteProtected(istream); 161 if (char1 == '>') { 162 log.debug("Received: '{}'", m); 163 // NOTE: Cast is OK because we checked runtime type of msg above. 164 ((DCCppReply) msg).parseReply(m.toString()); 165 return; 166 } else { 167 m.append((char) char1); 168 log.trace("msg char[{}]: {} ({})", i, char1, (char) char1); 169 } 170 } 171 log.warn("msg size {} exceeded before end of msg char '>' encountered.", msg.maxSize()); 172 log.warn("msg truncated to: '{}'", m); 173 } 174 175 private final static Logger log = LoggerFactory.getLogger(DCCppPacketizer.class); 176 177}