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 -sport-kit

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 -01-LNike adapter Serial

 

Nike adapter schematicNike adapter IMG_0220

 

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.

Show »

 

/* 

 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 = falseint 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.

Nike adapter RF24012

 

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 shield nrf24L01+brd                                                                  Nike adapter shield schematic nrf24L01+

Nike adapter software output

Finally you can see the output from the program when talking to a Nike adapter unit.

Nike iPod dump

 

Links:

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

Leave a Reply


two × = 12