Nike adapter
Nike together with Apple have introduced the Nike adapter on the market that measures the steps as you excercise. Their marketing mumbo jumbo says “You and your Nike adapter are the perfect running partners. See the minutes tick by. Watch the miles unfold. Hear real-time voice feedback. All to your favorite music — including the one song that always gets you through the home stretch.”
I wanted to see how this was technically possible by decoding the information which the foot pod sent to the iPod. Here are my attempts to get an arduino to understand this data traffic.
The Nike adapter is not RFID . The foot pod does transmit a unique ID, which it transmits in the 2.4GHz band. The foot pod transmits a ‘hello world I am XYZ’ every time the user takes a step.
I was interested what this signal looks like and so had a look to see what i could find on the internet. Look here if you want to buy a nike adapter
nike adapter using sparkfun interface
There is an interface unit specifically for the Nike adapter developed by sparkfun which interfaces to the receiver that normally is connected to the iPod.
This is a USB interface and there is a visual basic programme to support it. Very cool. Below you can see how the sparkfun USB interface is connected to the arduino.
The VB software you can download from the sparkfun site.


nike adapter using arduino shield with nrf24L01 interface
This is all very well and it decodes the ID or as it turns out part of the ID for the Nike adapter and displays the rest of the data as an undecoded set of data.
Then i discovered the site of Dmitry Grinberg which solved the riddle of what the data meant and he also used a standard 2.4GHz interface to connect to the Nike adapter sensor.
Of course this was a nice challenge for the arduino to see if this code could be solved using the arduino. Below you can find the code that does just that.
/* This Nike adapter code is released under the following license: - You may use the code for personal (non-commercial) projects free of charge on conditions that: (1) credit is given in any write-ups of the project, - and (2) your code changes (if any) are made public at the same time as the project is. - You must contact Dmitry Grinberg (dmitrygr@gmail.com) if you would like to use this code in a commercial project.
- If you do not agree to these terms, you may not use this code. Nike adapter A lot of hard work went into figuring all this out, so please respect the above. */ #include <SoftwareSerial.h> const char serialNumChars[] = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ"; const int randomTable1[] = {1, 4, 15, 12, 8, 2, 3, 6, 14, 0, 5, 7, 13, 10, 9, 11}; const int randomTable2[] = {0x67, 0x6, 0x51, 0xb8, 0x4a, 0xca, 0x35, 0x3e, 0x72, 0x85, 0x12, 0xd4, 0x8e, 0x9d, 0x1c, 0x23}; char buffer[50]; #define rxPin 3 #define txPin 2 #define ledPin 13 int init1[] = {0xFF,0x55,0x04,0x09,0x07,0x00,0x25,0xC7};
int init2[] = {0xFF,0x55,0x02,0x09,0x05,0xF0}; byte answ1[] = {0xFF,0x55,0x04,0x09,0x00,0x00,0x07,0xEC}; byte answ2[] = {0xFF,0x55,0x04,0x09,0x06,0x00,0x25,0xC8}; int packet[34]; boolean flag = false; int frame[33]; int crc; int i = 1; int j = 1; // set up a new serial port SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin);
byte pinState = 0; byte someChar; int getSrcFlags(const int* packet){ return packet[0]; } int getDstFlags(const int* packet){ return(getSrcFlags(packet) & 0x80) ? packet[1] : 0; }
int getSrcTypeLen(const int* packet){ return packet[0] & 3; } int getSrcAddrLen(const int* packet){ int ret = packet[0]; if(!(ret & 3)) return 0; return ((ret & 0x0C) >> 2) + 1; } int getDstAddrLen(const int* packet){ int ret = getDstFlags(packet); if(!(ret & 3)) return 0; return((ret & 0x0C) >> 2) + 1; }
int getDstTypeLen(const int* packet){ return getDstFlags(packet) & 3; } int* getPayloadPtr(int* packet){ int L1, L2, L3; L1 = getSrcTypeLen(packet); L2 = getSrcAddrLen(packet); L3 = getDstTypeLen(packet) + getDstAddrLen(packet); L3 += L3 ? ((getDstFlags(packet) & 0x10) ? 2 : 1) : 0; return packet + L1 + L2 + L3 + 2; } static long getAddr(const int* ptr, int sz){ int bytes[4] = {0}; switch((sz >> 2) & 3){ case 3: bytes[3] = ptr[3]; case 2: bytes[2] = ptr[2]; case 1: bytes[1] = ptr[1]; case 0: bytes[0] = ptr[0]; } return (((long)(bytes[3])) << 24) | (((long)(bytes[2])) << 16) | (((long)(bytes[1])) << 8) | ((long)(bytes[0])); } static long getSrcAddr(const int* packet){ int t, f = getSrcFlags(packet); t = (f & 3); if(!t) return 0; packet += t + 1; if(f & 0x80) packet++; return getAddr(packet, f); } static long getDstAddr(const int* packet){ int L1, L2, L3; L1 = getDstTypeLen(packet); if(!L1) return 0; L2 = getDstFlags(packet); L3 = getSrcTypeLen(packet) + getSrcAddrLen(packet); packet += L1 + 2 + L3; returngetAddr(packet, L2); }
static int getType(const int* ptr, int len){ int t = 0; switch(len){ case 2: t |= ((int)ptr[1]) << 8; case 1: t |= ptr[0]; } returnt; }
static int getSrcType(const int* packet){ return getType(packet + 2, getSrcTypeLen(packet) - 1);
} static int getDstType(const int* packet){ return getType(packet + getSrcTypeLen(packet) + getSrcAddrLen(packet) + 1, getDstTypeLen(packet) - 1); } int getPayloadLen(int* packet){ int* payload = getPayloadPtr(packet); return packet + 28 - payload; } int getTimingByte(const int* packet){ int L1, L2, L3; L1 = getSrcTypeLen(packet); L2 = getSrcAddrLen(packet); L3 = getDstTypeLen(packet); L3 += getDstAddrLen(packet); if(L3) L3++; return packet[L1 + L2 + L3 + 1]; } void descramblePayload(int* packet){ int* payload; int srcAddr, tR; int t2 = 0, t1 = 0, t3, t9 = 0x23, i; int payloadLen, timingByte; payload = getPayloadPtr(packet); srcAddr = getSrcAddr(packet); payloadLen = getPayloadLen(packet); timingByte = getTimingByte(packet); if(srcAddr & 0x0080UL) t2 |= 4; if(srcAddr & 0x2000UL) t2 |= 2; if(srcAddr & 0x0400UL) t2 |= 1; if(srcAddr & 0x1000UL) t1 |= 4; if(srcAddr & 0x0008UL) t1 |= 2; if(srcAddr & 0x8000UL) t1 |= 1; i = 0; tR = srcAddr >> t1; timingByte += t2; for(i = 0; i < payloadLen; i++, payload++, timingByte++){ timingByte &= 0x0F; t3 = randomTable1[timingByte]; t3 += tR; t3 &= 0x0F; t3 = randomTable2[t3]; t3 ^= t9; t9 = *payload; *payload = t9 - t3; if(t1 <= 11){ t1++; tR >>= 1; } else{ t1 = 0; tR = srcAddr; } } } void calcSN(long sn, int* decodedPacket, char* dst){ //will give partial SN even without a full packet!
int t; sn += 0xFF000000; dst[7] = serialNumChars[sn % 34]; //without the DECODED packet we can only do so much: sn /= 34; dst[6] = serialNumChars[sn % 34]; sn /= 34; dst[5] = serialNumChars[sn % 34]; sn /= 34; t = sn % 1156; sn /= 1156; dst[3] = serialNumChars[(t % 54) / 10]; dst[4] = serialNumChars[(t % 54) % 10]; dst[2] = serialNumChars[t / 54]; dst[1] = serialNumChars[sn % 34]; sn /= 34; if(decodedPacket){ //we can do more int* p = getPayloadPtr(decodedPacket) + 0x10; long x = 0; x = (x << 8) | (p[2] & 0x1F); x = (x << 8) | p[1]&0xFF; x = (x << 8) | p[0]&0xFF; dst[10] = serialNumChars[x % 34]; x /= 34; dst[9] = serialNumChars[x % 34]; x /= 34; dst[8] = serialNumChars[x % 34]; x /= 34; dst[0] = serialNumChars[x % 34]; } else{ dst[0] = '?'; dst[8] = '?'; dst[9] = '?'; dst[10] = '?'; } dst[11] = 0; } int getOnHours(int* payload){ return (((int)(payload[18] & 0xE0)) << 3) + payload[1]&0xFF; }
long getRunningStepCount(int* payload){ long r = 0; r = (r << 8) | payload[12]&0xFF; r = (r << 8) | payload[11]&0xFF; r = (r << 8) | payload[10]&0xFF; return r; } long getWalkingStepCount(int* payload){ long r = 0; r = (r << 8) | payload[6]&0xFF; r = (r << 8) | payload[5]&0xFF; r = (r << 8) | payload[4]&0xFF; return r; } long getLifetimeRunningMiles(int* payload){ // mul by 64 and div by 18947.368115186691 to get actual miles long r = 0; r = (r << 8) | payload[15]&0xFF; r = (r << 8) | payload[14]&0xFF; r = (r << 8) | payload[13]&0xFF; returnr; }
long getLifetimeWalkingMiles(int* payload){ // mul by 64 and div by 15319.148451089859 to get actual miles long r = 0; r = (r << 8) | payload[9]&0xFF; r = (r << 8) | payload[8]&0xFF; r = (r << 8) | payload[7]&0xFF; return r; } int getTc(int* payload){ //what the hell is "Tc" anyways? int r = 0; r = (r << 8) | payload[3]&0xFF; r = (r << 8) | payload[2]&0xFF; return r & 0x7FF; } const char* getDeviceType(int device){ switch(device){ case 0x01: return "Polar heart rate monitor"; break; case 0x06: return "Nike+ foot sensor"; break; case 0x0D: return "Nike+ Amp+ remote"; break; default: return "Unknown device"; } } void printd(const int* d, int sz){ while(sz) { sprintf(buffer,(sz--) ? "%02X" : "%02X", *d++ &0xFF); Serial.print(buffer); } } int hexv(char c){ if(c >= '0' && c <= '9') return c - '0'; if(c >= 'A' && c <= 'F') return c + 10 - 'A'; if(c >= 'a' && c <= 'f') return c + 10 - 'a'; return -1; } void process(int* packet){ char sn[12]; int* payload; descramblePayload(packet); payload = getPayloadPtr(packet); calcSN(getSrcAddr(packet), packet, sn); Serial.print(" FRAME:n"); sprintf(buffer," -> src flags: 0x%02Xn", getSrcFlags(packet));Serial.print(buffer);
sprintf(buffer," -> src type len: 0x%02Xn", getSrcTypeLen(packet));Serial.print(buffer); sprintf(buffer," -> src type: 0x%04Xn", getSrcType(packet));Serial.print(buffer);
sprintf(buffer," -> src addr len: 0x%02Xn", getSrcAddrLen(packet));Serial.print(buffer); sprintf(buffer," -> src addr: 0x%08lXn", getSrcAddr(packet));Serial.print(buffer); sprintf(buffer," -> dst flags: 0x%02Xn", getDstFlags(packet));Serial.print(buffer); sprintf(buffer," -> dst type len: 0x%02Xn", getDstTypeLen(packet));Serial.print(buffer); sprintf(buffer," -> dst type: 0x%04Xn", getDstType(packet));Serial.print(buffer); sprintf(buffer," -> dst addr len: 0x%02Xn", getDstAddrLen(packet));Serial.print(buffer); sprintf(buffer," -> dst addr: 0x%08lXn", getDstAddr(packet));Serial.print(buffer); sprintf(buffer," -> timing byte = 0x%02Xn", getTimingByte(packet));Serial.print(buffer); sprintf(buffer," -> payload len = 0x%02Xn", getPayloadLen(packet));Serial.print(buffer); sprintf(buffer," DATA:n");Serial.print(buffer); sprintf(buffer," ->packet is from device type ");Serial.print(buffer); Serial.println(getDeviceType(payload[0])); if(payload[0] == 0x06){ sprintf(buffer," ->packet is from device ");Serial.print(buffer);Serial.println(sn); sprintf(buffer," ->Nike+ foot pod data:n");Serial.print(buffer);
sprintf(buffer," ->on hours: ");Serial.print(buffer);Serial.println(getOnHours(payload)); sprintf(buffer," ->Tc: ");Serial.print(buffer);Serial.println(getTc(payload));
sprintf(buffer," ->walking steps: %lun", getWalkingStepCount(payload));Serial.print(buffer); sprintf(buffer," ->running steps: %lun", getRunningStepCount(payload));Serial.print(buffer); sprintf(buffer," -> lifetime walking miles: %lun", getLifetimeWalkingMiles(payload));Serial.print(buffer); sprintf(buffer," -> lifetime running miles: %lun", getLifetimeRunningMiles(payload));Serial.print(buffer); } sprintf(buffer," ->raw payload: ");Serial.print(buffer); printd(payload, getPayloadLen(packet)); sprintf(buffer,"n");Serial.print(buffer); } void setup() { // define pin modes for tx, rx, led pins: pinMode(rxPin, INPUT); pinMode(txPin, OUTPUT); pinMode(ledPin, OUTPUT); // set the data rate for the SoftwareSerial port mySerial.begin(57600); Serial.begin(57600); Serial.print("nStart"); do { flag = false; Serial.print("nTransmit --> FF 55 04 09 07 00 25 C7"); for (int i=0;i<8;i++){mySerial.write(init1[i]);} Serial.print("nExpecting <-- FF 55 04 09 00 00 07 EC"); Serial.print("nAnswer is <-- "); for (int i=0;i<8;i++){ someChar = byte (mySerial.read()); if (someChar != answ1[i]) {flag = true;} if (someChar <0x10) Serial.print("0"); Serial.print(someChar,HEX);Serial.print(" "); } Serial.print("nTransmit --> FF 55 02 09 05 F0"); for (int i=0;i<6;i++){mySerial.write(init2[i]);} Serial.print("nExpecting <-- FF 55 04 09 06 00 25 C8"); Serial.print("nAnswer is <-- "); for (int i=0;i<8;i++){ someChar = mySerial.read(); if (someChar != answ2[i]) {flag = true;} if (someChar <0x10) Serial.print("0"); Serial.print(someChar,HEX);Serial.print(" "); } } while (flag == true); Serial.print("nOK now innitialized and listening for steps!!!!n"); } void loop() { if (mySerial.available() > 0) { Serial.print("nNow receiving !!!!!!!!!!!!!!!!!n"); for(int i = 0 ; i < 34 ; i++) //Capture the 32 character frame returned by the iPod receiver { frame[i] = mySerial.read(); if (frame[i] <0x10) Serial.print("0"); Serial.print(frame[i],HEX); } Serial.println(); } else { frame[0]=0;frame[1]=0; } if (frame[0] == 0xFF & frame[1] == 0x55) interpret(); } void toggle(int pinNum) { // set the LED pin using the pinState variable: digitalWrite(pinNum, pinState); // if pinState = 0, set it to 1, and vice versa: pinState = !pinState; } void interpret(){ // test[] contains full string including leading FF 55 and checkbyte for the Nike adapter Serial.print("Data received OK !!!n"); crc = 0; for (int i=2; i<33;i++){crc +=frame[i];}//CRC is all bytes directly after FF 55 to one before checksum for the Nike adapter crc = 0x100 - (crc &0xFF); if (crc == frame[33]) Serial.print("CRC OKn");// do something if CRC not OK
for (int i=5;i<33;i++){ packet[i-5] = frame[i];// move and strip to packet for analysis if (packet[i-5] <0x10) Serial.print("0"); Serial.print(packet[i-5],HEX); } Serial.println(); sprintf(buffer,"Packet %d:n", j);Serial.print(buffer); process(packet); j++; toggle(ledPin); }
And here you can download it.
David Chatting also deposited some code on Dmitry’s site wich contains an arduino library which also decodes the Nike adapter data stream. I have not tested this.
Then there is roberts code in perl and i am sure there will be others if you look.
Innitially I had not yet made the hardware directly with the nrf24L01 components and used the sparkfun interface.
Then i discovered that interfacing with an nrf24L01+ was rather easy as you can use the receiver pictured on the below which is remarcably cheap and also contains an arduino software example.
I have designed a nrf24L01+ prototype shield which has the connector for this receiver. It will be available in the shop once it has been fully tested.
Nike adapter software output
Finally you can see the output from the program when talking to a Nike adapter unit.
Reverse Engineering the Nike iPod Protocol Never Use This Font
iPodLinux Apple Accessory Protocol Nike iPod Linux
Nike Store Nederlands. Polar Wearlink Transmitter
Dmitry Grinberg on the Nike adapter
http://www.cs.washington.edu/research/systems/privacy.html
http://web.student.tuwien.ac.at/~e0026607/ipod_remote/ipod_ap.html
http://ipl.derpapst.eu/wiki/User:Davandron
https://www.sparkfun.com/products/8245
http://smus.com/nike-hacking-with-python/
http://www.apple.com/ipod/nike/ Here you can buy the nike adapter







