From 70dd3218f30f4f506bbaf07cd945c3d25988033c Mon Sep 17 00:00:00 2001 From: fros4943 Date: Thu, 7 Feb 2008 14:55:35 +0000 Subject: [PATCH] esb/tr1001 interface --- .../cooja/mspmote/interfaces/TR1001Radio.java | 485 ++++++++++++++++++ .../TR1001RadioPacketConverter.java | 321 ++++++++++++ 2 files changed, 806 insertions(+) create mode 100644 tools/cooja/apps/mspsim/src/se/sics/cooja/mspmote/interfaces/TR1001Radio.java create mode 100644 tools/cooja/apps/mspsim/src/se/sics/cooja/mspmote/interfaces/TR1001RadioPacketConverter.java diff --git a/tools/cooja/apps/mspsim/src/se/sics/cooja/mspmote/interfaces/TR1001Radio.java b/tools/cooja/apps/mspsim/src/se/sics/cooja/mspmote/interfaces/TR1001Radio.java new file mode 100644 index 000000000..19695479f --- /dev/null +++ b/tools/cooja/apps/mspsim/src/se/sics/cooja/mspmote/interfaces/TR1001Radio.java @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: TR1001Radio.java,v 1.1 2008/02/07 14:55:35 fros4943 Exp $ + */ + +package se.sics.cooja.mspmote.interfaces; + +import java.util.*; +import javax.swing.*; +import java.awt.event.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.mspsim.core.*; +import se.sics.cooja.*; +import se.sics.cooja.interfaces.*; +import se.sics.cooja.mspmote.ESBMote; + +/** + * TR1001 radio interface on ESB platform. Assumes driver specifics such as + * preambles, synchbytes, GCR coding, CRC16. + * + * @author Fredrik Osterlind + */ +@ClassDescription("TR1001 Radio") +public class TR1001Radio extends Radio implements USARTListener, ByteRadio { + private static Logger logger = Logger.getLogger(TR1001Radio.class); + + /** + * Minimum delay in CPU cycles between each byte fed to USART. + */ + private static final long CYCLES_BETWEEN_BYTES = 1200; /* ~19.200 bps */ + + private ESBMote mspMote; + + private boolean radioOn = true; + + private boolean transmitting = false; + + private boolean isInterfered = false; + + private RadioEvent lastEvent = RadioEvent.UNKNOWN; + + private int lastEventTime = 0; + + private USART radioUSART = null; + + private byte[] packetToMote = null; + + private byte[] packetFromMote = null; + + /* Outgoing packet data buffer */ + private byte[] outgoingData = new byte[1024]; // TODO Decrease max size + + private int outgoingDataLength = 0; + + private int ticksSinceLastSend = -1; + + /* Incoming byte-to-packet data buffer */ + private Vector bufferedBytes = new Vector(); + + private Vector bufferedByteDelays = new Vector(); + + /* Outgoing byte data buffer */ + private byte byteFromMote = -1; + + private long transmissionStartCycles = -1; + + private long byteFromMoteDelay = -1; + + /* Incoming byte data buffer */ + private byte byteToMote = -1; + + private byte lastDeliveredByte = -1; + + private long lastDeliveredByteTimestamp = -1; + + private long lastDeliveredByteDelay = -1; + + private TR1001RadioPacketConverter tr1001PacketConverter = null; + + /** + * Creates an interface to the TR1001 radio at mote. + * + * @param mote + * Radio's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public TR1001Radio(ESBMote mote) { + mspMote = mote; + + /* Start listening to CPU's USART */ + IOUnit usart = mote.getCPU().getIOUnit("USART 0"); + if (usart instanceof USART) { + radioUSART = (USART) usart; + radioUSART.setUSARTListener(this); + } + } + + /* Packet radio support */ + public byte[] getLastPacketTransmitted() { + return packetFromMote; + } + + public byte[] getLastPacketReceived() { + return packetToMote; + } + + public void setReceivedPacket(byte[] p) { + packetToMote = p; + if (packetToMote == null || packetToMote.length == 0) { + logger.fatal("Received null packet"); + return; + } + + if (isInterfered) { + logger.fatal("Received packet when interfered"); + return; + } + + /* Convert to TR1001 packet data */ + byte[] tr1001Frame = TR1001RadioPacketConverter.fromCoojaToTR1001(p); + + /* Feed to the CPU "slowly" */ + for (byte element : tr1001Frame) { + receiveByte(element, CYCLES_BETWEEN_BYTES); + } + } + + /* Byte radio support */ + public byte getLastByteTransmitted() { + return byteFromMote; + } + + public long getLastByteTransmittedDelay() { + return byteFromMoteDelay; + } + + public byte getLastByteReceived() { + return byteToMote; + } + + public void receiveByte(byte b, long delay) { + byteToMote = b; + bufferedBytes.add(b); + bufferedByteDelays.add(delay); + } + + /** + * @return True if undelivered bytes exist. + */ + public boolean hasPendingBytes() { + return bufferedBytes.size() > 0; + } + + /** + * If non-delivered bytes exist, tries to deliver one byte to the CPU by + * checking USART receive flag. + * + * @param cycles + * Current CPU cycles + */ + public void tryDeliverNextByte(long cycles) { + // Check that pending bytes exist + if (!hasPendingBytes()) { + return; + } + + // Check if time to deliver byte + long nextByteDelay = bufferedByteDelays.firstElement(); + if (cycles - lastDeliveredByteDelay < nextByteDelay) { + return; + } + + lastDeliveredByte = bufferedBytes.firstElement(); + + bufferedBytes.remove(0); + bufferedByteDelays.remove(0); + + if (radioUSART.isReceiveFlagCleared()) { + //logger.info(nextByteDelay + " < " + // + (cycles - receptionStartedCycles) + // + ":\tDelivering 0x" + Utils.hex8(lastDeliveredByte) + " (TODO=" + // + bufferedBytes.size() + ")"); + radioUSART.byteReceived(lastDeliveredByte); + } else { + /*logger.fatal(nextByteDelay + " < " + + (cycles - receptionStartedCycles) + + ":\tDROPPING 0x" + Utils.hex8(lastDeliveredByte) + " (TODO=" + + bufferedBytes.size() + ")");*/ + } + lastDeliveredByteDelay = cycles; + +// /* TODO BUG: Resends last byte, interrupt lost somewhere? */ +// else if (cycles > lastDeliveredByteTimestamp + CYCLES_BETWEEN_BYTES) { +// logger.warn("0x" + Utils.hex16((int) cycles) + ":\tRedelivering 0x" +// + Utils.hex8(lastDeliveredByte) + " (TODO=" + bufferedBytes.size() +// + ")"); +// radioUSART.byteReceived(lastDeliveredByte); +// lastDeliveredByteTimestamp = cycles; +// } + } + + /* USART listener support */ + public void dataReceived(USART source, int data) { + if (outgoingDataLength == 0 && !isTransmitting()) { + /* New transmission discovered */ + //logger.debug("----- NEW MSP TRANSMISSION DETECTED -----"); + tr1001PacketConverter = new TR1001RadioPacketConverter(); + + transmitting = true; + lastEventTime = mspMote.getSimulation().getSimulationTime(); + lastEvent = RadioEvent.TRANSMISSION_STARTED; + transmissionStartCycles = mspMote.getCPU().cycles; + lastDeliveredByteTimestamp = transmissionStartCycles; + this.setChanged(); + this.notifyObservers(); + } + + // Remember recent radio activity + ticksSinceLastSend = 0; + + if (outgoingDataLength >= outgoingData.length) { + logger.fatal("Ignoring byte due to buffer overflow"); + return; + } + + // Store packet data + outgoingData[outgoingDataLength++] = (byte) data; + + // Deliver byte to radio medium + lastEvent = RadioEvent.BYTE_TRANSMITTED; + byteFromMote = (byte) data; + byteFromMoteDelay = mspMote.getCPU().cycles - lastDeliveredByteTimestamp; + lastDeliveredByteTimestamp = mspMote.getCPU().cycles; + if (byteFromMoteDelay < 0) { + byteFromMoteDelay = 0; + } + + this.setChanged(); + this.notifyObservers(); + + // Feed to application level immediately + boolean finished = tr1001PacketConverter.fromTR1001ToCoojaAccumulated(byteFromMote); + if (finished) { + /* Transmission finished - deliver packet immediately */ + if (tr1001PacketConverter.accumulatedConversionIsOk()) { + packetFromMote = tr1001PacketConverter.getAccumulatedConvertedData(); + } else { + packetFromMote = new byte[0]; + } + + /* Notify observers of new prepared packet */ + /*logger.debug("----- MSP DELIVERED PACKET -----");*/ + lastEvent = RadioEvent.PACKET_TRANSMITTED; + this.setChanged(); + this.notifyObservers(); + + // Reset counters and wait for next packet + outgoingDataLength = 0; + ticksSinceLastSend = -1; + + // Signal we are done transmitting + transmitting = false; + lastEvent = RadioEvent.TRANSMISSION_FINISHED; + this.setChanged(); + this.notifyObservers(); + + /*logger.debug("----- MSP TRANSMISSION ENDED -----");*/ + } + } + + /* General radio support */ + public boolean isTransmitting() { + return transmitting; + } + + public boolean isReceiving() { + return hasPendingBytes(); + } + + public boolean isInterfered() { + return isInterfered; + } + + public int getChannel() { + // TODO Implement support for channels + return 1; + } + + public void signalReceptionStart() { + lastEvent = RadioEvent.RECEPTION_STARTED; + /*receptionStartedCycles = mspMote.getCPU().cycles;*/ + this.setChanged(); + this.notifyObservers(); + } + + public void signalReceptionEnd() { + // TODO Should be done according to serial port instead + // TODO Compare times with OS abstraction level + if (isInterfered()) { + isInterfered = false; + return; + } + lastEvent = RadioEvent.RECEPTION_FINISHED; + this.setChanged(); + this.notifyObservers(); + } + + public RadioEvent getLastEvent() { + return lastEvent; + } + + public void interfereAnyReception() { + if (!isInterfered()) { + isInterfered = true; + + bufferedBytes.clear(); + bufferedByteDelays.clear(); + + lastEvent = RadioEvent.RECEPTION_INTERFERED; + lastEventTime = mspMote.getSimulation().getSimulationTime(); + this.setChanged(); + this.notifyObservers(); + } + } + + public double getCurrentOutputPower() { + // TODO Implement method + return 1.5; + } + + public int getCurrentOutputPowerIndicator() { + // TODO Implement output power indicator + return 100; + } + + public double getCurrentSignalStrength() { + // TODO Implement signal strength + return 100; + } + + public void setCurrentSignalStrength(double signalStrength) { + // TODO Implement signal strength + // logger.warn("Not implemented"); + } + + public Position getPosition() { + return mspMote.getInterfaces().getPosition(); + } + + public void doActionsBeforeTick() { + } + + public void doActionsAfterTick() { + // Detect transmission end due to inactivity + if (isTransmitting() && ticksSinceLastSend > 4) { + /* Dropping packet due to inactivity */ + packetFromMote = new byte[0]; + + /* Notify observers of new empty packet */ + logger.warn("----- DELIVERED MSP NULL PACKET -----"); + lastEvent = RadioEvent.PACKET_TRANSMITTED; + this.setChanged(); + this.notifyObservers(); + + // Reset counters and wait for next packet + outgoingDataLength = 0; + ticksSinceLastSend = -1; + + // Signal we are done transmitting + transmitting = false; + lastEvent = RadioEvent.TRANSMISSION_FINISHED; + this.setChanged(); + this.notifyObservers(); + + //logger.debug("----- NULL TRANSMISSION ENDED -----"); + } else if (isTransmitting() && ticksSinceLastSend >= 0) { + // Increase counter to detect when transmission ends + ticksSinceLastSend++; + } + } + + public JPanel getInterfaceVisualizer() { + // Location + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + final JLabel statusLabel = new JLabel(""); + final JLabel lastEventLabel = new JLabel(""); + final JLabel ssLabel = new JLabel(""); + final JButton updateButton = new JButton("Update SS"); + + panel.add(statusLabel); + panel.add(lastEventLabel); + panel.add(ssLabel); + panel.add(updateButton); + + updateButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ssLabel.setText("Signal strength (not auto-updated): " + + getCurrentSignalStrength() + " dBm"); + } + }); + + Observer observer; + this.addObserver(observer = new Observer() { + public void update(Observable obs, Object obj) { + if (isTransmitting()) { + statusLabel.setText("Transmitting packet now!"); + } else if (isReceiving()) { + statusLabel.setText("Receiving packet now!"); + } else if (radioOn) { + statusLabel.setText("Listening..."); + } else { + statusLabel.setText("HW turned off"); + } + + lastEventLabel.setText("Last event (time=" + lastEventTime + "): " + + lastEvent); + ssLabel.setText("Signal strength (not auto-updated): " + + getCurrentSignalStrength() + " dBm"); + } + }); + + observer.update(null, null); + + // Saving observer reference for releaseInterfaceVisualizer + panel.putClientProperty("intf_obs", observer); + + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + Observer observer = (Observer) panel.getClientProperty("intf_obs"); + if (observer == null) { + logger.fatal("Error when releasing panel, observer is null"); + return; + } + + this.deleteObserver(observer); + } + + public double energyConsumptionPerTick() { + return 0; + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML, boolean visAvailable) { + } + + public Mote getMote() { + return mspMote; + } +} diff --git a/tools/cooja/apps/mspsim/src/se/sics/cooja/mspmote/interfaces/TR1001RadioPacketConverter.java b/tools/cooja/apps/mspsim/src/se/sics/cooja/mspmote/interfaces/TR1001RadioPacketConverter.java new file mode 100644 index 000000000..57bf4e54a --- /dev/null +++ b/tools/cooja/apps/mspsim/src/se/sics/cooja/mspmote/interfaces/TR1001RadioPacketConverter.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2007, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: TR1001RadioPacketConverter.java,v 1.1 2008/02/07 14:55:35 fros4943 Exp $ + */ + +package se.sics.cooja.mspmote.interfaces; + +import org.apache.log4j.Logger; + +/** + * Converts radio packets between TR1001/ESB and COOJA. + * Some functionality, such as GCR coding, is ESB TR1001 driver specific. + * + * @author Fredrik Osterlind + */ +public class TR1001RadioPacketConverter { + private static Logger logger = Logger.getLogger(TR1001RadioPacketConverter.class); + + private static GCRCoder gcrCoder = new GCRCoder(); + + /* TR1001 packet: PREAMBLE(20) _ SYNCH(1+4+1) _ GCR(ESBDATA(?)) _ TRAIL(4) */ + final static int TR1001_PREAMBLE_LENGTH = 20; + + final static int TR1001_NR_SYNCHBYTES = 4; + + final static byte TR1001_SYNCH1 = 0x3c; + + final static byte TR1001_SYNCH2 = 0x03; + + final static int TR1001_HEADER_LENGTH = TR1001_PREAMBLE_LENGTH + 1 + + TR1001_NR_SYNCHBYTES + 1; + + final static int TR1001_FOOTER_LENGTH = 4; + + /* "ESB" packet: LENGTH(2) _ APPDATA(?) _ CRC(2) */ + final static int ESB_HEADER_LENGTH = 2; + + final static int ESB_FOOTER_LENGTH = 2; + + + private enum AccumulatedConversionState { + TR1001_PREAMBLE, TR1001_SYNCH, ESB_LEN1, ESB_LEN2, ESB_DATA, ESB_CRC1, ESB_CRC2, ESB_POST, + } + + private AccumulatedConversionState accumulatedConversionState = AccumulatedConversionState.TR1001_PREAMBLE; + + private boolean accumulatedConversionOK = true; + + private int[] accumulatedConversionDataLengthArray = new int[2]; + + private int[] accumulatedConversionDataArray = null; + + private int[] accumulatedConversionCRCArray = new int[2]; + + private int accumulatedConversionDataCounter = 0; + + private int accumulatedConversionDataLength = 0; + + private int accumulatedConversionFooterLength = 0; + + private GCRCoder AccumulatedConversionGCRCoder = new GCRCoder(); + + /** + * Converts radio packet data from COOJA to TR1001. This + * implementation is ESB platform and TR1001 driver specific. + * + * It consists of (in sequential order): + * - Adding 2-byte length header + * - Calculating and adding 2-byte CRC16 footer + * - GCR encoding data + * - Add TR1001 header: preamble and synch bytes + * - Add TR1001 footer: 4 trail bytes + * + * The returned array typically needs to be feeded to the emulated "slowly", + * i.e. one by one and using interrupts. + * + * @param coojaPacket + * COOJA radio packet + * @return TR1001 radio packet + */ + public static byte[] fromCoojaToTR1001(byte[] coojaPacket) { + + /* Create "ESB" packet: add length header and CRC16 footer */ + byte[] esbPacket = addLengthAndCRC(coojaPacket); + + /* GCR encode radio frame */ + byte[] encodedData = gcrCoder.gcrEncode(esbPacket, esbPacket.length); + + /* Create TR1001 header (preamble and synchbytes) */ + byte[] tr1001Frame = new byte[TR1001_HEADER_LENGTH + encodedData.length + + TR1001_FOOTER_LENGTH]; + for (int i = 0; i < TR1001_PREAMBLE_LENGTH; i++) { + tr1001Frame[i] = (byte) 0xaa; + } + tr1001Frame[TR1001_PREAMBLE_LENGTH] = (byte) 0xff; + for (int i = 0; i < TR1001_NR_SYNCHBYTES; i++) { + tr1001Frame[TR1001_PREAMBLE_LENGTH + 1 + i] = TR1001_SYNCH1; + } + tr1001Frame[TR1001_PREAMBLE_LENGTH + 1 + TR1001_NR_SYNCHBYTES] = TR1001_SYNCH2; + + /* Add encoded data */ + System.arraycopy(encodedData, 0, tr1001Frame, TR1001_HEADER_LENGTH, + encodedData.length); + + /* Add TR1001 footer (trail bytes) */ + tr1001Frame[TR1001_HEADER_LENGTH + encodedData.length] = (byte) 0x33; + tr1001Frame[TR1001_HEADER_LENGTH + encodedData.length + 1] = (byte) 0xcc; + tr1001Frame[TR1001_HEADER_LENGTH + encodedData.length + 2] = (byte) 0x33; + tr1001Frame[TR1001_HEADER_LENGTH + encodedData.length + 3] = (byte) 0xcc; + + return tr1001Frame; + } + + /** + * Converts radio packet data from TR1001 to COOJA. This + * implementation is ESB platform and TR1001 driver specific. + * + * It consists of (in sequential order): + * - Removing TR1001 header and footer + * - GCR decode data + * - Read length header + * - Remove both length header and CRC footer + * + * @param tr1001Data + * TR1001 packet data + * @param tr1001DataLength + * TR1001 packet data length + * @return COOJA radio packet + */ + public static byte[] fromTR1001ToCooja(byte[] tr1001Data, + int tr1001DataLength) { + /* Remove TR1001 specifics: preamble, synch and trail bytes */ + System.arraycopy(tr1001Data, TR1001_HEADER_LENGTH, tr1001Data, 0, + tr1001DataLength - TR1001_HEADER_LENGTH - TR1001_FOOTER_LENGTH); + tr1001DataLength = tr1001DataLength - TR1001_HEADER_LENGTH + - TR1001_FOOTER_LENGTH; + + /* GCR decode */ + byte[] decodedData = gcrCoder.gcrDecode(tr1001Data, tr1001DataLength); + + if (decodedData != null) { + /* Decoding succeded, fetch length from the two first bytes */ + int dataLength = ((decodedData[0] & 0xff) << 8) + (decodedData[1] & 0xff); + + if (dataLength + ESB_HEADER_LENGTH + ESB_HEADER_LENGTH > decodedData.length) { + logger.fatal("Decoded data is smaller than specified data length: " + + (dataLength + ESB_HEADER_LENGTH + ESB_HEADER_LENGTH) + " vs " + + decodedData.length); + return new byte[0]; + } + + /* Remove ESB header and CRC footer */ + byte[] packetData = new byte[dataLength]; + System.arraycopy(decodedData, ESB_HEADER_LENGTH, packetData, 0, + dataLength); + + return packetData; + } + + logger + .fatal("Error when converting emulated to application level, returning empty: " + + tr1001DataLength); + return new byte[0]; + } + + /** + * Adds length header and CRC16 footer to given data. + * + * @param packetData + * Packet data + * @return Packet data with length header and CRC16 footer + */ + private static byte[] addLengthAndCRC(byte[] packetData) { + short accumulatedCRC = (short) 0xffff; + byte[] radioFrame = new byte[ESB_HEADER_LENGTH + packetData.length + + ESB_FOOTER_LENGTH]; + + /* Add length */ + radioFrame[0] = (byte) ((packetData.length >> 8) & 0xff); + accumulatedCRC = CRCCoder.crc16Add(radioFrame[0], accumulatedCRC); + radioFrame[1] = (byte) (packetData.length & 0xff); + accumulatedCRC = CRCCoder.crc16Add(radioFrame[1], accumulatedCRC); + + /* Add data */ + System.arraycopy(packetData, 0, radioFrame, ESB_HEADER_LENGTH, + packetData.length); + + /* Calculate CRC */ + for (byte element : packetData) { + accumulatedCRC = CRCCoder.crc16Add(element, accumulatedCRC); + } + + /* Add CRC */ + radioFrame[ESB_HEADER_LENGTH + packetData.length] = (byte) ((accumulatedCRC >> 8) & 0xff); + radioFrame[ESB_HEADER_LENGTH + packetData.length + 1] = (byte) (accumulatedCRC & 0xff); + return radioFrame; + } + + /** + * Adds another another byte to the accumulated conversion from TR1001 + * to Cooja. + * + * @see #accumulatedConversionIsOk() + * @param b + * Byte + * @return True if conversion finished (either successful of failed). + */ + public boolean fromTR1001ToCoojaAccumulated(byte b) { + if (accumulatedConversionState == AccumulatedConversionState.TR1001_PREAMBLE) { + if (b == (byte) 0xaa || b == (byte) 0xff) { + return false; + } else if (b == (byte) 0x3c) { + accumulatedConversionState = AccumulatedConversionState.TR1001_SYNCH; + return false; + } else { + accumulatedConversionOK = false; + return true; + } + } else if (accumulatedConversionState == AccumulatedConversionState.TR1001_SYNCH) { + if (b == TR1001_SYNCH1) { + return false; + } else if (b == TR1001_SYNCH2) { + accumulatedConversionState = AccumulatedConversionState.ESB_LEN1; + AccumulatedConversionGCRCoder.gcr_init(); + return false; + } else { + accumulatedConversionOK = false; + return true; + } + } else if (accumulatedConversionState == AccumulatedConversionState.ESB_LEN1) { + AccumulatedConversionGCRCoder.gcr_decode(0xff & b); + if (AccumulatedConversionGCRCoder.gcr_get_decoded(accumulatedConversionDataLengthArray, 0)) { + accumulatedConversionState = AccumulatedConversionState.ESB_LEN2; + } + return false; + } else if (accumulatedConversionState == AccumulatedConversionState.ESB_LEN2) { + AccumulatedConversionGCRCoder.gcr_decode(0xff & b); + if (AccumulatedConversionGCRCoder.gcr_get_decoded(accumulatedConversionDataLengthArray, 1)) { + accumulatedConversionState = AccumulatedConversionState.ESB_DATA; + accumulatedConversionDataLength = (accumulatedConversionDataLengthArray[0] & 0xff) << 8; + accumulatedConversionDataLength = (accumulatedConversionDataLengthArray[1] & 0xff); + accumulatedConversionDataArray = new int[accumulatedConversionDataLength]; + } + return false; + } else if (accumulatedConversionState == AccumulatedConversionState.ESB_DATA) { + AccumulatedConversionGCRCoder.gcr_decode(0xff & b); + if (AccumulatedConversionGCRCoder.gcr_get_decoded(accumulatedConversionDataArray, accumulatedConversionDataCounter)) { + accumulatedConversionDataCounter++; + if (accumulatedConversionDataCounter >= accumulatedConversionDataArray.length) { + accumulatedConversionState = AccumulatedConversionState.ESB_CRC1; + } + } + return false; + } else if (accumulatedConversionState == AccumulatedConversionState.ESB_CRC1) { + AccumulatedConversionGCRCoder.gcr_decode(0xff & b); + if (AccumulatedConversionGCRCoder.gcr_get_decoded(accumulatedConversionCRCArray, 0)) { + accumulatedConversionState = AccumulatedConversionState.ESB_CRC2; + } + return false; + } else if (accumulatedConversionState == AccumulatedConversionState.ESB_CRC2) { + AccumulatedConversionGCRCoder.gcr_decode(0xff & b); + if (AccumulatedConversionGCRCoder.gcr_get_decoded(accumulatedConversionCRCArray, 1)) { + accumulatedConversionState = AccumulatedConversionState.ESB_POST; + } + return false; + } else if (accumulatedConversionState == AccumulatedConversionState.ESB_POST) { + accumulatedConversionFooterLength++; + return accumulatedConversionFooterLength >= 4; + } else { + accumulatedConversionOK = false; + return true; + } + } + + /** + * @return Converted data (application level) + */ + public byte[] getAccumulatedConvertedData() { + byte[] dataArrayByte = new byte[accumulatedConversionDataArray.length]; + for (int i = 0; i < accumulatedConversionDataArray.length; i++) { + dataArrayByte[i] = (byte) accumulatedConversionDataArray[i]; + } + return dataArrayByte; + } + + /** + * Returns status from accumulated conversion. + * + * @return True if ok, false if error occurred. + */ + public boolean accumulatedConversionIsOk() { + return accumulatedConversionOK; + } + +}