package org.openlcb.can; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.openlcb.*; import org.openlcb.messages.TractionControlReplyMessage; import org.openlcb.messages.TractionControlRequestMessage; import org.openlcb.messages.TractionProxyReplyMessage; import org.openlcb.messages.TractionProxyRequestMessage; /** * Converts CAN frame messages to regular messages * and vice-versa. *

* In general, the connection is one-to-one, but * sometimes (datagram expansion) more than one frame will be * created. * * @author Bob Jacobsen Copyright 2010 * @version $Revision: 4021 $ */ public class MessageBuilder { /** * The provided AliasMap will be updated * as inbound frames are processed. */ public MessageBuilder(AliasMap map) { this.map = map; } AliasMap map; /** * Accept a frame, and convert to * a standard OpenLCB Message object. * * The returned Message is fully initialized * with content, and the original frame can * be dropped. * * The List is always returned, even if empty. */ public List processFrame(CanFrame f) { // check for special cases first if ( (f.getHeader() & 0x08000000) != 0x08000000 ) return null; // not OpenLCB frame // break into types int format = ( f.getHeader() & 0x07000000 ) >> 24; switch (format) { case 0: return processFormat0(f); case 1: return processFormat1(f); case 2: return processFormat2(f); case 3: return processFormat3(f); case 4: return processFormat4(f); case 5: return processFormat5(f); case 6: return processFormat6(f); case 7: return processFormat7(f); default: // should not happen return null; } } HashMap> datagramData = new HashMap>(); // dph HashMap> streamData = new HashMap>(); int getSourceID(CanFrame f) { return f.getHeader()&0x00000FFF; } int getMTI(CanFrame f) { return ( f.getHeader() & 0x00FFF000 ) >> 12; } EventID getEventID(CanFrame f) { return new EventID(f.getData()); } List processFormat0(CanFrame f) { // reserved return null; } class AccumulationMemo { long header; NodeID source; NodeID dest; byte[] data; public AccumulationMemo(long header, NodeID source, NodeID dest, byte[] data) { this.header = header; this.source = source; this.dest = dest; this.data = data; } public boolean equals(Object obj) { if (! (obj instanceof AccumulationMemo) ) return false; AccumulationMemo other = (AccumulationMemo)obj; if (header != other.header) return false; if ( ! source.equals(other.source)) return false; if ( ! dest.equals(other.dest)) return false; return true; } // data varies in length, not considered. public int hashCode() { return (int)(header+dest.hashCode()+source.hashCode()); } } HashMap accumulations = new HashMap(); List processFormat1(CanFrame f) { // MTI List retlist = new java.util.ArrayList(); NodeID source = map.getNodeID(getSourceID(f)); NodeID dest = null; int mti = getMTI(f); byte[] data = f.getData(); byte[] content = null; if ( ((mti&0x008) != 0) && (f.getNumDataElements() >= 2) ) { // addressed message dest = map.getNodeID( ( (f.getElement(0) << 8) + (f.getElement(1) & 0xff) ) & 0xFFF ); AccumulationMemo mnew = new AccumulationMemo(f.getHeader(), source, dest, data); // is header already in map? AccumulationMemo mold = accumulations.get(f.getHeader()); if (mold == null) { // no - start accumulation accumulations.put(f.getHeader(),mnew); mold = mnew; } else { // combine data into old one byte[] newdata = new byte[mold.data.length+mnew.data.length-2]; // skip address System.arraycopy(mold.data, 0, newdata, 0, mold.data.length); System.arraycopy(mnew.data, 2, newdata, mold.data.length, mnew.data.length-2); mold.data = newdata; } // see if final bit active if ( (f.getElement(0) & 0x10 ) != 0) { // no, accumulate return retlist; // which is null right now } // we're going to continue processing with the accumulated data data = mold.data; accumulations.remove(f.getHeader()); content = new byte[data.length-2]; System.arraycopy(data, 2, content, 0, content.length); } MessageTypeIdentifier value = MessageTypeIdentifier.get(mti); if (value == null) { // something bad happened String mtiString = "000"+Integer.toHexString(mti).toUpperCase(); mtiString = mtiString.substring(mtiString.length()-3); System.out.println(" failed to parse MTI 0x"+mtiString); return retlist; // nothing in it from this } switch (value) { case InitializationComplete: retlist.add(new InitializationCompleteMessage(source)); return retlist; case VerifyNodeIdGlobal: // check for content if (data.length >= 6) { NodeID node = new NodeID(data); retlist.add(new VerifyNodeIDNumberMessage(source, node)); } else { retlist.add(new VerifyNodeIDNumberMessage(source)); } return retlist; case VerifiedNodeId: retlist.add(new VerifiedNodeIDNumberMessage(source)); return retlist; case OptionalInteractionRejected: { int d2 = data.length >= 3 ? f.getElement(2) : 0; int d3 = data.length >= 4 ? f.getElement(3) : 0; int d4 = data.length >= 5 ? f.getElement(4) : 0; int d5 = data.length >= 6 ? f.getElement(5) : 0; int retmti = ((d2&0xff)<<8) | (d3&0xff); int code = ((d4&0xff)<<8) | (d5&0xff);; retlist.add(new OptionalIntRejectedMessage(source, dest,retmti,code)); return retlist; } case ProtocolSupportInquiry: retlist.add(new ProtocolIdentificationRequestMessage(source, dest)); return retlist; case ProtocolSupportReply: retlist.add(new ProtocolIdentificationReplyMessage(source, dest, f.dataAsLong())); return retlist; case TractionControlRequest: retlist.add(new TractionControlRequestMessage(source, dest, content)); return retlist; case TractionControlReply: retlist.add(new TractionControlReplyMessage(source, dest, content)); return retlist; case TractionProxyRequest: retlist.add(new TractionProxyRequestMessage(source, dest, content)); return retlist; case TractionProxyReply: retlist.add(new TractionProxyReplyMessage(source, dest, content)); return retlist; case IdentifyConsumer: retlist.add(new IdentifyConsumersMessage(source, getEventID(f))); return retlist; case ConsumerIdentifiedUnknown: retlist.add(new ConsumerIdentifiedMessage(source, getEventID(f), EventState.Unknown)); return retlist; case ConsumerIdentifiedValid: retlist.add(new ConsumerIdentifiedMessage(source, getEventID(f), EventState.Valid)); return retlist; case ConsumerIdentifiedInvalid: retlist.add(new ConsumerIdentifiedMessage(source, getEventID(f), EventState.Invalid)); return retlist; case IdentifyProducer: retlist.add(new IdentifyProducersMessage(source, getEventID(f))); return retlist; case ProducerIdentifiedUnknown: retlist.add(new ProducerIdentifiedMessage(source, getEventID(f), EventState.Unknown)); return retlist; case ProducerIdentifiedValid: retlist.add(new ProducerIdentifiedMessage(source, getEventID(f), EventState.Valid)); return retlist; case ProducerIdentifiedInvalid: retlist.add(new ProducerIdentifiedMessage(source, getEventID(f), EventState.Invalid)); return retlist; case ProducerConsumerEventReport: retlist.add(new ProducerConsumerEventReportMessage(source, getEventID(f))); return retlist; case IdentifyEventsAddressed: retlist.add(new IdentifyEventsMessage(source, dest)); return retlist; case LearnEvent: retlist.add(new LearnEventMessage(source, getEventID(f))); return retlist; case SimpleNodeIdentInfoRequest: retlist.add(new SimpleNodeIdentInfoRequestMessage(source, dest)); return retlist; case SimpleNodeIdentInfoReply: retlist.add(new SimpleNodeIdentInfoReplyMessage(source, dest, content)); return retlist; case DatagramReceivedOK: retlist.add(new DatagramAcknowledgedMessage(source,dest)); return retlist; case DatagramRejected: retlist.add(new DatagramRejectedMessage(source,dest,(int)f.dataAsLong())); return retlist; // dph: add all stream messages reply and proceed. case StreamInitiateRequest: retlist.add(new StreamInitiateRequestMessage(source,dest,content[2]<<8+content[3],content[4], content[5])); return retlist; case StreamInitiateReply: retlist.add(new StreamInitiateReplyMessage(source,dest,content[0]<<8+content[1],content[2], content[3])); return retlist; // case StreamData is Format 7 case StreamDataProceed: retlist.add(new StreamDataProceedMessage(source,dest,content[2], content[3])); return retlist; case StreamDataComplete: retlist.add(new StreamDataCompleteMessage(source,dest,content[2], content[3])); return retlist; default: System.out.println(String.format(" received unhandled MTI 0x%03X: %s",mti, value .toString())); return null; } } List processFormat2(CanFrame f) { // datagram only-segment NodeID source = map.getNodeID(getSourceID(f)); List list = datagramData.get(source); if (list == null) { list = new ArrayList(); // don't need to put it back, as we're doing just one } else { // this is actually an error, datagram already in process for only-segment } for (int i = 0; i < f.getNumDataElements(); i++) { list.add(f.getElement(i)); } // done, forward int[] data = new int[list.size()]; for (int i=0; i retlist = new java.util.ArrayList(); NodeID dest = map.getNodeID( (f.getHeader() & 0x00FFF000) >> 12); retlist.add(new DatagramMessage(source, dest, data)); return retlist; } List processFormat3(CanFrame f) { // datagram first-segment NodeID source = map.getNodeID(getSourceID(f)); List list = datagramData.get(source); if (list == null) { list = new ArrayList(); datagramData.put(source, list); } else { // this is actually an error, datagram already in process for only-segment } for (int i = 0; i < f.getNumDataElements(); i++) { list.add(f.getElement(i)); } return null; } List processFormat4(CanFrame f) { // datagram middle-segment NodeID source = map.getNodeID(getSourceID(f)); List list = datagramData.get(source); if (list == null) { // this is actually an error, should be already started list = new ArrayList(); datagramData.put(source, list); } for (int i = 0; i < f.getNumDataElements(); i++) { list.add(f.getElement(i)); } return null; } List processFormat5(CanFrame f) { // datagram last NodeID source = map.getNodeID(getSourceID(f)); List list = datagramData.get(source); if (list == null) { list = new ArrayList(); } for (int i = 0; i < f.getNumDataElements(); i++) { list.add(f.getElement(i)); } datagramData.put(source, null); // not accumulating any more int[] data = new int[list.size()]; for (int i=0; i retlist = new java.util.ArrayList(); NodeID dest = map.getNodeID( (f.getHeader() & 0x00FFF000) >> 12); retlist.add(new DatagramMessage(source, dest, data)); return retlist; } List processFormat6(CanFrame f) { // reserved return null; } List processFormat7(CanFrame f) { // stream data NodeID source = map.getNodeID(getSourceID(f)); int bufSize = 64; // need to define this !!!!!!!!!!!!!!!!!!!!!!!!!! List list = streamData.get(source); if (list == null) { list = new ArrayList(); } int n = Math.min(bufSize, f.getNumDataElements()); if(n < bufSize) { // won't fill buffer, so add all for (int i = 0; i < f.getNumDataElements(); i++) list.add(f.getElement(i)); return null; } else { // got a full buffer, fill it and send it on for (int i = 0; i < n; i++) list.add(f.getElement(i)); int[] data = new int[list.size()]; for(int i=0; i retlist = new java.util.ArrayList(); NodeID dest = map.getNodeID( (f.getHeader() & 0x00FFF000) >> 12); //retlist.add(new DatagramMessage(source, dest, data)); retlist.add(new StreamDataSendMessage(source, dest, data)); // make a new List and fill it with the rest of received data list = new ArrayList(); for (int i=n; i processMessage(Message msg) { FrameBuilder f = new FrameBuilder(); return f.convert(msg); } private class FrameBuilder extends org.openlcb.MessageDecoder { /** * Catches messages that are not explicitly * handled and throws an error */ @Override protected void defaultHandler(Message msg, Connection sender) { throw new java.lang.NoSuchMethodError("no handler for Message: "+msg.toString()); } List retlist; List convert(Message msg) { retlist = new java.util.ArrayList(); // Uses the double dispatch mechanism built into Message put(msg, null); // no Connection needed return retlist; } private void handleAddressedPayloadMessage(AddressedPayloadMessage msg, Connection sender) { byte[] payload = msg.getPayload(); if (payload == null) { payload = new byte[0]; } for (int i = 0; i < Math.max(1, payload.length); i += 6) { // Note that the order of these calls are carefully chosen so that internally // OpenLcbCanFrame does not overwrite parts of the payload with each other. OpenLcbCanFrame f = new OpenLcbCanFrame(map.getAlias(msg.getSourceNodeID())); f.setOpenLcbMTI(msg.getEMTI().mti()); int thislen = Math.min(6, payload.length - i); byte[] data = new byte[thislen + 2]; System.arraycopy(payload, i, data, 2, thislen); f.setData(data); f.setDestAlias(map.getAlias(msg.getDestNodeID())); f.setContinuation(i == 0, (i + 6 >= payload.length)); retlist.add(f); } } /** * Handle "Initialization Complete" message */ @Override public void handleInitializationComplete(InitializationCompleteMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setInitializationComplete(map.getAlias(msg.getSourceNodeID()), msg.getSourceNodeID()); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Verified Node ID Number" message */ public void handleVerifiedNodeIDNumber(VerifiedNodeIDNumberMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setVerifiedNID(msg.getSourceNodeID()); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Verify Node ID Number" message */ @Override public void handleVerifyNodeIDNumber(VerifyNodeIDNumberMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setVerifyNID(msg.getSourceNodeID()); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); if (msg.getContent() != null) f.setData(msg.getContent().getContents()); retlist.add(f); } /** * Handle "Protocol Identification Inquiry (Request)" message */ public void handleProtocolIdentificationRequest(ProtocolIdentificationRequestMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.ProtocolSupportInquiry.mti()); f.setDestAlias(map.getAlias(msg.getDestNodeID())); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Producer/Consumer Event Report" message */ @Override public void handleProducerConsumerEventReport(ProducerConsumerEventReportMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setPCEventReport(msg.getEventID()); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Identify Consumers" message */ @Override public void handleIdentifyConsumers(IdentifyConsumersMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.IdentifyConsumer.mti()); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); f.loadFromEid(msg.getEventID()); retlist.add(f); } /** * Handle "Consumer Identified" message */ @Override public void handleConsumerIdentified(ConsumerIdentifiedMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(msg.getEventState().getConsumerIdentifierMti().mti()); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); f.loadFromEid(msg.getEventID()); retlist.add(f); } /** * Handle "Identify Producers" message */ @Override public void handleIdentifyProducers(IdentifyProducersMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.IdentifyProducer.mti()); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); f.loadFromEid(msg.getEventID()); retlist.add(f); } /** * Handle "Producer Identified" message */ @Override public void handleProducerIdentified(ProducerIdentifiedMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(msg.getEventState().getProducerIdentifierMti().mti()); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); f.loadFromEid(msg.getEventID()); retlist.add(f); } /** * Handle "Identify Event" message */ @Override public void handleIdentifyEvents(IdentifyEventsMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.IdentifyEventsGlobal.mti()); f.setDestAlias(map.getAlias(msg.getDestNodeID())); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Learn Event" message */ @Override public void handleLearnEvent(LearnEventMessage msg, Connection sender){ defaultHandler(msg, sender); } @Override /** * Handle "Traction Control Request" message */ public void handleTractionControlRequest(TractionControlRequestMessage msg, Connection sender) { handleAddressedPayloadMessage(msg, sender); } /** * Handle "Traction Control Reply" message */ public void handleTractionControlReply(TractionControlReplyMessage msg, Connection sender) { handleAddressedPayloadMessage(msg, sender); } /** * Handle "Traction Proxy Request" message */ public void handleTractionProxyRequest(TractionProxyRequestMessage msg, Connection sender) { handleAddressedPayloadMessage(msg, sender); } /** * Handle "Traction Proxy Reply" message */ public void handleTractionProxyReply(TractionProxyReplyMessage msg, Connection sender) { handleAddressedPayloadMessage(msg, sender); } /** * Handle "Simple Node Ident Info Request" message */ @Override public void handleSimpleNodeIdentInfoRequest(SimpleNodeIdentInfoRequestMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.SimpleNodeIdentInfoRequest.mti()); f.setDestAlias(map.getAlias(msg.getDestNodeID())); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Datagram" message */ @Override public void handleDatagram(DatagramMessage msg, Connection sender){ // must loop over data to send 8 byte chunks int remains = msg.getData().length; int j = 0; boolean first = true; // always sends at least one datagram, even with zero bytes do { int size = Math.min(8, remains); int[] data = new int[size]; for (int i = 0; i 0); } /** * Handle "Datagram Rejected" message */ @Override public void handleDatagramRejected(DatagramRejectedMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.DatagramRejected.mti()); f.setData(new byte[]{(byte)0, (byte)0, (byte)((msg.getCode()>>8)&0xFF), (byte)(msg.getCode()&0xFF)}); f.setDestAlias(map.getAlias(msg.getDestNodeID())); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Datagram Acknowledged" message */ @Override public void handleDatagramAcknowledged(DatagramAcknowledgedMessage msg, Connection sender){ OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.DatagramReceivedOK.mti()); f.setDestAlias(map.getAlias(msg.getDestNodeID())); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Stream Init Request" message */ //final int STREAMBUFFERSIZE 10; @Override public void handleStreamInitiateRequest(StreamInitiateRequestMessage msg, Connection sender){ //defaultHandler(msg, sender); // dph OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.StreamInitiateRequest.mti()); // dest(2), maxBuffer(2),flags(2),sourceStream, reserved f.setData(new byte[]{ (byte)0, (byte)0, 0, 64, 0, 0, msg.getSourceStreamID(), (byte)0 }); f.setDestAlias(map.getAlias(msg.getDestNodeID())); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Stream Init Reply" message */ @Override public void handleStreamInitiateReply(StreamInitiateReplyMessage msg, Connection sender){ //defaultHandler(msg, sender); // dph OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.StreamInitiateReply.mti()); // dest(2), maxBufferSize(2), flags(2),sourceStream, destinationStream f.setData(new byte[]{ (byte)0, (byte)0, 0, 64, msg.getSourceStreamID(), msg.getDestinationStreamID() } ); f.setDestAlias(map.getAlias(msg.getDestNodeID())); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Stream Data Send" message */ @Override public void handleStreamDataSend(StreamDataSendMessage msg, Connection sender){ // dph // must loop over data to send 8 byte chunks // do we need to check the destinationStreamID???????????????????? int remains = msg.getData().length; int j = 0; // always sends at least one stream message, even with zero bytes ??????? do { int size = Math.min(8, remains); byte[] data = new byte[size]; for (int i = 0; i 0); } /** * Handle "Stream Data Proceed" message */ @Override public void handleStreamDataProceed(StreamDataProceedMessage msg, Connection sender){ // dph OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.StreamDataProceed.mti()); // sourceStream, destinationStream, flags(2) f.setData(new byte[]{(byte)0, (byte)0, msg.getSourceStreamID(), msg.getDestinationStreamID(), 0, 0 }); f.setDestAlias(map.getAlias(msg.getDestNodeID())); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } /** * Handle "Stream Data Complete" message */ @Override public void handleStreamDataComplete(StreamDataCompleteMessage msg, Connection sender){ // dph OpenLcbCanFrame f = new OpenLcbCanFrame(0x00); f.setOpenLcbMTI(MessageTypeIdentifier.StreamDataComplete.mti()); // sourceStream, destinationStream, flags(2) f.setData(new byte[]{(byte)0, (byte)0, msg.getSourceStreamID(), msg.getDestinationStreamID(), 0, 0 }); f.setDestAlias(map.getAlias(msg.getDestNodeID())); f.setSourceAlias(map.getAlias(msg.getSourceNodeID())); retlist.add(f); } } }