I am trying to get a weight value from a set of weighing scales over USB. It should be quite simple, according to their doc, I need to send two bytes the letter "W" and the carriage return byte. It then responds with 16 bytes of data representing the current weight on the device.
The device has 1 interface, 2 endpoints with a max packet size of 64. I believe I must use the bulkTransfer function as the endpoint types are USB_ENDPOINT_XFER_BULK.
Here is the doc graphic:
How exactly should I send this request and receive the response? My attempt is below, and the response is simply a Start of Heading symbol, then a back quote symbol "`" and a load of zeroes. I have tried to run the code on a polling loop or just a single request but get the same result.
val connection = usbManager.openDevice(scales)
val intf: UsbInterface = scales.getInterface(0)
connection.claimInterface(intf, true)
val endpointReadIn = intf.getEndpoint(0)
val endpointWriteOut = intf.getEndpoint(1)
val bytes = byteArrayOf(0x57.toByte(), 0x0D.toByte())
thread {
val request = connection.bulkTransfer(endpointWriteOut, bytes, bytes.size, 0)
Log.d(TAG, "Was request to write successful? $request")
val buffer = ByteArray(16)
val response = connection.bulkTransfer(endpointReadIn, buffer, buffer.size, 0)
Log.d(TAG, "Was response from read successful? $response")
val responseString = StringBuilder()
for (i in 0..15) {
responseString.append(buffer[i])
}
Log.d(TAG, "Response: $responseString")
val hex = toHexString(buffer)
Log.d(TAG, "Hex: $hex")
connection.close()
}
fun fromHexString(hexString: String): ByteArray {
val len = hexString.length / 2
val bytes = ByteArray(len)
for (i in 0 until len) bytes[i] = hexString.substring(2 * i, 2 * i + 2).toInt(16).toByte()
return bytes
}
Output:
Was request to write successful? 2
Was response from read successful? 2
Response: 19600000000000000
Hex: 01 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00
There was a couple of missing pieces here. I think the big one was not specifying the baud rate, data bit, stop bit and parity which are all achieved via the controlTransfer function.
In the end I couldn't get it to work myself despite getting successful responses while setting these. Then I found this lovely library which is compatible with this RS232 device and it works well. I just need to specify the vid / pid to get a custom driver using the FtdiSerialDriver class.
Related
I am attempting to communicate with an ELM327 OBDII Bluetooth dongle using the Android BluetoothChat example. I can connect to the device without any trouble, and messages from BluetoothChat to the ODBII device seem to be transmitted and received by the device correctly.
However, the response messages from the OBDII device are often split into several messages, scrambled, or missing characters.
For example, it takes three tries of the ati command to receive the full expected response:
Me: ati
OBDII: a
OBDII: 327
OBDII: 327
OBDII: 327 v1.5 >
Me: ati
OBDII: 1
OBDII: 1
OBDII: 1.5 >v
OBDII: 1.5 >
Me: ati
OBDII: ELM327 v1.5 >
Similarly, sending 010c should trigger a one line response containing three hex pairs. Instead, I usually (but not always) get results like the following:
Me: 010c
OBDII:
OBDII: 4
OBDII: 3C
OBDII: 3
OBDII: 3C C
OBDII: 3C
OBDII:
OBDII:
OBDII: >
I have tried several different baud rates and different OBDII protocols, but changes from the default settings only seem to make matters worse. Is there a problem with my response message handling? Why is the response message splitting? The Bluetooth dongle works properly with available apps such as Torque, so I don't think the device is malfunctioning.
The code that I'm using is almost identical to the BluetoothChat project (source here). I have only modified the UUID for my Bluetooth device and added a carriage return to the outgoing message (as per this StackOverflow question).
Change 1 (in BluetoothChatService.java):
// Unique UUID for this application
private static final UUID MY_UUID_SECURE =
//UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private static final UUID MY_UUID_INSECURE =
//UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66");
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
Change 2 (in BluetoothChat.java):
// The action listener for the EditText widget, to listen for the return key
private TextView.OnEditorActionListener mWriteListener =
new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
// If the action is a key-up event on the return key, send the message
if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {
String message = view.getText().toString();
//sendMessage(message);
sendMessage(message + "\r");
}
if(D) Log.i(TAG, "END onEditorAction");
return true;
}
};
ELM327 manual for reference
I found a solution to my message splitting problem in this BluetoothSPP project by user akexorcist on Github. The relevant function from class ConnectedThread is presented below:
public void run() {
byte[] buffer;
ArrayList<Integer> arr_byte = new ArrayList<Integer>();
// Keep listening to the InputStream while connected
while (true) {
try {
int data = mmInStream.read();
if(data == 0x0A) {
} else if(data == 0x0D) {
buffer = new byte[arr_byte.size()];
for(int i = 0 ; i < arr_byte.size() ; i++) {
buffer[i] = arr_byte.get(i).byteValue();
}
// Send the obtained bytes to the UI Activity
mHandler.obtainMessage(BluetoothState.MESSAGE_READ
, buffer.length, -1, buffer).sendToTarget();
arr_byte = new ArrayList<Integer>();
} else {
arr_byte.add(data);
}
} catch (IOException e) {
connectionLost();
// Start the service over to restart listening mode
BluetoothService.this.start(BluetoothService.this.isAndroid);
break;
}
}
}
Apparently, though please correct me if I'm wrong, .read() cannot grab and maintain the format of whatever bytes appear on the stream without some assistance.
I'm trying to read a smartcard via my LG P710 Optimus L7 2.
I'm following this tutorial
I can select the "1PAY.SYS.DDF01" directory
I can select the Application
But I can't perform an "GET PROCESSING OPTIONS"
It always result in an 6700 error (Lc or Le wrong)
here is my code
NfcAdapter mNFCAdapter;
Intent intent;
PendingIntent pendingIntent;
private TextView mTextView;
String[][] techList;
IntentFilter[] filters = new IntentFilter[3];
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mTextView = (TextView) findViewById(R.id.title);
mNFCAdapter = NfcAdapter.getDefaultAdapter(this);
intent = new Intent(getApplicationContext(), getClass());
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
pendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
techList = new String[][]{
new String[]
{ MifareClassic.class.getName() },
new String[]
{ IsoDep.class.getName() }
};
filters[0] = new IntentFilter();
filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
filters[0].addCategory(Intent.CATEGORY_DEFAULT);
// add type of tag data you want to have - here ndef -> plain text
try {
filters[0].addDataType(MIME_TEXT_PLAIN);
} catch (MalformedMimeTypeException e) {
e.printStackTrace();
}
filters[1] = new IntentFilter();
filters[1].addAction(NfcAdapter.ACTION_TAG_DISCOVERED);
filters[1].addCategory(Intent.CATEGORY_DEFAULT);
filters[2] = new IntentFilter();
filters[2].addAction(NfcAdapter.ACTION_TECH_DISCOVERED);
filters[2].addCategory(Intent.CATEGORY_DEFAULT);
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String action = intent.getAction();
mTextView.setText(action);
Toast.makeText(getApplicationContext(), action, Toast.LENGTH_SHORT).show();
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
IsoDep tagIsoDep;
if((tagIsoDep = IsoDep.get(tagFromIntent)) != null)
if(handleIsoDep(tagIsoDep))
return;
}
private boolean handleIsoDep(IsoDep tag){
try{
tag.connect();
tag.setTimeout(20);
byte[] responseAPDU;
//2PAY.SYS.DDF01
byte[] select_Dir = new byte[]{
(byte)0x00, (byte)0xa4, (byte)0x04, (byte)0x00, (byte)0x0e,
(byte)0x32, (byte)0x50, (byte)0x41, (byte)0x59, (byte)0x2e,
(byte)0x53, (byte)0x59, (byte)0x53, (byte)0x2e, (byte)0x44,
(byte)0x44, (byte)0x46, (byte)0x30, (byte)0x31
};
//Select CC Applet
byte[] select_Applet = new byte[]{
(byte)0x00, (byte)0xa4, (byte)0x04, (byte)0x00, (byte)7,
(byte)0xa0, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x04,
(byte)0x30, (byte)0x60
};
//Send GET PROCESSING OPTIONS command
byte[] Send_Get = new byte[]{
(byte)0x80,(byte)0xA8,(byte)0x00,(byte)0x00,(byte)0x02,
(byte)0x83,(byte)0x00,
(byte)0x00
};
responseAPDU = tag.transceive(select_Dir);
mTextView.setText(mTextView.getText() + handleResponse(responseAPDU));
this returns the APDU-Statusword 9000 -> success
responseAPDU = tag.transceive(select_Applet);
mTextView.setText(mTextView.getText() + handleResponse(responseAPDU));
this returns the APDU-Statusword 9000 -> success
responseAPDU = tag.transceive(Send_Get);
mTextView.setText(mTextView.getText() + handleResponse(responseAPDU));
and this one is making problems: it returns 6700 -> wrong Lc or Le
mTextView.setText(mTextView.getText() + "\n\nDone");
tag.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
The function handleResponse just parses the "responseAPDU" from Binary to Hex an highlights the Statusword
Can anybody tell my what is going wrong?
or just help me out?
PS sry for bad english ;)
As response to my application-select I get:
6f298407a0000000043060a51e50074d41455354524f5f2d046465656e9f38039f5c08bf0c059f4d020b0a9000
6F -> FCI Template 29
84 -> DF Name 07 A0 00 00 00 04 30 60
A5 -> FCI Properietary Template 1E
50 -> Application Lable 07 4D 41 45 53 54 52 4F 5F 2D 04 64 65 6E
9F38 -> PDOL 03 9F 5C 08
BF0C -> FCI Issuer Data 05
9F4D -> Log Entry 02 0B
0A Additional Issuer Data
But I don't know what ive to insert into the Data fild from the GET PROCESSING OPTIONS.
Iv'e red the guidelines in EMV Book 3, section "5.4 Rules for Using a Data Object List (DOL)".
So do I just have to set the data field 83 03 9F 5C 08
and Lc = 5?
In order to help you, the entire ADPU dialog (commands/responses) would be needed.
However, based on your code : hardcoding your select_Dir and select_Applet commands is correct, but you can't hardcode the GET PROCESSING OPTIONS command whose syntax depends on the response of the card (ICC) to your select_Applet command.
EMV 4.3 Book 1, "Table 45: SELECT Response Message Data Field (FCI) of an ADF", explains that a successful card response to the SELECT command contains a "Processing Options Data Object List" (PDOL, tag 9F38). That's the list of fields required by the card to process the transaction (ex : amount, ...). These fields values are to be returned to the card by the terminal (your phone) through the GET PROCESSING OPTIONS command data field (tag 83), as documented in EMV 4.3 book 3, section "6.5.8.3 Data Field Sent in the Command Message" :
The data field of the command message is a data object coded according to the PDOL provided by the ICC, as defined in section 5.4, and is introduced by the tag '83'. When the data object list is not provided by the ICC, the terminal sets the length field of the template to zero. Otherwise, the length field of the template is the total length of the value fields of the data objects transmitted to the ICC.
Knowing that :
Your selected AID (A0 00 00 00 04 30 60) is a Mastercard Maestro one, which is unlikely to have an empty PDOL
But your GET PROCESSING OPTIONS command does not list any value in its data field
You probably have a mismatch between the length of your GET PROCESSING OPTIONS data field and the total length of the fields asked by the card in the PDOL, hence the 6700 checking error returned by the card (EMV Book 1, "Table 33: GET RESPONSE Error Conditions").
You have identified the PDOL requested by the card as : 9F38 -> 03 9F 5C 08.
The 03 tells you the PDOL is 3 bytes long. 9F5C is the tag of the requested field, 08 is the length of the field value that is to be returned by the phone.
Tag 9F5C is defined in EMV Contactless 2.3 Book C2 kernel 2 specification, section "A.1.59 DS Requested Operator ID". The DS Requested Operator ID is defined as
Contains the Terminal determined operator identifier for data
storage. It is sent to the Card in the GET PROCESSING
OPTIONS command.
I'm not familiar with this tag, so I can't tell you what a proper value is.
However, here is what the data field of the GET PROCESSING OPTIONS command should look like, assuming a DS Requested Operator ID has value 01 02 03 04 05 06 07 08, and given the Data Object List formatting guidelines in EMV Book 3, section "5.4 Rules for Using a Data Object List (DOL)" :
83 08 01 02 03 04 05 06 07 08
and Lc = 10
I made a simple test application for quick debugging. I send some bytes, print what I sent on the phones screen and print what I receive.
When I send WRONG commands I get the corresponding error codes in the two byte SW1SW2.
I can also call my own command and override SW1SW2 with my own values and I can get them.
Here's the problem: When I send a CORRECT command the transceive command fails with the informative exception "Transceive failed".
If I send the correct command, but override SW1SW2 to something other than 90 00 then I get the SW value I set, but NO response data. (likely because the card does not send ODATA when SW1SW2 <> 90 00)
So how come I'm so sure I sent correct commands? Well other than messing with my own test command I called the GetAppId command - which failed saying I have to define AppId in the card.
So I define it in the card, send the same command and transceive fails.
So I'm pretty sure the problem is that transceiving fails when there is ODATA, but I do not understand WHY or how to FIX it.. help please!
EDIT: My card is the 7.5 D contactless basiccard from ZeitControl.
EDIT2: I have set the timeout to 2000ms with no change in behavior. I'm trying to return a single byte of data and the system command I called also doesn't sound heavy.
Then I downloaded and attached the Android source and debugged. There was some segments it would still not go into - but the card seems to return null on valid commands unless I return some manually set SW1SW2 in which case that is the only thing received.
EDIT3: The system command I tried was:
192 14 0 0 0
(or C0 0E 00 00 00)
(or CLA INS P1 P2 Lc)
I'm not 100% sure I'm doing that one correctly, but I have tried with various lengths (up to 22) of Le and without Le as above and only without does it not give me 6700 (wrong Le/Lc)
Of course instead of 6700 it returns null it seems...
The other command is my own defined as 20 0A (value as Byte) and no P1/P2 specified in the .BAS file.
I call that one with:
32 10 1 0 1
(or 20 0A 01 00 01)
(or CLA INS Lc IDATA Le)
This should mean 1 byte data in, set to 0, and 1 byte expected out (+ SW1/SW2 as always).
(Setting P1/P2 gives 6700 so unless defined in the command declaration I dont think they should be there)
This also returns null. I Expect 00 90 00 to be returned here. (if I set "value" to 00 that is)
I'm using a HTC One X.
EDIT4:
MinSdk version = 14 and target 18.
if(bitcoinCard != null){
try {
String sentmsg, receivedmsg;
byte[] send = getBytes(commandBytes.getText().toString());
byte[] data = null;
if(send != null){
bitcoinCard.setTimeout(2000);
data = bitcoinCard.transceive(send);
}
//bitcoinCard.close();
/*if(data != null && data.length == 2)
{
mainLabel.setText("SW1SW2: " + (data[0] < 0 ? -data[0] +
128 : data[0]) + " " + (data[1] < 0 ? -data[1] + 128 : data[1]));
}else */if (data != null && send != null)
{
sentmsg = "" + (send[0] < 0 ? send[0] + 256 : send[0]);
for(int i = 1; i < send.length; i++)
{
sentmsg = sentmsg + " " + (send[i] < 0 ? send[i] +
256 : send[i]);
}
receivedmsg = "" + (data[0] < 0 ? data[0] + 256 : data[0]);
for(int i = 1; i < data.length; i++)
{
receivedmsg = receivedmsg + " " + (data[i] < 0 ? data[i] + 256 : data[i]);
}
mainLabel.setText("Sent: " + sentmsg + "\n" +
"Response: " +
receivedmsg);
}else
{
mainLabel.setText("Sent or received null.");
}
} catch (IOException e) {
mainLabel.setText("Tried to talk to card, but had error: " +
e.getMessage());
}
}
First, when you send APDUs you should use an IsoDep object (and not NfcA). Android should show both tag technologies as available for your card. The problem here is that Android will typically only activate the card in ISO 14443-4 protocol mode if you use IsoDep. Thus, wehn using NfcA, your card will not be ready to accept APDUs.
I just tested and this is at least the case on a Nexus S with Android 4.1.2. In fact trying to transceive using the NfcA object leads to TagLostExceptions with some cards and to some other really odd behavior with another card I tried.
Second, if you send
byte[] cmd = { (byte)0xC0, (byte)0x0E, (byte)0x00, (byte)0x00, (byte)0x00 };
I would expect the card to return the actual application ID. However, the answer to this command (i.e. <data> <SW1=61> <SW2=len>) does not comply to ISO 7816-4 (no data should be returned for a 61xx status code) so this might cause a problem.
UPDATE: I just tested this with a Nexus S (Android 4.1.2) and receiving such responses was not a problem.
Last, your other command (20 0A) is not what you expect it to be:
I strongly suggest you only use CLA bytes set to 0x00 or 0x80 unless you know what you are doing (use of secure messaging, use of logical channels, ...). Though, Android (at least with NXP's NFC chipset) does not care about the structure of the APDU, but your card might!
An APDU always has the form <CLA> <INS> <P1> <P2> [Lc [DATA]] <Le> (with the special case of <CLA> <INS> <P1> <P2>). So this means you cannot simply omit P1 and P2.
You are correct in that BasicCard will discard ODATA if SW<>9000 and SW1<>61.
I'm busy with an app to emulate normal APDU communication on a Nexus 7 with CM10.1 to an ACR122U102 reader/writer. I found this blog about software card emulation and wrote an app to make my device (the nexus) appear as a card. Now I'm trying to send messages back and forth between this device and the ACR122u. So far, I've only managed to communicate with the nexus 7 by sending D4 40 01 (InDataExchange page 127) APDU's. For the application I'm writing, this should be sufficient.
The problem lays in the answer I send from the device to the reader. Using the transcieve function (android.nfc.tech.IsoPcdA with reflection), I can reply with a byte array of length > 0. This would show up on the reader-end like a normal InDataExchange response (e.g: D5 41 00 01 02 03 with {01 02 03} being the byte array supplied to the transcieve function). But I can't control the status byte nor the SW bytes in the response (D5 41 XX and both SW's). There's no documentation to be found about this IsoPcdA class except the source code itself.
What I want to be able to do is change the XX to a byte of my choice and to send answers of length = 0 (e.g: D5 41 01 without any extra data). Is it possible?
I'm not exactly sure what you are trying to achieve here. Whatever you transceive with IsoPcdA's transceive method are complete APDUs (as defined in ISO/IEC 7816-4, or rather any PDU within the ISO-DEP transport protocol). So the return value of transceive is a full C-APDU (command APDU) and the byte array parameter of transceive is a full R-APDU (response APDU) including the two bytes of the status word (SW1 | SW2). Thus, the last two bytes of that parameter are the status word. In your example SW1 would be 02 and SW2 would be 03.
What you see as status byte in the InDataExchange command of the PN532 NFC controller is not the status word of the APDU but the status of the command execution within the PN532 NFC controller. This status byte gives you information about buffer overflows, communication timeouts, etc and is not something that is returned by the card side.
EDIT : Sample code + test commands:
Sample Code running on Galaxy Nexus (CM 10):
try {
Class isoPcdA = Class.forName("android.nfc.tech.IsoPcdA");
Method isoPcdA_get = isoPcdA.getDeclaredMethod("get", Tag.class);
final IsoPcdA techIsoPcdA = (IsoPcdA)isoPcdA_get.invoke(null, tag);
if (techIsoPcdA != null) {
if (mWorker != null) {
mInterrupt = true;
mWorker.interrupt();
try {
mWorker.join();
} catch (Exception e) {}
}
mInterrupt = false;
mWorker = new Thread(new Runnable() {
public void run () {
try {
techIsoPcdA.connect();
byte[] command = techIsoPcdA.transceive(new byte[]{ (byte)0x90, (byte)0x00 });
Log.d(CardEmulationTest.class.getName(), "Connected.");
while (!mInterrupt) {
Log.d(CardEmulationTest.class.getName(), "C-APDU=" + StringUtils.convertByteArrayToHexString(command));
command = techIsoPcdA.transceive(command);
}
} catch (Exception e) {
Log.e(CardEmulationTest.class.getName(), "Exception while communicating on IsoPcdA object", e);
} finally {
try {
techIsoPcdA.close();
} catch (Exception e) {}
}
}
});
mWorker.start();
}
} catch (Exception e) {
Log.e(CardEmulationTest.class.getName(), "Exception while processing IsoPcdA object", e);
}
Test (using ACR122U):
InListPassivTargets (1 target at 106kbps)
> FF00000004 D44A 0100 00
< D54B 010100046004088821310578338800 9000
InDataExchange with DATA = 0x01
> FF00000004 D440 01 01 00
< D541 00 01 9000
So we get an error code of 0x00 from the card reader (status of InDataExchange command; not part of the actual response APDU), we get 0x01 as the response (this is the IsoDepA response APDU) and we get 0x9000 as the status code for the card reader wrapper APDU (not part of the actual response APDU).
InDataExchange with DATA = 0x01 0x02
> FF00000005 D440 01 0102 00
< D541 00 0102 9000
So we get an error code of 0x00 from the card reader (status of InDataExchange command; not part of the actual response APDU), we get 0x01 0x02 as the response (this is the IsoDepA response APDU) and we get 0x9000 as the status code for the card reader wrapper APDU (not part of the actual response APDU).
InDataExchange with DATA = 0x01 0x02 0x03
> FF00000006 D440 01 010203 00
< D541 00 010203 9000
So we get an error code of 0x00 from the card reader (status of InDataExchange command; not part of the actual response APDU), we get 0x01 0x02 0x03 as the response (this is the IsoDepA response APDU) and we get 0x9000 as the status code for the card reader wrapper APDU (not part of the actual response APDU).
InDataExchange with DATA = 0x01 0x02 0x03 0x04
> FF00000007 D440 01 01020304 00
< D541 00 01020304 9000
So we get an error code of 0x00 from the card reader (status of InDataExchange command; not part of the actual response APDU), we get 0x01 0x02 0x03 0x04 as the response (this is the IsoDepA response APDU) and we get 0x9000 as the status code for the card reader wrapper APDU (not part of the actual response APDU).
Thus, we get exactly the data taht we send as command APDU as response APDU (note that none of these APDUs is formatted according to ISO 7816-4, but that doesnt matter as the IsoPcdA card emulation works with any ISO 14443-4 transport protocol format).
The status code of 0x9000 belongs to the card reader APDU encapsulation (CLA=FF INS=00 P1P2=0000 Lc [PN542 COMMAND] Le=00) that is required as the ACR122U's PN532 is accessed over the CCID (PC/SC) interface. These are pure reader command encapsulation and have nothing to do with the communication over ISO-DEP.
The D440 01 [DATA] is the PN532 command to exchange data (e.g. APDUs) over ISO-DEP and the D541 00 [DATA] is the associated response.
I'm wanting to read MIDI data from an asset stream. The file is a MIDI0 file of length 150 bytes according to Windows. Using this code, I read 150 bytes measured by count, but the output string is only 127.5 bytes.
try {
assetStream = assets.open("MIDI0_7.mid");
int count=0;
do {
byteValue = assetStream.read();
count++;
outputString = outputString + Integer.toHexString(byteValue);
} while (byteValue > -1) ;
Log.d("MUSIC", "Final string " +outputString);
Log.d("MUSIC", "bytes read " +count);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
This read HEX data also doesn't match with the MIDI spec AFAICT. The opening 8 bytes should read
4d 54 68 64 00 00 00 06
but I get
4d 54 68 64 00 06
I can't be sure about the save format of my MIDI file (exported test file with 7 notes from Cakewalk SONAR) so I'm not sure why the MIDI doesn't correspond with the standards, but before I can solve that I need to know where my missing data is! What am I doing wrong to see some bytes getting dropped from my output stream?
Edit:
Okay, found it. Bytes less than 16 are returned as a single character by Integer.toHexString() instead of being a 0x figure. Easily fixed.