#include <SparkFun_ST25DV64KC_Arduino_Library.h> // Click here to get the library:  http://librarymanager/All#SparkFun_ST25DV64KC
SFE_ST25DV64KC_NDEF tag;
#define DATASIZE 32
uint8_t password[8] = {0,0,0,0,0,0,0,0}; // Default password is all zeros
uint8_t tagRead[DATASIZE];
char hex[]="0123456789ABCDEF";
#define SCAN_LEN (256)
char* s=NULL;						//for input scanning
char b[SCAN_LEN]="";
int p=0;
#define TEXT_LEN (8192)
char txt[TEXT_LEN];
#define BACKSPACE (8)
char buf[SCAN_LEN]=""; //for generic use
char buf1[SCAN_LEN]=""; //for generic use
uint8_t uid[8];
#define RAWBUFSIZE (8192)
#define LINESIZE (16)
uint8_t rawBuf[RAWBUFSIZE];
uint8_t rawBuf2[RAWBUFSIZE];
#define SSID_LEN 34
char ssid[SSID_LEN]="";
char pass[SSID_LEN]="";
char misc[SSID_LEN]="";
int mimeHdrLen=0;
#define MIMEHDRSIZE (32)
uint8_t mimeHdr[MIMEHDRSIZE];
//elements from library for WiFi NDEF
#define AUTH_COUNT (7)
const char auths[AUTH_COUNT][20]={
  "Open",
  "WPA Personal",
  "Shared",
  "WPA Enterprise",
  "WPA2 Enterprise",
  "WPA2 Personal",
  "WPA/WPA2 Personal"
};
#define ENCRYPT_COUNT (5)
const char encrypts[ENCRYPT_COUNT][20]={
  "None",
  "WEP",
  "TKIP",
  "AES",
  "AES/TKIP"
};

const uint8_t auth_codes[AUTH_COUNT][2]={
  {0x00, 0x01},
  {0x00, 0x02},
  {0x00, 0x04},
  {0x00, 0x08},
  {0x00, 0x10},
  {0x00, 0x20},
  {0x00, 0x22}
};

const uint8_t encrypt_codes[ENCRYPT_COUNT][2]={
  {0x00, 0x01},
  {0x00, 0x02},
  {0x00, 0x04},
  {0x00, 0x08},
  {0x00, 0x0C}
};

const char vCardType[]="text/vcard";

#define MESSAGE_COUNT 4
int messageAdd[MESSAGE_COUNT];
int messageLen[MESSAGE_COUNT];

void dumpArray(uint8_t* a,int n){
  int i,j;
  Serial.println("                       0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");
  for(j=0;j<n;j=j+LINESIZE){  //16 per line
    if(j<16){Serial.write('0');}
    if(j<256){Serial.write('0');}
    if(j<4096){Serial.write('0');}
    Serial.print(j,HEX);
    Serial.write(' ');
    for(i=0;i<LINESIZE;i++){
      if((a[i+j]>31)&&(a[i+j]<127)){  //ascii
        Serial.write(a[i+j]);
      }else{
        Serial.write('.');
      }
    }
    for(i=0;i<LINESIZE;i++){
      Serial.write(' ');
      if (a[i+j]<0x10){Serial.write('0');}          
      Serial.print(a[i+j], HEX);
    }
    Serial.println();
  }
}

int unhex(char c){
    if((c>='0')&&(c<='9')){return c-'0';}
    if((c>='a')&&(c<='f')){return c-'a'+10;}
    if((c>='A')&&(c<='F')){return c-'A'+10;}
    return -1;
}
void showMenu(void){
  Serial.println("~  Reboot microcontroller");
  Serial.println("r  Read NDEF entries (1,2,3,4 for specific entry or blank for all).");
  Serial.println("a  Decode NDEF entries (1,2,3,4 for specific entry or blank for all).");
  Serial.println("c  Decode CC and NDEF headers");
  Serial.println("i  Read UID data.");
  Serial.println("e  Erase EEPROM and write blank NDEF.");  
  Serial.println("w  Write NDEF entries:");  
  Serial.println("wt   Write text NDEF.");  
  Serial.println("ww   Write WiFi NDEF.");  
  Serial.println("wu   Write URI NDEF.");  
  Serial.println("wv   Write vCard NDEF.");  
  Serial.println("wm   Write text MIME type.");  
  Serial.println("wb   Write binary (hex) MIME type.");  
  Serial.println("d  Dump raw EEPROM.");
  Serial.println("h  Write raw hex (haaaadd) to EEPROM");  
  Serial.println("o  Open I2C session");  
  Serial.println("s  Dump system memory.");  
  Serial.println("l  Set RF write lock bits (0=allowed 3=never).");  
}

void showLock(uint8_t bits){
  Serial.printf("Area 1 RF access lock bits are set to %d;",bits);
  switch(bits){
    case 0: Serial.println("read always, write always allowed."); break;
    case 1: Serial.println("read always, write with security code."); break;
    case 2: Serial.println("read always, write with security code."); break;
    case 3: Serial.println("read always, write never allowed."); break;
    default: Serial.println("unknown."); break;
  }
}

void setRFLock(uint8_t bits){
  bits=bits&3;
  showLock((uint8_t)tag.getAreaRfRwProtection(1));
  Serial.printf("Updating to %d.\r\n",bits);
  if(tag.setAreaRfRwProtection(1,(SF_ST25DV_RF_RW_PROTECTION)bits)){
    if(tag.isI2CSessionOpen()){
      Serial.println("Change successful.");
    }else{
      Serial.println("Please open the session with 'o' before changing write access.");
    }
  }else{
    Serial.println("Change failed.");
  }
  showLock((uint8_t)tag.getAreaRfRwProtection(1));
}

bool writeCCfile(int i){  //i=result of getMemoryAreaEndAddress
  bool r=false;
  int s;
  uint8_t cc[8];
  s=i/8;
  if(s<0xFF){ //short
    cc[0]=0xE1;
    cc[1]=0x40;
    cc[2]=s&0xFF;
    cc[3]=0;
    r=tag.writeEEPROM(0,cc,4);
    if(r){
      tag.setCCFileLen(4);
    }
    return r;
  }else{      //long
    cc[0]=0xE2;
    cc[1]=0x40;
    cc[2]=0;
    cc[3]=1;
    cc[4]=0;
    cc[5]=0;
    cc[6]=(s>>8)&0xFF;
    cc[7]=(s>>0)&0xFF;
    r=tag.writeEEPROM(0,cc,8);
    if(r){
      tag.setCCFileLen(8);
    }
    return r;
  }
  return false;
}

bool scanShort(char* s, int n){ //receive a short text entry from user, n is max length
  int d,p;
  p=0;
  bool done=0;
  while(1){
    if(Serial.available()){
      d=Serial.read();
      if((d==3)||(d==27)){  //Ctrl-C/ESC
        s[0]=0;
        Serial.println("\r\nCancelled\r\n");
        return false;//cancel
      }  
      if(d>=' '){
        if(p<n-2){
          s[p]=d;
          p++;
          s[p]=0; //null term
          Serial.write(d);  //echo
        }
      }else if(d==BACKSPACE){  //backspace
          if(p){
            p--;
            s[p]=0; //delete last
            Serial.write(BACKSPACE);    //back up
            Serial.write(' ');  		 //blank
            Serial.write(BACKSPACE);    //back up again
          }
      }else if(d==13){
        Serial.println("");
        return true;  //success
      }
    }
  }
}

char* scanSerial(void){
	int d;
	if(p==0){b[p]=0;} //if data has been read out, reset
	while(Serial.available()){
		d=Serial.read();
    if((d==3)||(d==27)){  //Ctrl-C/ESC
      b[0]=3;
      b[1]=0;
      p=0;    
      Serial.println("\r\nCancelled\r\n");
      return b;//return empty
    }
    if(d>=' '){
      if(p<SCAN_LEN-2){
      b[p]=d;
      p++;
      b[p]=0; //null term
      Serial.write(d);  //echo
      }
    }else{
      if(d==BACKSPACE){  //backspace
        if(p){
          p--;
          b[p]=0; //delete last
          Serial.write(BACKSPACE);    //back up
          Serial.write(' ');  		 //blank
          Serial.write(BACKSPACE);    //back up again
        }
      }
      if(d==13){Serial.println("");p=0;return b;}
    }
	}
  return 0;
}

void eraseAll(void){
  int s,j,c;
  c=0;
  s=tag.getMemoryAreaEndAddress(1); //we're only looking at area 1
  if(s){
    for(j=0;j<LINESIZE;j++){rawBuf[j]=0;} //blank
    for(j=0;j<s+1;j=j+LINESIZE){
      if(tag.writeEEPROM(j,rawBuf,LINESIZE)==false){
        Serial.println("Erase failed.");
        return;
      }
      if((j*10)>((c+1)*(s+1))){ //progress monitor as this can be slow
        c=c+1;
        Serial.print(c*10);
        Serial.println("% done erasing");
        Serial.flush();   //seems to back up otherwise
      }
    }
    Serial.println("Erase complete.");
  }else{
    Serial.println("Nothing to erase.");
  }
}

void readRaw(void){
  int s=0;
  int i,j;
  s=tag.getMemoryAreaEndAddress(1); //we're only looking at area 1
  if(s){
    showLock((uint8_t)tag.getAreaRfRwProtection(1)); 
    Serial.printf("Memory is %d bytes.\r\n",s+1);
    Serial.println("                       0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");
    for(j=0;j<s+1;j=j+LINESIZE){  //16 per line
      tag.readEEPROM(j, rawBuf, LINESIZE);//read LINESIZE bytes into rawBuf
      if(j<16){Serial.write('0');}
      if(j<256){Serial.write('0');}
      if(j<4096){Serial.write('0');}
      Serial.print(j,HEX);
      Serial.write(' ');
      for(i=0;i<LINESIZE;i++){
        if((rawBuf[i]>31)&&(rawBuf[i]<127)){  //ascii
          Serial.write(rawBuf[i]);
        }else{
          Serial.write('.');
        }
      }
      for(i=0;i<LINESIZE;i++){
        Serial.write(' ');
        if (rawBuf[i]<0x10){Serial.write('0');}          
        Serial.print(rawBuf[i], HEX);
      }
      Serial.println();
    }      
  }else{
    Serial.println("Nothing to read.");
  }
}

void readSys(void){
  int i;
  uint8_t d;  
  Serial.println("System Configuration");
  Serial.println("      0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");
  for(i=0;i<0x0908;i++){
    if((i&0xF)==0){
      if(i<16){Serial.write('0');}
      if(i<256){Serial.write('0');}
      if(i<4096){Serial.write('0');}
      Serial.print(i,HEX);
      Serial.write(' ');
    }
    if(tag.st25_io.readSingleByte(SF_ST25DV64KC_ADDRESS::SYSTEM,i,&d)){
      if (d<0x10){Serial.write('0');}          
      Serial.print(d, HEX);
      Serial.write(' ');
    }else{
      Serial.write('-');
      Serial.write('-');
      Serial.write(' ');
    }
    if((i&0xF)==0xF){
      Serial.println();
    }
    if(i==0x23){
      i=0x900-1;
      Serial.println();
    }
  }
  Serial.println();
  Serial.println("Dynamic Configuration");
  Serial.println("      0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F");
  for(i=0x2000;i<0x2008;i++){
    if((i&0xF)==0){
      if(i<16){Serial.write('0');}
      if(i<256){Serial.write('0');}
      if(i<4096){Serial.write('0');}
      Serial.print(i,HEX);
      Serial.write(' ');
    }
    if(tag.st25_io.readSingleByte(SF_ST25DV64KC_ADDRESS::DATA,i,&d)){
      if (d<0x10){Serial.write('0');}          
      Serial.print(d, HEX);
      Serial.write(' ');
    }else{
      Serial.write('-');
      Serial.write('-');
      Serial.write(' ');
    }
  }
  Serial.println();
}

bool confirm(void){
  Serial.println("Are you sure (y/n)?");
  s=scanSerial();
  while(s==NULL){
    s=scanSerial();
  }
  if(s[0]=='y'){return true;}
  return false;
}

void getInfo(void){
  int i;
  int s=0;
  if(tag.getDeviceUID(uid)){
    Serial.println("UID value:");
    for(i=0;i<8;i++){
      Serial.write(hex[(uid[i]>>4)&0xF]);
      Serial.write(hex[(uid[i]>>0)&0xF]);
      if(i<7){Serial.write(':');}
    }
    Serial.println();
    if((uid[0]==0xE0)&&(uid[1]==0x02)){ //ST tag
      switch(uid[2]){
        case 0x24: Serial.println("ST25DV04K-IE found.");break;
        case 0x25: Serial.println("ST25DV04K-JF found.");break;
        case 0x26: Serial.println("ST25DV[16/64]K-IE found.");break;
        case 0x27: Serial.println("ST25DV[16/64]K-JF found.");break;
        case 0x50: Serial.println("ST25DV04KC-IE found.");break;
        case 0x51: Serial.println("ST25DV[16/64]KC-IE found.");break;
        case 0x52: Serial.println("ST25DV04KC-JF found.");break;
        case 0x53: Serial.println("ST25DV[16/64]KC-JF found.");break;
      }
    }
  }else{
    Serial.println("Could not read UID information.");
  }
  s=tag.getMemoryAreaEndAddress(1); //we're only looking at area 1
  if(s){
    Serial.printf("EEPROM is %d bytes.\r\n",s+1);
  }else{
    Serial.println("EEPROM not found.");
  }
}

bool setCC(void){
  uint8_t magic=0;
  tag.readEEPROM(0, &magic, 1); //get magic number
  if(magic==0xE1){              //short4-byte CC, eg ST25DV04KC
    tag.setCCFileLen(4);
    return true;
  }else if(magic==0xE2){        //8-byte CC, eg ST25DV64KC 
    tag.setCCFileLen(8);  
    return true;
  }else{                        //fail, not valid
    Serial.println("No CC found.");
    return false;
  }
}

void readNDEF(int n){
  bool r=0;
  if(setCC()==false){
    return;
  }
  Serial.print(n);
  r=tag.readNDEFText(buf,SCAN_LEN,n,NULL,0);
  if(r){
    Serial.println(":Text record found.");
    Serial.println(buf);
  }else{
    r=tag.readNDEFURI(buf,SCAN_LEN,n);
    if(r){
      Serial.println(":URI record found.");
      Serial.println(buf);
    }else{
      r=tag.readNDEFWiFi(buf,SCAN_LEN,buf1,SCAN_LEN,n);
      if(r){
        Serial.println(":WiFi record found.");
        Serial.print("SSID    :");
        Serial.println(buf);
        Serial.print("Password:");
        Serial.println(buf1);
      }else{
        Serial.println(":No record found.");
      }
    }
  }
}

int getHex(void){   //put hex digits into txt[], report length or -1 on cancel
  while(Serial.available()){Serial.read();}   //any stray LFs
  Serial.println("Enter hex digits (whitespace ignored) and use Ctrl-D or Ctrl-Z to end.");
  Serial.println("Use Ctrl-C or ESC to cancel.");
  int ptr=0;
  txt[0]=0; 
  bool done=0;
  int dPos=0;   //# of nybbles read within multi-nybble hex digit
  int cNybble=0;
  int cByte=0;
  int d;
  while(!done){
 	  if(Serial.available()){
		  d=Serial.read();    
      cNybble=unhex(d);
      if(cNybble!=-1){
        Serial.write(d);
        cByte=cByte*16+cNybble;
        dPos++;
        if(dPos>1){
          Serial.write(BACKSPACE);
          Serial.write(BACKSPACE);
          Serial.write(hex[(cByte>>4)&0xF]);
          Serial.write(hex[(cByte>>0)&0xF]);
          Serial.write(' ');
          if((ptr&0xF)==0xF){Serial.println();}
          txt[ptr]=cByte;
          ptr++;
          if(ptr>(TEXT_LEN-2)){
            Serial.println("Buffer overflow error.");
            return -1;
          }
          cByte=0;
          dPos=0;
        }
      }else if(d==BACKSPACE){ //cancel current byte or delete previous
        if(dPos){
          dPos=dPos-1;
          cByte=cByte/16;
          Serial.write(BACKSPACE);
          Serial.write(' ');
          Serial.write(BACKSPACE);
        }else{
          if(ptr){      //if buffer not empty
            Serial.write(BACKSPACE);  //erase last byte
            Serial.write(BACKSPACE);
            Serial.write(BACKSPACE);
            Serial.write(' ');
            Serial.write(' ');
            Serial.write(' ');
            Serial.write(BACKSPACE);
            Serial.write(BACKSPACE);
            Serial.write(BACKSPACE);
            ptr--;          //step back a byte
          }
        }
      }else if((d==3)||(d==27)){  //Ctrl-C/ESC
        return -1;    //return cancel
      }else if((d==4)||(d==26)){  //Ctrl-D/Ctrl-Z
        return ptr;   //result result
      }else{    //whitespace, end current byte if in process
        if(dPos){   //ignore if nothing entered
          while(dPos--){
            Serial.write(BACKSPACE);
          }
          Serial.write(hex[(cByte>>4)&0xF]);
          Serial.write(hex[(cByte>>0)&0xF]);
          Serial.write(' ');
          if((ptr&0xF)==0xF){Serial.println();}
          txt[ptr]=cByte;
          ptr++;
          if(ptr>(TEXT_LEN-2)){
            Serial.println("Buffer overflow error.");
            return -1;
          }
          cByte=0;
          dPos=0;
        }        
      }
    }  
  } 
  return -1;  //some sort of error
}


char* getTxt(void){ //get a text file, can include line endings etc
  delay(10);
  while(Serial.available()){Serial.read();}   //any stray LFs
  Serial.println("Enter text and use Ctrl-D or Ctrl-Z to end.");
  Serial.println("Use Ctrl-C or ESC to cancel.");
  int ptr=0;
  txt[0]=0; 
  bool done=0;
  int d;
  while(!done){
 	  if(Serial.available()){
		  d=Serial.read();      
      if((d>=' ')||(d==13)||(d==10)){
        if(ptr<(TEXT_LEN-2)){
          Serial.write(d);
          txt[ptr]=d;
          ptr++;
          txt[ptr]=0;
        }
      }else if(d==BACKSPACE){
        if(ptr){
          ptr--;
          txt[ptr]=0; //delete last
          Serial.write(BACKSPACE);    //back up
          Serial.write(' ');  		 //blank
          Serial.write(BACKSPACE);    //back up again
        }
      }else if((d==3)||(d==27)){  //Ctrl-C/ESC
        return NULL;
      }else if((d==4)||(d==26)){  //Ctrl-D/Ctrl-Z
        return txt;
      }
    }   
  }
  return NULL;
}

void writeText(char* t){
  uint16_t a=tag.getCCFileLen(); 
  if(setCC()==false){
    return;
  }
  if(tag.writeNDEFText(t,&a,true,true,"en")){
    Serial.println("Text NDEF record written.");
  }else{
    Serial.println("Write failed.");
  }
}

void writeWiFi(void){
  int i,a,e;  //counter, auth index, encryption index
  uint16_t add;
  a=0;
  e=0;
  setCC();
  add=tag.getCCFileLen(); 
  Serial.println("Enter SSID name.");
  if(scanShort(ssid,SSID_LEN)){
    Serial.println("Enter password.");
    if(scanShort(pass,SSID_LEN)){
      Serial.println("Choose Auth (6 is default):");
      for(i=0;i<AUTH_COUNT;i++){
        Serial.write(i+'1');
        Serial.write(':');
        Serial.println(auths[i]);
      }
      if(scanShort(misc,SSID_LEN)){
        a=misc[0]-'1';
        if((a>=0)&&(a<AUTH_COUNT)){
          Serial.println("Choose Encryption (4 is default):");
          for(i=0;i<ENCRYPT_COUNT;i++){
            Serial.write(i+'1');
            Serial.write(':');
            Serial.println(encrypts[i]);
          }
          if(scanShort(misc,SSID_LEN)){
            e=misc[0]-'1';
            if((e>=0)&&(e<ENCRYPT_COUNT)){
              //all parts entered
              Serial.println(ssid);
              Serial.println(pass);
              Serial.println(auths[a]);
              Serial.println(encrypts[e]);
              if(tag.writeNDEFWiFi(ssid,pass,&add,true,true,auth_codes[a],encrypt_codes[e])){
                Serial.println("WiFi write successful.");
                return;
              }else{
                Serial.println("WiFi write failed.");
                return;
              }
            }
          }
        }
      }
    }
  }
  Serial.println("Cancelled.");   //fallen through at some point
}

void writeURI(void){
  int pref,i;
  uint16_t add;
  setCC();
  add=tag.getCCFileLen(); 
  Serial.println("Enter URI type/prefix:");
  for(i=0;i<=SFE_ST25DV_NDEF_URI_ID_CODE_URN_NFC;i++){
    Serial.printf("%4d  %s\r\n",i,tag.getURIPrefix(i));
  }
  if(scanShort(misc,SSID_LEN)){
    pref=atoi(misc);
    Serial.println("Enter URI (without prefix):");
    if(scanShort(txt,TEXT_LEN)){
      Serial.print(tag.getURIPrefix(pref));
      Serial.println(txt);
      if(tag.writeNDEFURI(txt,pref,&add,true,true)){
        Serial.println("URI written successfully.");
        return;
      }else{
        Serial.println("URI write failed.");
        return;
      }
    }else{
      Serial.println("Cancelled.");
      return;
    }
  }else{
    Serial.println("Cancelled.");
    return;
  }
  Serial.println("Cancelled.");   //fallen through at some point
}

int getSize(uint8_t* d){
  if(d[0]==0xFF){
    return d[1]*256+d[2];
  }else{
    return d[0];
  }
}

void decodeMessage(int a,int s){  //address,length
  uint8_t mb,me,cf,sr,il,tnf;
  unsigned int typeLen,payLen,idLen,i,miscLen;
  int ptr=0;
  int mType=0;
  typeLen=0;
  payLen=0;
  idLen=0;
  if(a<0){Serial.println("No record.");return;}
  if(s>RAWBUFSIZE){Serial.println("Size error.");return;}
  if(s==0){Serial.println("Size error.");return;}
  if(tag.readEEPROM(a, rawBuf, s)){    //CC can be up to 8 bytes
    Serial.printf("NDEF record found at 0x%04x.\r\n",a);
    mb=(rawBuf[ptr]>>7)&1;
    me=(rawBuf[ptr]>>6)&1;
    cf=(rawBuf[ptr]>>5)&1;
    sr=(rawBuf[ptr]>>4)&1;
    il=(rawBuf[ptr]>>3)&1;
    tnf=(rawBuf[ptr]>>0)&7;    
    ptr++;
    Serial.println(" MB ME CF SR IL TNF");
    Serial.printf("%3d%3d%3d%3d%3d ",mb,me,cf,sr,il);
    switch(tnf){
      case 0: Serial.println("Empty"); break;
      case 1: Serial.println("NFC type"); break;
      case 2: Serial.println("MIME type"); break;
      case 3: Serial.println("URI type"); break;
      case 4: Serial.println("External type"); break;
      case 6: Serial.println("Unchanged"); break;
      case 7: Serial.println("Reserved"); break;
      default : Serial.println("Unknown"); break; //also type 5
    }
    typeLen=rawBuf[ptr];
    ptr++;
    if(sr){
      payLen=rawBuf[ptr];
      ptr++; 
    }else{
      payLen=rawBuf[ptr++];
      payLen=payLen*256+rawBuf[ptr++];
      payLen=payLen*256+rawBuf[ptr++];
      payLen=payLen*256+rawBuf[ptr++];
    }
    if(il){
      idLen=rawBuf[ptr++];
    }
    switch(tnf){
      case 0:
        Serial.println("Empty record.");
        break;
      case 1:
        if(typeLen==1){
          switch(rawBuf[ptr++]){
            case 'T': Serial.println("Text type.");mType='T';break;
            case 'U': Serial.println("URI type.");mType='U';break;
          }
        }else{
          ptr=ptr+typeLen;  
          mType=0;
          Serial.println("Type unknown.");
        }
        break;
      default: ptr=ptr+typeLen;  break;
    }
    Serial.printf("%d bytes of type, %d bytes of payload, %d bytes of ID.\r\n",typeLen,payLen,idLen);
    ptr=ptr+idLen;
    //ptr at start of payload
    if(tnf==1){
      if(mType=='T'){
        if(rawBuf[ptr]&0x80){
          Serial.print("UTF-16, ");
        }else{
          Serial.print("UTF-8, ");        
        }
        miscLen=rawBuf[ptr]&0x3F;
        for(i=0;i<miscLen;i++){   //language code
          Serial.write(rawBuf[ptr+i+1]);
        }
        Serial.printf(", text is %d bytes.\r\n",payLen-miscLen-1);
        for(i=3;i<payLen;i++){
          Serial.write(rawBuf[ptr+i]);
        }
        Serial.println();
      }else if(mType=='U'){
        miscLen=rawBuf[ptr]; //URL prefix
        Serial.printf("URI Prefix code: %d:%s\r\n",miscLen,tag.getURIPrefix(miscLen));
        for(i=1;i<payLen;i++){
          Serial.write(rawBuf[ptr+i]);
        }
        Serial.println();
      }
    }else if(tnf==2){ //MIME
      Serial.print("MIME type:");
      for(i=0;i<typeLen;i++){
        Serial.write(rawBuf[ptr+i-typeLen]);
      }
      Serial.println();
      for(i=0;i<payLen;i++){
        Serial.write(rawBuf[ptr+i]);
      }
      Serial.println();
    }
    ptr=ptr+payLen;
    if(me){Serial.println("ME flag ends message.");}
    if(cf){Serial.println("CF flag ends message.");}
  }else{
    Serial.println("Read failed.");
  }
}

void getMessageAdds(uint8_t* msg, int s, int offset){ //to allow random access
  uint8_t mb,me,cf,sr,il,tnf;
  unsigned int typeLen,payLen,idLen,i,miscLen;
  int ptr=0;
  bool done=0;
  int mType=0;
  int mCount=0;
  while((messageAdd[mCount]>=0)&&(mCount<MESSAGE_COUNT)){mCount++;} //find empty slots
  if(mCount>=MESSAGE_COUNT){return;}
  while(!done){    
    typeLen=0;
    payLen=0;
    idLen=0;
    mType=0;
    mb=(msg[ptr]>>7)&1;
    me=(msg[ptr]>>6)&1;
    cf=(msg[ptr]>>5)&1;
    sr=(msg[ptr]>>4)&1;
    il=(msg[ptr]>>3)&1;
    tnf=(msg[ptr]>>0)&7;
    //Serial.printf("NDEF record found at 0x%04x.\r\n",ptr+offset);
    messageAdd[mCount]=ptr+offset;
    if(mCount>=MESSAGE_COUNT){return;}
    ptr++;
    typeLen=msg[ptr];
    ptr++;
    if(sr){
      payLen=msg[ptr];
      ptr++; 
    }else{
      payLen=msg[ptr++];
      payLen=payLen*256+msg[ptr++];
      payLen=payLen*256+msg[ptr++];
      payLen=payLen*256+msg[ptr++];
    }
    if(il){
      idLen=msg[ptr++];
    }
    if(tnf){  //TNF 0 has no type length
      ptr=ptr+typeLen;  
    }
    ptr=ptr+idLen;
    //ptr at start of payload
    ptr=ptr+payLen;
    if(me){done=1;}//Serial.println("ME flag ends message.");}
    if(cf){done=1;}//Serial.println("CF flag ends message.");}
    if(!done){if(ptr>=s){done=1;Serial.println("End of message found without terminator.");}}
    messageLen[mCount]=ptr+offset-messageAdd[mCount];
    mCount++;
  }
}

void decodeCCQuiet(){
  int ccSize=0;
  int cPtr=0;
  int nSize=0;
  bool done=0;
  int eSize=0;
  int i;
  for(i=0;i<MESSAGE_COUNT;i++){messageAdd[i]=-1;messageLen[i]=0;} //clear
  eSize=tag.getMemoryAreaEndAddress(1);
  if(eSize<1){
    Serial.println("End of memory reached.");
    return;
  }
  if(tag.readEEPROM(0, tagRead, 8)){    //CC can be up to 8 bytes
    if(tagRead[0]==0xE1){
      ccSize=4;
      nSize=tagRead[2]*8;  
      Serial.println("4 byte CC found.");    
    }else if(tagRead[0]==0xE2){
      ccSize=8;
      Serial.println("8 byte CC found.");    
      nSize=tagRead[6]*2048+tagRead[7]*8;
    }else{
      Serial.println("CC Not present.");
      return;
    }
  }else{
    Serial.println("Read failed.");
    return;
  }
  cPtr=ccSize;  
  while(!done){
    if(cPtr>eSize-2){
      Serial.println("End of memory reached.");
      return;
    }
    //Serial.printf("Looking for TLV at %d\r\n",cPtr);
    if(tag.readEEPROM(cPtr, rawBuf, 4)){ // T and L of TLV can be up to 4 bytes
      nSize=getSize(&rawBuf[1]);
      if(nSize<1){
        Serial.println("Empty TLV found");
        return;        
      }
      switch(rawBuf[0]){
        case 0x00:
          cPtr++; //skip padding          
          break;
        case 0x03:
          if(rawBuf[1]==0xFF){
            cPtr=cPtr+4;
          }else{
            cPtr=cPtr+2;
          }
          //Serial.printf("NDEF message %d bytes starting at 0x%04x:\r\n",nSize,cPtr);
          if(nSize>0){
            if(tag.readEEPROM(cPtr, rawBuf, nSize)){
              getMessageAdds(rawBuf,nSize,cPtr);
              cPtr=cPtr+nSize;
            }else{
              Serial.println("Read failed.");
              return;
            }
          }
          break;
        case 0xFE:
          //Serial.println("NDEF terminator found.");
          done=1;
          break;
        default:
          //Serial.println("Unknown type.");
          done=1;
          break;
      }
    }else{
      Serial.println("Read failed.");
      return;
    }
  }
}

//mid-level decoder
void scanMessage(uint8_t* msg, int s, int offset){
  uint8_t mb,me,cf,sr,il,tnf;
  unsigned int typeLen,payLen,idLen,i,miscLen;
  int ptr=0;
  bool done=0;
  int mType=0;
  while(!done){
    typeLen=0;
    payLen=0;
    idLen=0;
    mType=0;
    mb=(msg[ptr]>>7)&1;
    me=(msg[ptr]>>6)&1;
    cf=(msg[ptr]>>5)&1;
    sr=(msg[ptr]>>4)&1;
    il=(msg[ptr]>>3)&1;
    tnf=(msg[ptr]>>0)&7;
    Serial.printf("NDEF record found at 0x%04x.\r\n",ptr+offset);
    ptr++;
    Serial.println(" MB ME CF SR IL TNF");
    Serial.printf("%3d%3d%3d%3d%3d ",mb,me,cf,sr,il);
    switch(tnf){
      case 0: Serial.println("Empty"); break;
      case 1: Serial.println("NFC type"); break;
      case 2: Serial.println("MIME type"); break;
      case 3: Serial.println("URI type"); break;
      case 4: Serial.println("External type"); break;
      case 6: Serial.println("Unchanged"); break;
      case 7: Serial.println("Reserved"); break;
      default : Serial.println("Unknown"); break; //also type 5
    }
    typeLen=msg[ptr];
    ptr++;
    if(sr){
      payLen=msg[ptr];
      ptr++; 
    }else{
      payLen=msg[ptr++];
      payLen=payLen*256+msg[ptr++];
      payLen=payLen*256+msg[ptr++];
      payLen=payLen*256+msg[ptr++];
    }
    if(il){
      idLen=msg[ptr++];
    }
    switch(tnf){
      case 0:
        Serial.println("Empty record.");
        break;
      case 1:
        if(typeLen==1){
          switch(msg[ptr++]){
            case 'T': Serial.println("Text type.");mType='T';break;
            case 'U': Serial.println("URI type.");mType='U';break;
          }
        }else{
          ptr=ptr+typeLen;  
          mType=0;
          Serial.println("Type unknown.");
        }
        break;
      default: ptr=ptr+typeLen;  break;
    }
    Serial.printf("%d bytes of type, %d bytes of payload, %d bytes of ID.\r\n",typeLen,payLen,idLen);
    ptr=ptr+idLen;
    //ptr at start of payload
    if(tnf==1){
      if(mType=='T'){
        if(msg[ptr]&0x80){
          Serial.print("UTF-16, ");
        }else{
          Serial.print("UTF-8, ");        
        }
        miscLen=msg[ptr]&0x3F;
        for(i=0;i<miscLen;i++){   //language code
          Serial.write(msg[ptr+i+1]);
        }
        Serial.printf(", text is %d bytes.\r\n",payLen-miscLen-1);
        for(i=3;i<payLen;i++){
          Serial.write(msg[ptr+i]);
        }
        Serial.println();
      }else if(mType=='U'){
        miscLen=msg[ptr]; //URL prefix
        Serial.printf("URI Prefix code: %d:%s\r\n",miscLen,tag.getURIPrefix(miscLen));
        for(i=1;i<payLen;i++){
          Serial.write(msg[ptr+i]);
        }
        Serial.println();
      }
    }else if(tnf==2){ //MIME
      Serial.print("MIME type:");
      for(i=0;i<typeLen;i++){
        Serial.write(msg[ptr+i-typeLen]);
      }
      Serial.println();
      for(i=0;i<payLen;i++){
        Serial.write(msg[ptr+i]);
      }
      Serial.println();
    }
    ptr=ptr+payLen;
    if(me){done=1;Serial.println("ME flag ends message.");}
    if(cf){done=1;Serial.println("CF flag ends message.");}
    if(!done){if(ptr>=s){done=1;Serial.println("End of message found without terminator.");}}
  }
}

void decodeCC(void){
  int ccSize=0;
  int cPtr=0;
  int nSize=0;
  bool done=0;  
  Serial.println("Capability Container:");
  if(tag.readEEPROM(0, tagRead, 8)){
    if(tagRead[0]==0xE1){
      ccSize=4;
      nSize=tagRead[2]*8;      
      Serial.printf("Short (4 byte) CC version %d.%d, %d bytes available for NDEF.\r\n",(tagRead[1]>>6)&0x3,(tagRead[1]>>4)&0x3,nSize);
    }else if(tagRead[0]==0xE2){
      ccSize=8;
      nSize=tagRead[6]*2048+tagRead[7]*8;
      Serial.printf("Long (8 byte) CC version %d.%d, %d bytes available for NDEF.\r\n",(tagRead[1]>>6)&0x3,(tagRead[1]>>4)&0x3,nSize);
    }else{
      Serial.println("Not present.");
      return;
    }
  }else{
    Serial.println("Read failed.");
    return;
  }
  cPtr=ccSize;
  while(done==0){
    if(cPtr>8191){done=1;}  //failsafe
    Serial.printf("TLV record at 0x%04x:\r\n",cPtr);
    if(tag.readEEPROM(cPtr, tagRead, 4)){ // T and L of TLV can be up to 4 bytes
      nSize=getSize(&tagRead[1]);
      if(nSize<1){
        Serial.println("Empty TLV found");
        return;        
      }
      switch(tagRead[0]){
        case 0x00:
          cPtr++; //skip padding          
          break;
        case 0x03:
          if(tagRead[1]==0xFF){
            cPtr=cPtr+4;
          }else{
            cPtr=cPtr+2;
          }
          Serial.printf("NDEF message %d bytes starting at 0x%04x:\r\n",nSize,cPtr);
          if(tag.readEEPROM(cPtr, rawBuf, nSize)){
            scanMessage(rawBuf,nSize,cPtr);
            cPtr=cPtr+nSize;
          }else{
            Serial.println("Read failed.");
            return;
          }
          break;
        case 0xFE:
          Serial.println("NDEF terminator.");
          done=1;
          break;
        default:
          Serial.println("Unknown type.");
          done=1;
          break;
      }
    }else{
      Serial.println("Read failed.");
      return;
    }
  }
  Serial.println("\r\nScan complete.");
}

void createMIME(const uint8_t* f, const char* t, int s){ //create headers for MIME type t (null term) file f of size s
  uint8_t hdr=0;
  int p=0;
  int tlvLen=0;   //excluding V, this can be up to 4 bytes
  int typeLen=0;  //this is stored in 1 byte
  int payLen=0;   //this can be stored in 1 or 4 bytes depending on SR
  int payLenB=0; 
  int ndefLen=0; 
  //need to work backwards to work out the size for headers
  mimeHdrLen=0;  
  // TLV (length) Head TypeLen PayloadLen Type Payload
  //V of TLV record
  payLen=s;
  typeLen=strlen(t);
  if(payLen<256){
    payLenB=1;
    hdr=0xD2;   //MB/ME/SR/MIME type
  }else{
    payLenB=4;
    hdr=0xC2;   //MB/ME/MIME type, ie not short record
  }
  ndefLen=2+payLenB+typeLen+payLen; //header + type length +
  if(ndefLen<255){
    tlvLen=2;
  }else{
    tlvLen=4;
  }
  mimeHdr[0]=0x03;  //NDEF TLV message
  if(tlvLen==2){
    mimeHdr[1]=ndefLen&0xFF;
    p=2;      //next
  }else{
    mimeHdr[1]=0xFF;  //flag longer #
    mimeHdr[2]=(ndefLen>>8)&0xFF;
    mimeHdr[3]=(ndefLen>>0)&0xFF;
    p=4;      //next
  }
  mimeHdr[p++]=hdr;
  mimeHdr[p++]=typeLen&0xFF;
  if(payLenB==1){
    mimeHdr[p++]=payLen&0xFF;
  }else{
    mimeHdr[p++]=(ndefLen>>24)&0xFF;
    mimeHdr[p++]=(ndefLen>>16)&0xFF;
    mimeHdr[p++]=(ndefLen>>8)&0xFF;
    mimeHdr[p++]=(ndefLen>>0)&0xFF;
  }
  mimeHdrLen=p;
  //mimeHdr is written after CC, is followed by type then payload and then 0xFE as terminator
}

void writeMime(const char* t, int contentLen){  //type in t, 
  int res;
  int i;
  res=tag.getMemoryAreaEndAddress(1);
  i=mimeHdrLen+strlen(t)+contentLen;
  if(res<i){
    Serial.println("MIME file too large for EEPROM.");
    Serial.printf("%d + %d + %d = %d < %d\r\n",mimeHdrLen,strlen(t),contentLen,i,res);
    return;
  }
  Serial.println("Writing a MIME file to EEPROM.");
  if(res){
    if(writeCCfile(res)){
      i=tag.getCCFileLen();
      res=tag.writeEEPROM(i,mimeHdr,mimeHdrLen);
      if(res){
        i=i+mimeHdrLen;
        res=tag.writeEEPROM(i,(uint8_t*)t,strlen(t));
        if(res){
          i=i+strlen(t);
          res=tag.writeEEPROM(i,(uint8_t*)txt,contentLen);
          if(res){
            Serial.println("MIME write successful.");return;  
          }else{
            Serial.println("MIME content write failed.");return;  
          }
        }else{
          Serial.println("MIME type write failed.");return;
        }
      }else{
          Serial.println("MIME header write failed.");return;
      }
    }else{
      Serial.println("CC write failed.");return;
    }
  }else{
    Serial.println("CC write failed.");return;
  }  
}

void writeVcard(void){
  int i;
  int res;
  bool done=0;
  char f[32]="";
  char l[32]="";
  char t[32]="";
  char cp[32]="";
  char wp[32]="";
  char hp[32]="";
  char e[128]="";
  char w[128]="";
  char a1[64]=""; //address elements; mail stop
  char a2[64]=""; //Unit etc
  char a3[64]=""; //street address
  char a4[64]=""; //town/locality
  char a5[64]=""; //region/state
  char a6[32]=""; //postal code
  char a7[64]=""; //country
  char cust[256]="";  //custom field
  for(i=0;i<TEXT_LEN;i++){txt[i]=0;}  //blank
  Serial.println("Enter first name:");
  if(!scanShort(f,32)){return;}//prints Cancelled message
  Serial.println(f);
  Serial.println("Enter last name:");
  if(!scanShort(l,32)){return;}//prints Cancelled message
  Serial.println(l);
  Serial.println("Enter title:");
  if(!scanShort(t,32)){return;}//prints Cancelled message
  Serial.println(t);
  Serial.println("Enter work phone number (blank to omit):");
  if(!scanShort(wp,32)){return;}//prints Cancelled message
  Serial.println(wp);
  Serial.println("Enter home phone number (blank to omit):");
  if(!scanShort(hp,32)){return;}//prints Cancelled message
  Serial.println(hp);
  Serial.println("Enter mobile phone number (blank to omit):");
  if(!scanShort(cp,32)){return;}//prints Cancelled message
  Serial.println(cp);
  Serial.println("Enter email address (blank to omit):");
  if(!scanShort(e,128)){return;}//prints Cancelled message
  Serial.println(e);
  Serial.println("Enter website/URL (blank to omit):");
  if(!scanShort(w,128)){return;}//prints Cancelled message
  Serial.println(w);
  Serial.println("Enter Address mail stop (blank all to omit):");
  if(!scanShort(a1,64)){return;}//prints Cancelled message
  Serial.println(a1);
  Serial.println("Enter Address unit/suite (blank all to omit):");
  if(!scanShort(a2,64)){return;}//prints Cancelled message
  Serial.println(a2);
  Serial.println("Enter Address street number and name (blank all to omit):");
  if(!scanShort(a3,64)){return;}//prints Cancelled message
  Serial.println(a3);
  Serial.println("Enter Address town or locality (blank all to omit):");
  if(!scanShort(a4,64)){return;}//prints Cancelled message
  Serial.println(a4);
  Serial.println("Enter Address region or state (blank all to omit):");
  if(!scanShort(a5,64)){return;}//prints Cancelled message
  Serial.println(a5);
  Serial.println("Enter Address postal code (blank all to omit):");
  if(!scanShort(a6,32)){return;}//prints Cancelled message
  Serial.println(a6);
  Serial.println("Enter Address country (blank all to omit):");
  if(!scanShort(a7,64)){return;}//prints Cancelled message
  Serial.println(a7);
  sprintf(&txt[strlen(txt)],"BEGIN:VCARD\nVERSION:2.1\n");
  sprintf(&txt[strlen(txt)],"N:%s;%s;;%s;\n",l,f,t);
  sprintf(&txt[strlen(txt)],"FN:%s %s %s\n",t,f,l);
  if(cp[0]){sprintf(&txt[strlen(txt)],"TEL;CELL:%s\n",cp);}
  if(wp[0]){sprintf(&txt[strlen(txt)],"TEL;WORK:%s\n",wp);}
  if(hp[0]){sprintf(&txt[strlen(txt)],"TEL;HOME:%s\n",hp);}
  if(e[0]){sprintf(&txt[strlen(txt)],"EMAIL:%s\n",e);}
  if(w[0]){sprintf(&txt[strlen(txt)],"URL:%s\n",w);}
  if(a1[0]||a2[0]||a3[0]||a4[0]||a5[0]||a6[0]||a7[0]){
    sprintf(&txt[strlen(txt)],"ADR;TYPE=pref:%s;",a1);
    sprintf(&txt[strlen(txt)],"%s;",a2);
    sprintf(&txt[strlen(txt)],"%s;",a3);
    sprintf(&txt[strlen(txt)],"%s;",a4);
    sprintf(&txt[strlen(txt)],"%s;",a5);
    sprintf(&txt[strlen(txt)],"%s;",a6);
    sprintf(&txt[strlen(txt)],"%s\n",a7);
  }
  //enter one or more custom fields
  while(!done){
    Serial.println("Enter Custom field including field name followed by colon (blank to skip):");
    if(!scanShort(cust,256)){return;}//prints Cancelled message
    Serial.println(cust);
    if(cust[0]){
      sprintf(&txt[strlen(txt)],"%s\n",cust);
      cust[0]=0;
    }else{
      done=1;
    }
  }
  sprintf(&txt[strlen(txt)],"END:VCARD\n");
  Serial.println(txt);  
  createMIME((uint8_t*)txt,vCardType,strlen(txt));
  sprintf(&txt[strlen(txt)],"\xFE");  //terminator NDEF
  Serial.println("Write vCard?");
  if(confirm()){
    writeMime(vCardType,strlen(txt));
  }else{
    Serial.println("Write cancelled.");return;
  }
  //dumpArray((uint8_t*)txt,256);
  //dumpArray(mimeHdr,32);
}


void writetextMime(void){   //ascii compatible MIME object
  int i;
  bool res=0;
  //vcard content in txt, vcard type in buf
  for(i=0;i<TEXT_LEN;i++){txt[i]=0;}          //blank
  for(i=0;i<SCAN_LEN;i++){buf[i]=0;}          //blank
  Serial.println("Enter MIME type (type/subtype):");
  if(!scanShort(buf,SCAN_LEN)){return;}       //prints Cancelled message
  Serial.println("Enter content:");
  res=getTxt();     //uses txt
  if(res){
    Serial.println(buf);
    Serial.println(txt);
    createMIME((uint8_t*)txt,buf,strlen(txt));
    sprintf(&txt[strlen(txt)],"\xFE");  //terminator NDEF
    Serial.println("Write MIME object?");
    if(confirm()){
      writeMime(buf,strlen(txt));    //reports completion
    }else{
      Serial.println("Write cancelled.");return;
    }
  }else{
    Serial.println("Entry cancelled.");return;
  }
}

void writeBinaryMime(void){   //binary MIME object entered by HEX codes
  int i;
  bool res=0;
  int txtLen=0;
  //vcard content in txt, vcard type in buf
  for(i=0;i<TEXT_LEN;i++){txt[i]=0;}          //blank
  for(i=0;i<SCAN_LEN;i++){buf[i]=0;}          //blank
  Serial.println("Enter MIME type (type/subtype):");
  if(!scanShort(buf,SCAN_LEN)){return;}       //prints Cancelled message
  Serial.println("Enter content:");
  txtLen=getHex();
  Serial.println();
  if(txtLen>-1){
    Serial.print("MIME type:");
    Serial.println(buf);
    dumpArray((uint8_t*)txt,txtLen);
    Serial.printf("%d bytes in content.\r\n",txtLen);
    createMIME((uint8_t*)txt,buf,txtLen);
    txt[txtLen]=0xFE;
    txtLen++;  //append terminator
    Serial.println("Write MIME object?");
    if(confirm()){
      writeMime(buf,txtLen);    //reports completion
    }else{
      Serial.println("Write cancelled.");return;
    }
  }else{
    Serial.println("Cancelled.");
  }
}