// NAME: PN5180-ISO14443.ino
//
// DESC: Example usage of the PN5180 library for the PN5180-NFC Module
//       from NXP Semiconductors.
//
// Copyright (c) 2018 by Andreas Trappmann. All rights reserved.
// Copyright (c) 2019 by Dirk Carstensen.
//
// This file is part of the PN5180 library for the Arduino environment.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public 
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// BEWARE: SPI with an Arduino to a PN5180 module has to be at a level of 3.3V
// use of logic-level converters from 5V->3.3V is absolutly neccessary
// on most Arduinos for all input pins of PN5180!
// If used with an ESP-32, there is no need for a logic-level converter, since
// it operates on 3.3V already.
//
// Arduino <-> Level Converter <-> PN5180 pin mapping:
// 5V             <-->             5V
// 3.3V           <-->             3.3V
// GND            <-->             GND
// 5V      <-> HV
// GND     <-> GND (HV)
//             LV              <-> 3.3V
//             GND (LV)        <-> GND
// SCLK,13 <-> HV1 - LV1       --> SCLK
// MISO,12        <---         <-- MISO
// MOSI,11 <-> HV3 - LV3       --> MOSI
// SS,10   <-> HV4 - LV4       --> NSS (=Not SS -> active LOW)
// BUSY,9         <---             BUSY
// Reset,7 <-> HV2 - LV2       --> RST
//
// ESP-32    <--> PN5180 pin mapping:
// 3.3V      <--> 3.3V
// GND       <--> GND
// SCLK, 18   --> SCLK
// MISO, 19  <--  MISO
// MOSI, 23   --> MOSI
// SS, 16     --> NSS (=Not SS -> active LOW)
// BUSY, 5   <--  BUSY
// Reset, 17  --> RST
// CE, 21     --> CE (active LOW, optional; set PN5180_CE to 21 if used)
//

/*
 * Pins on ICODE2 Reader Writer:
 *
 *   ICODE2   |     PN5180
 * pin  label | pin  I/O  name
 * 1    +5V
 * 2    +3,3V
 * 3    RST     10   I    RESET_N (low active)
 * 4    NSS     1    I    SPI NSS
 * 5    MOSI    3    I    SPI MOSI
 * 6    MISO    5    O    SPI MISO
 * 7    SCK     7    I    SPI Clock
 * 8    BUSY    8    O    Busy Signal
 * 9    GND     9  Supply VSS - Ground
 * 10   GPIO    38   O    GPO1 - Control for external DC/DC
 * 11   IRQ     39   O    IRQ
 * 12   AUX     40   O    AUX1 - Analog/Digital test signal
 * 13   REQ     2?  I/O   AUX2 - Analog test bus or download
 *
 */

//#define WRITE_ENABLED 1

#include <PN5180.h>
#include <PN5180ISO14443.h>

#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_AVR_NANO)

#define PN5180_NSS  10
#define PN5180_BUSY 9
#define PN5180_RST  7

#elif defined(ARDUINO_ARCH_ESP32)

#define PN5180_NSS  16   // swapped with BUSY
#define PN5180_BUSY 5  // swapped with NSS
#define PN5180_RST  17
#define PN5180_CE   -1  // CE (active LOW). Set to 21 if wired, or -1 to disable.

#else
#error Please define your pinout here!
#endif

PN5180ISO14443 nfc(PN5180_NSS, PN5180_BUSY, PN5180_RST);

static const unsigned long kBusyStuckMs = 5;
static const uint8_t kHardResetAfterErrors = 3;

static bool isBusyStuck(unsigned long waitMs) {
  unsigned long started = millis();
  while (millis() - started < waitMs) {
    if (digitalRead(PN5180_BUSY) == LOW) {
      return false;
    }
    delay(1);
  }
  return digitalRead(PN5180_BUSY) == HIGH;
}

static void hardResetCE() {
#if defined(ARDUINO_ARCH_ESP32) && (PN5180_CE >= 0)
  digitalWrite(PN5180_CE, LOW);  // CE active-low
  delay(10);
  digitalWrite(PN5180_CE, HIGH);
  delay(50);
#endif
}

void setup() {
  Serial.begin(115200);
  Serial.println(F("=================================="));
  Serial.println(F("Uploaded: " __DATE__ " " __TIME__));
  Serial.println(F("PN5180 ISO14443 Demo Sketch"));

  nfc.begin();
#if defined(ARDUINO_ARCH_ESP32) && (PN5180_CE >= 0)
  pinMode(PN5180_CE, OUTPUT);
  digitalWrite(PN5180_CE, HIGH);
#endif

  Serial.println(F("----------------------------------"));
  Serial.println(F("PN5180 Hard-Reset..."));
  nfc.reset();

  Serial.println(F("----------------------------------"));
  Serial.println(F("Reading product version..."));
  uint8_t productVersion[2];
  nfc.readEEprom(PRODUCT_VERSION, productVersion, sizeof(productVersion));
  Serial.print(F("Product version="));
  Serial.print(productVersion[1]);
  Serial.print(".");
  Serial.println(productVersion[0]);

  if (0xff == productVersion[1]) { // if product version 255, the initialization failed
    Serial.println(F("Initialization failed!?"));
    Serial.println(F("Press reset to restart..."));
    Serial.flush();
    exit(-1); // halt
  }
  
  Serial.println(F("----------------------------------"));
  Serial.println(F("Reading firmware version..."));
  uint8_t firmwareVersion[2];
  nfc.readEEprom(FIRMWARE_VERSION, firmwareVersion, sizeof(firmwareVersion));
  Serial.print(F("Firmware version="));
  Serial.print(firmwareVersion[1]);
  Serial.print(".");
  Serial.println(firmwareVersion[0]);

  Serial.println(F("----------------------------------"));
  Serial.println(F("Reading EEPROM version..."));
  uint8_t eepromVersion[2];
  nfc.readEEprom(EEPROM_VERSION, eepromVersion, sizeof(eepromVersion));
  Serial.print(F("EEPROM version="));
  Serial.print(eepromVersion[1]);
  Serial.print(".");
  Serial.println(eepromVersion[0]);

  Serial.println(F("----------------------------------"));
  Serial.println(F("Enable RF field..."));
  nfc.setupRF();
}

uint32_t loopCnt = 0;
bool errorFlag = false;
uint8_t errorStreak = 0;

int8_t readCardSerialWithWupa(uint8_t *buffer) {
  uint8_t response[10] = { 0 };
  int8_t uidLength = nfc.activateTypeA(response, 0);
  if (uidLength <= 0) {
    uidLength = nfc.activateTypeA(response, 1);
  }
  if (uidLength <= 0) {
    return uidLength;
  }
  if (uidLength < 4) {
    return 0;
  }
  if ((response[0] == 0xFF) && (response[1] == 0xFF)) {
    uidLength = 0;
  }
  if (response[3] == 0x00) {
    uidLength = 0;
  }
  bool validUID = false;
  for (int i = 1; i < uidLength; i++) {
    if ((response[i + 3] != 0x00) && (response[i + 3] != 0xFF)) {
      validUID = true;
      break;
    }
  }
  if (uidLength == 4) {
    if (response[3] == 0x88) {
      validUID = false;
    }
  }
  if (uidLength == 7) {
    if (response[6] == 0x88) {
      validUID = false;
    }
    if ((response[6] == 0x00) && (response[7] == 0x00) && (response[8] == 0x00) && (response[9] == 0x00)) {
      validUID = false;
    }
    if ((response[6] == 0xFF) && (response[7] == 0xFF) && (response[8] == 0xFF) && (response[9] == 0xFF)) {
      validUID = false;
    }
  }
  if (validUID) {
    for (int i = 0; i < uidLength; i++) buffer[i] = response[i + 3];
    return uidLength;
  }
  return 0;
}


// ISO 14443 loop
void loop() {
  if (errorFlag) {
    uint32_t irqStatus = 0;
    bool busyStuck = isBusyStuck(kBusyStuckMs);
    if (!busyStuck) {
      irqStatus = nfc.getIRQStatus();
      showIRQStatus(irqStatus);
      if (0 == (RX_SOF_DET_IRQ_STAT & irqStatus)) { // no card detected
        Serial.println(F("*** No card detected!"));
      }
    }
    if (busyStuck || (irqStatus == 0xFFFFFFFF) || (errorStreak >= kHardResetAfterErrors)) {
      hardResetCE();
      errorStreak = 0;
    } else {
      errorStreak++;
    }
    nfc.reset();
    nfc.setupRF();

    errorFlag = false;
    delay(10);
  }
  Serial.println(F("----------------------------------"));
  Serial.print(F("Loop #"));
  Serial.println(loopCnt++);
  uint8_t uid[8];
  int8_t uidLen = readCardSerialWithWupa(uid);
  if (uidLen == 0) {
    Serial.println(F("no card found"));
    return;
  }
  if (uidLen < 0) {
    Serial.print(F("Error in readCardSerial: "));
    errorFlag = true;
    return;
  }
  errorStreak = 0;
  Serial.print(F("card serial successful, UID="));
  for (int i=0; i<uidLen; i++) {
    if (uid[i] < 0x10) Serial.print("0");
    Serial.print(uid[i], HEX); 
    if (i + 1 < uidLen) Serial.print(":");
  }
  Serial.println();

  Serial.println(F("----------------------------------"));

  delay(1000);  
}



void showIRQStatus(uint32_t irqStatus) {
  Serial.print(F("IRQ-Status 0x"));
  Serial.print(irqStatus, HEX);
  Serial.print(": [ ");
  if (irqStatus & (1<< 0)) Serial.print(F("RQ "));
  if (irqStatus & (1<< 1)) Serial.print(F("TX "));
  if (irqStatus & (1<< 2)) Serial.print(F("IDLE "));
  if (irqStatus & (1<< 3)) Serial.print(F("MODE_DETECTED "));
  if (irqStatus & (1<< 4)) Serial.print(F("CARD_ACTIVATED "));
  if (irqStatus & (1<< 5)) Serial.print(F("STATE_CHANGE "));
  if (irqStatus & (1<< 6)) Serial.print(F("RFOFF_DET "));
  if (irqStatus & (1<< 7)) Serial.print(F("RFON_DET "));
  if (irqStatus & (1<< 8)) Serial.print(F("TX_RFOFF "));
  if (irqStatus & (1<< 9)) Serial.print(F("TX_RFON "));
  if (irqStatus & (1<<10)) Serial.print(F("RF_ACTIVE_ERROR "));
  if (irqStatus & (1<<11)) Serial.print(F("TIMER0 "));
  if (irqStatus & (1<<12)) Serial.print(F("TIMER1 "));
  if (irqStatus & (1<<13)) Serial.print(F("TIMER2 "));
  if (irqStatus & (1<<14)) Serial.print(F("RX_SOF_DET "));
  if (irqStatus & (1<<15)) Serial.print(F("RX_SC_DET "));
  if (irqStatus & (1<<16)) Serial.print(F("TEMPSENS_ERROR "));
  if (irqStatus & (1<<17)) Serial.print(F("GENERAL_ERROR "));
  if (irqStatus & (1<<18)) Serial.print(F("HV_ERROR "));
  if (irqStatus & (1<<19)) Serial.print(F("LPCD "));
  Serial.println("]");
}
