
/*
  ESP8266 RFID Reader - MQTT with OLED display for WiFi and Enthernet

  The aim of the project is, to have a RFID-cardreader which has Wifi and Ethernet capabilities at low costs.
  The hardware therfor consists of low cost modules like: 
  - ESP8266 or ESP32 
  - 0.96" OLED module (ESP8266) or e-ink module (ESP32)
  - RC522 RFID module
  - ENC28j60 Ethernet module

  With only a minimum of additional components they provide a full functioning hardware

  General function:
  The RC522 reads a RFID token and sends the UID via MQTT to a broker. The data is sent in JASON format. For security reasons, the tokens can be encrypted 
  and a timestamp can be stored on the card wich changes each time the card is read. The old and new timestamp will be transmitted 
  to the broker as well (if activated).
  The hardware forsees an input (ADC in case of the EPS8266), where a button can (must, for the first configuration) be connected. During boot, this button can be pressed to enter 
  into configuration mode. This creates an access point (AP). By connecting to this access point via a browser (http://192.168.4.1/ ), all connection
  parameters of the unit can be changed. Here the connection mode (WiFI or ethernet) can be set aswell.

  Seceurity issues:
  PLEASE BE AWARE
  1. the used Mifare Classic technology has been hacked and RFID-tokens can be copied including the UID. Do not use this 
  technology for sensitive applications. Use DESfire or Mifare Ultralight C instead.
  2. all connection data are stored in the EEPROM. To prevent someone from reading these data, they need to be encrypted.
  Only the ESP32 is capable of encrypting the EEPROM (allthough this encryption can be hacked in a day by skillfull people).
  When using a ESP8266 use a separate network for MQTT or secure the devices against theft. 

    
  Version description:
  Version 01
    initial Version
  Version 01
    include ESP32 as option
  Version 6
    show text messages.  

*/

#ifdef ESP32
  #include <WiFi.h>                         // ESP32
  #include <WebServer.h>                    // ESP32
  #include <ESPmDNS.h>                      // ESP32
  #include <pgmspace.h>
  #include <GxEPD.h>                        // E-Paper
  //#include <GxGDE0213B1/GxGDE0213B1.h>      // 2.13" b/w
  //#include <GxGDEH0213B72/GxGDEH0213B72.h>  // 2.13" b/w new panel
  #include <GxGDEH0213B73/GxGDEH0213B73.h>  // E-Paper
  #include <Fonts/FreeMonoBold9pt7b.h>
  #include <Fonts/FreeMonoBold12pt7b.h>
  #include <Fonts/FreeMonoBold18pt7b.h>
  #include <Fonts/FreeMonoBold24pt7b.h>
  #include <GxIO/GxIO_SPI/GxIO_SPI.h>
  #include <GxIO/GxIO.h>
#else
  #include <ESP8266WiFi.h>                  // EPS8266
  #include <ESP8266WebServer.h>             // ESP8266
  #include <ESP8266mDNS.h>                  // ESP8266
  // #include <ESP8266HTTPUpdateServer.h>   /// 
#endif

#include <WiFiClient.h>
#include <UIPEthernet.h>
#include <PubSubClient.h>
#include <SPI.h>
#include <Wire.h>
#include <EEPROM.h>
#include <MFRC522.h>
#include <SSD1306Wire.h>
#include <ArduinoJson.h>
#include <DES.h>


// #include <esp_system.h>                      // for retreiving the MAC adress


// Definition der Pins     
#ifdef ESP32
  #define SS_PIN   32    // GPIO17 
  #define RST_PIN  33    // GPIO16            
  #define BEEP_PIN 27    // GPIO16
  #define BUTTON_PIN 39  // GPIO39
  #define CS 19          // ESP32
  
  #define SPI_MOSI 23
  #define SPI_MISO -1
  #define SPI_CLK 18

  #define ELINK_SS 5
  #define ELINK_BUSY 4
  #define ELINK_RESET 16
  #define ELINK_DC 17
#else
//  #define SS_PIN   15   // GPIO15    -> uncomment this for the old PCB
  #define SS_PIN   2      // GPIO2
  #define RST_PIN  0      // GPIO00            
  #define BEEP_PIN 16     // GPIO16
  #define BUTTON_PIN 13   // GPIO2      -> uncomment this for the old PCB
  #define CS 15           // ESP8266
#endif

#define HSPI_MOSI 15
#define HSPI_MISO 2
#define HSPI_SCLK 14


// to make some messages easier to read
#define CONNECT_TO_WIFI 0
#define CONNECT_TO_MQTT 1
#define PUSH_BUTTON     2
#define IN_AP_MODE      3
#define CONNECT_TO_LAN  4
#define ERROR_RFID      5
#define MQTT_RFID       6
#define WRONG_RFID_TYPE 7
#define ERROR_DHCP      8


int Old_PCB=0;        


/* configuration variables for your wifi -> will be loaded from the EEPROM !!! */ 
char ssid[32] = "WLAN";                             // WLAN ID 
char password[64] = "Passwort";                     // WLAN password for WPA PSK 

/* configuration variables MQTT-client mode -> will be loaded from the EEPROM !!!*/     
char host[128] = "rfid_reader";                     // Name at the MQTT Broker
char broker[16] = "192.168.2.13";                   // IP adresse of the MQTT broker in your local (W)LAN
char mqttUser[32] = "mqttuser";                     // to access the the MQTT broker, if broker is configured that way 
char mqttPass[32] = "mqttpassword";                 // to access the the MQTT broker, if broker is configured that way
char ReaderID[5] ; //= "000";                       // identifyer for the reader.
char subTopic[18] = "/cmnd/reader/";          // MQTT Topic where to find commands for the reader + ReaderID + 0x00
char ConType [3] = "0";

/* Configuration for access point mode -> can be anything you like*/
const char *ssidAP = "RFID-Reader";                 // modify to your needs
const char *passwordAP = "Passwort";                // modify to your needs

const char* vers = "V02";                           // Software Version
boolean StartUpMode = 0;                            // to start either as access point (0) and MQTT-client (1)
boolean OTA=0;                                      // is there currently an OTA process running?
byte Wifi_Ether = 0;                                // to connect either via Wifi (0) or Ethernet (1) -> using a Byte to store it in EEPROM easily
byte RFID_Mod;                                      // what card type to be read -> using a Byte to store it in EEPROM easily
byte Format_Mod;                                    // what format to send data -> using a Byte to store it in EEPROM easily

uint64_t IV=0;
byte C_key[24]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; 

//= { 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42, 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, 0x46, 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42};

unsigned long oldMillis;                            // used to identify time outs

int maxMessage = 18;

String lastLine1, lastLine2;  // to save previous displayed messages
boolean linesSaved = 0;       // no previous message has been saved
int fsize=0;                  // current font size (for E-paper)

String Message_Disp[] = {"verfuegbar",                      // 0 - equipment available      // 0
                      "reserviert",                         // 0 - equipment reserved       // 1
                      "reserviert ab",                      // 13 - reserved starting from  // 2
                      "in Benutzung",                       // 0 - equipment in use         // 3
                      "freigegeben",                        // 0 - access granted           // 4
                      "Anmeld. fehlgeschlagen",             // 8 - login error              // 5
                      "Nutzungsgebuehr",                    // 15 - usage fee               // 6
                      "Nutzung nicht erlaubt",              // 13 - operation not allowed   // 7
                      "Nutzung auf eigenem Risiko",         // 11 - operation at own risk   // 8
                      "gesperrt durch",                     // 14 - equipment locked by     // 9
                      "freizugeben durch",                  // 11 - to be released by       // 10
                      "ausserhalb der Nutzungzeit",         // 15 - outside ooperating hours// 11
                      "Nutzungsdauer",                      // 13 - duration of use         // 12
                      "abgemeldet",                         // 0 - logged out               // 13
                      "abschalten in",                      // 13 - equipment will lock down// 14
                      "Karte gesperrt",                     // 0 - card locked              // 15
                      "Karte unbekannt",                    // 0 - card unknown             // 16
                      "Berechtigung endet",                 // 12 - training to expire      // 17
                      "Ruhemodus",                          // 0 - sleep mode               // 18
                      "Aufsicht bestaetigen"};              // 8 - confirm presence         // 19

int SplitAt[]={0,0,13,0,0,8,15,13,11,15,11,14,14,0,13,0,0,12,0,8};

/*
String Message_Com[] = {"1",                                // equipment available
                      "2",                                  // equipment reserved
                      "3",                                  // reserved starting from
                      "4",                                  // equipment in use
                      "5",                                  // access granted
                      "6",                                  // login error
                      "7",                                  // usage fee
                      "8",                                  // operation not allowed
                      "9",                                  // operation at own risk
                      "10",                                 // equipment locked by
                      "11",                                 // to be released by
                      "12",                                 // outside ooperating hours
                      "13",                                 // duration of use
                      "14",                                 // logged out
                      "15"                                  // equipment will lock down
                      "16",                                 // card locked
                      "17",                                 // card unknown
                      "18",                                 // training to expire
                      "19",
                      "20"};                                // sleep mode
*/


//-----------  E-Paper-Stuff
#ifdef ESP32
  SPIClass eSPI(HSPI);
  GxIO_Class io(eSPI, /*CS=5*/ ELINK_SS, /*DC=*/ ELINK_DC, /*RST=*/ ELINK_RESET);
  GxEPD_Class EPaper(io, /*RST=*/ ELINK_RESET, /*BUSY=*/ ELINK_BUSY);
#endif


EthernetClient ethClient;               // in case of Ethernet connection 
WiFiClient wifiClient;                  // in case of Wifi connection
PubSubClient MQTTclient;                // The MQTT Client

#ifdef ESP32     
  WebServer httpServer(80);             // ESP32
#else
  ESP8266WebServer httpServer(80);      // ESP8266    
#endif


// instance for the RFID-Reader     
MFRC522 rfid(SS_PIN, RST_PIN); 
MFRC522::MIFARE_Key key;
MFRC522::StatusCode status;
DES des;

//Starting page for parameter configuration
const char INDEX_HTML[] =
"<!DOCTYPE HTML>"
"<html>"
"<head>"
"<meta content=\"text/html; charset=ISO-8859-1\""
" http-equiv=\"content-type\">"
"<meta name = \"viewport\" content = \"width = device-width, initial-scale = 1.0, maximum-scale = 1.0, user-scalable=0\">"
"<title>Verbindungsdaten RFID-Reader</title>"
"<style>"
"\"body { background-color: #808080; font-family: Arial, Helvetica, Sans-Serif; Color: #000000; }\""
"</style>"
"</head>"
"<body>"
"<h1>Verbindungsdaten RFID-Reader</h1>"
"<FORM action=\"/\" method=\"post\">"
"<P>"
"<label>ssid:&nbsp;</label><input maxlength=\"30\" name=\"ssid\"><br>"
"<label>Passwort:&nbsp;</label><input maxlength=\"30\" name=\"Passwort\"><br>"
"<label>BrokerIP:&nbsp;</label><input maxlength=\"15\" name=\"BrokerIP\"><br>"
"<label>ReaderID:&nbsp;</label><input maxlength=\"3\" name=\"ReaderID\"><br>"
"Connection<BR>"
"<INPUT type=\"radio\" name=\"ConType\" value=\"1\">Ehternet<BR>"
"<INPUT type=\"radio\" name=\"ConType\" value=\"0\">WiFi<BR>"
"RFID-Type<BR>"
"<INPUT type=\"radio\" name=\"RFIDType\" value=\"0\">Mifare Classic<BR>"
"<INPUT type=\"radio\" name=\"RFIDType\" value=\"1\">Mifare Classic + Timestamp<BR>"
"<INPUT type=\"radio\" name=\"RFIDType\" value=\"2\">Mifare DESFire<BR>"
"<INPUT type=\"radio\" name=\"RFIDType\" value=\"3\">Mifar Ultralight C<BR>"
"Data Package Format<BR>"
"<INPUT type=\"radio\" name=\"DatFormat\" value=\"0\">JSON<BR>"
"<INPUT type=\"radio\" name=\"DatFormat\" value=\"1\">Text <BR>"
"<INPUT type=\"submit\" value=\"Send\"> <INPUT type=\"reset\">"
"</P>"
"</FORM>"
"</body>"
"</html>";


// Initialize the OLED display using Wire library
#ifdef ESP32
  SSD1306Wire  display(0x3c, 21, 22); // ESP32 SSD1306Wire display(0x3c, SDA, SCL);
#else
  SSD1306Wire  display(0x3c, 4, 5);   // ESP8266 SSD1306Wire display(0x3c, SDA, SCL);
#endif


     
     
void setup(void){
  
  pinMode(BEEP_PIN,   OUTPUT);      // buzzer pin
  pinMode(BUTTON_PIN,   INPUT);     // button pin
  pinMode(CS,   OUTPUT);            // CS-Pin of the ENC28J60 Module 

  digitalWrite(CS, HIGH);           // -> needs to be set, otherwise MISO will be pulled LOW
  
  #ifdef ESP32
    SPI.begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI, CS);         // for the LAN Module and the RC522 module
    eSPI.begin(SPI_CLK, SPI_MISO, SPI_MOSI, ELINK_SS);      // For the e-Paper Module
  #else
    SPI.begin();
  #endif
  
  rfid.PCD_Init();                  // RC522 initialize
  // rfid.PCD_PerformSelfTest();
     
  Serial.begin(115200);
  Serial.println();
  Serial.println("Booting...");

  des.init(C_key,IV);  

  #ifdef ESP32
    EPaper.init(); // enable diagnostic output on Serial
    EPaper.setRotation(3);
    EPaper.fillScreen(GxEPD_WHITE);
    EPaper.setTextColor(GxEPD_BLACK);
    EPaper.setFont(&FreeMonoBold12pt7b);
    EPaper.setCursor(0, 0);
    EPaper.update();
  #endif
  
  display.init();                   // initialize display
  display.clear();
  display.flipScreenVertically();
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  display.display();

  EEPROM.begin(512);                // initialize EEPROM 
  
  StatusToDisplay(PUSH_BUTTON);
  
  // displayPushButton();              // message shown during the boot up
  delay(5000);                      // dealy, to give time to press the button

  int value=800;
  if(Old_PCB){
    if(digitalRead(BUTTON_PIN)==LOW)
      value=0;
  }
  else{
    value = analogRead(A0);
  }

  Serial.print("value: ");
  Serial.println(value);
  
  if (value > 512)                    // button pressed? -> attention: button signal is inverted
    setupAP();                      // -> configuration
  else
    setupMQTT();                    // -> start MQTT-Client 

  oldMillis=millis();

}

void setupAP(void){
  StartUpMode = 0;

  Serial.println("Configuring access point...");
  WiFi.mode(WIFI_AP); //Access Point mode IP: 192.168.4.1
  
  // Serial.println("Setting soft-AP configuration ... ");
  // WiFi.softAPConfig(ap_local_IP, ap_gateway, ap_subnet);
  Serial.println("Setting soft-AP ... ");
  WiFi.softAP(ssidAP, passwordAP);
  Serial.print("Soft-AP IP address = ");
  Serial.println(WiFi.softAPIP());
  
  //Configuring the web server
  httpServer.on("/", handleRootAP);
  httpServer.onNotFound(handleNotFoundAP);
  httpServer.begin();
  Serial.println("HTTP server started");
  StatusToDisplay(IN_AP_MODE);
}

void setupMQTT(void){
  uint8_t mac[6];
  
  StartUpMode = 1;
  readConfig();                                           // loading parameter set from EEPROM
  MQTTclient.setBufferSize(356);                          // 256 APDU size + topic + JSON chars 
  if(Wifi_Ether){                                         // either start via ethernet or Wifi
    // esp_read_mac(mac, ESP_MAC_WIFI_STA);               // reading teh MAC address of the unit
    StatusToDisplay(CONNECT_TO_LAN);
    
    WiFi.macAddress(mac);
    Ethernet.init(CS);                               // set the correct pin
    if(Ethernet.begin(mac) == 0) {
      Serial.println(F("Ethernet configuration using DHCP failed"));
      StatusToDisplay(ERROR_DHCP);
      while(1);
    }
    else{
      Serial.println(F("Ethernet configuration using DHCP OK"));
    }
    Serial.print("set Client to Ethernet: ");
    MQTTclient.setClient(ethClient);
    //WiFi.forceSleepBegin();  // TODO
    //WiFi.mode(WIFI_OFF);      // TODO
    Serial.println("OK");
  }
  else{
    StatusToDisplay(CONNECT_TO_WIFI);
    WiFi.mode(WIFI_STA);                                    // only station, no access point
    WiFi.begin(ssid, password);
    Serial.print("set Client to Wifi: ");
    MQTTclient.setClient(wifiClient);
    Serial.println("OK");
    Serial.print("MAC address: ");
    Serial.println(WiFi.macAddress());
  }
  StatusToDisplay(CONNECT_TO_MQTT);
  Serial.print("set MQTT-Server..."); 
  MQTTclient.setServer(broker,1883);                      //for using local broker
  Serial.println(" OK");
  Serial.print("setting callback...");
  MQTTclient.setCallback(callback);
  Serial.println(" OK");
  //Serial.print("starting MDNS...");   
  //MDNS.begin(host);
  //Serial.println(" OK");
  Serial.println("Up and running!");

/*  Generate the Mifare Keys (key A and B)
 *  Standard key is FFFFFFFFFFFFh for unformatted RFID tokens
 *  Special key can be configured and will be loaded from EEPROM (To-Do)
 */
  
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }
  StatusToDisplay(MQTT_RFID);
}


     
void loop(void){
  if(StartUpMode){                          // if in MQTT-Mode
    if(!MQTTclient.connected()) {           // if not connected
      connect();
    }
    MQTTclient.loop();                      // do all the MQTT-stuff
    delay(10);                              
    if(!OTA)                                // search only for new cards if there is no OTA process ongoing
      handleRFID();                         // if a card was found and data was sent  
    delay(50);
  }

  httpServer.handleClient();
}
     

/*
 * ------------------------------------------------------------------------------------------------------------------
 *                                              Code for Access Point mode
 */

/*
 * Show configuration page
 */
void handleRootAP() {

  if (httpServer.hasArg("ssid")&& httpServer.hasArg("Passwort")&& httpServer.hasArg("BrokerIP")&&httpServer.hasArg("ReaderID") ) {//If all form fields contain data call handelSubmit()
    handleSubmit();
  }
  else {
    String s = INDEX_HTML;
    httpServer.send(200, "text/html", s);
  }
  
}

/*
 * Shows the data, to be saved in the EEPROM
 */

void handleSubmit(){
  
  // TO DO - check if entries are too long and promt an error!
  
  String response="<p>SSID: ";
  response += httpServer.arg("ssid");                         // max 32 characters
  response +="<br>";
  response +="Password: ";
  response +=httpServer.arg("Passwort");                      // max 64 characters
  response +="<br>";
  response +="The broker IP-Address is: ";
  response +=httpServer.arg("BrokerIP");                      // max 16 characters
  response +="<br>";
  response +="Hostname is: ";                                // max 128 characters 
  response +="rfid_reader_";                                  // TO DO -> complete argument for the Hostname 
  response +=httpServer.arg("ReaderID");                      // max 3 characters
  response +="<br>";
  response +="Topic name is: ";
  response +="/rfid_reader/";                                 // TO DO -> complete argument for the topic
  response +=httpServer.arg("ReaderID");
  response +="<br>";
  response +="connection via: ";
  if(httpServer.arg("ConType")=="1"){
    response +="Ethernet";                              
    Wifi_Ether=1;
  }
  else{
    response +="Wifi"; 
    Wifi_Ether=0;                             
  }
  response +="<br>";
  response +="RFID-ID: ";
  if (httpServer.arg("RFIDType")=="1"){
    response +="Mifare Classic + Timestamp"; 
    RFID_Mod=1;                             
  }
  else if (httpServer.arg("RFIDType")=="2"){
    response +="Mifare DESFire"; 
    RFID_Mod=2;                             
  }  
  else if (httpServer.arg("RFIDType")=="3"){
    response +="Mifar Ultralight C"; 
    RFID_Mod=3;                             
  }
  else{                                             // default
    response +="Mifare Classic";                              
    RFID_Mod=0;
  }
  response +="<br>";
  response +="MQTT-Data Format: ";
  if (httpServer.arg("DatFormat")=="1"){
    response +="Text"; 
    Format_Mod=1;                             
  }
  else{
    response +="JSON"; 
    Format_Mod=0; 
  }
  
  
  response +="</P><BR>";
  response +="<H2><a href=\"/\">go home</a></H2><br>";

  // show data
  httpServer.send(200, "text/html", response);
  
  // write data to EEPROM  
  write_to_Memory(String(httpServer.arg("ssid")),String(httpServer.arg("Passwort")),String(httpServer.arg("BrokerIP")),String(httpServer.arg("ReaderID")), Wifi_Ether, RFID_Mod, Format_Mod );
}


/*
 * Write data to the EEPROM
 * The data is merged to a single string, separated by "\r"
 * TO DO -> the complete string is then encrypted.
 * resulting String is wrtitten to the EEPROM
 * 
 */
void write_to_Memory(String s,String p,String i, String g, byte w, byte r, byte f){
 EEPROM.write(0,w);       // how to connect    
 EEPROM.write(1,r);       // what type of RFID tokens are used
 EEPROM.write(2,f);       // how to format MQTT-data
 s+="\r";                 // SSID max 32 characters
 s+=p;                    // password max 64 characters
 s+="\r";                  
 s+=i;                    // Broker-IP 16 charaacters
 s+="\r";                 
 s+=g;                    // ID max 3 characters
 s+="\r"; 
 write_EEPROM(s,3);
 EEPROM.commit();
}

//Daten in den EEPROM schreiben
void write_EEPROM(String x,int pos){
  for(int n=pos;n<x.length()+pos;n++){
     EEPROM.write(n,x[n-pos]);
  }
}

//In case something went wrong with the requestt
void handleNotFoundAP(){
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += httpServer.uri();
  message += "\nMethod: ";
  message += (httpServer.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += httpServer.args();
  message += "\n";
  for (uint8_t i=0; i<httpServer.args(); i++){
    message += " " + httpServer.argName(i) + ": " + httpServer.arg(i) + "\n";
  }
  message +="<H2><a href=\"/\">go home</a></H2><br>";
  httpServer.send(404, "text/plain", message);
}



/*---------------------------------------------------------------------------------------------------------------------------------------
 *                              Code for MQQT Client mode
 */

/*
 * connect to Wifi / MQTT-broker 
 */
void connect() {
  if(!Wifi_Ether){                                          // only if connected by Wifi !!!
    StatusToDisplay(CONNECT_TO_WIFI);
    while(WiFi.waitForConnectResult() != WL_CONNECTED){
      WiFi.begin(ssid, password);
      Serial.println("WiFi failed, retrying.");
    }
    
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  }   
  while (!MQTTclient.connected()) {
    StatusToDisplay(CONNECT_TO_MQTT);
    Serial.println("Reconnecting MQTT...");
    if (!MQTTclient.connect(ReaderID, mqttUser, mqttPass)) {
      Serial.print("failed, rc=");
      Serial.println(MQTTclient.state());
    }
  }
  byte topicLenght=sizeof(subTopic);
  topicLenght=topicLenght-1;                    
  for (int i=0; i<3 ;i++){
    subTopic[(topicLenght-4)+i]=ReaderID[i];  
  }
  subTopic[topicLenght]=0x00; 
  MQTTclient.subscribe(subTopic);
  Serial.println(F("connected!"));
  Serial.print(F("subscribed to topic: "));
  Serial.println(subTopic);
  StatusToDisplay(MQTT_RFID);
}


/*
 * answer to HTTP-requests
 */

void handleRootMQTT() {
  httpServer.send(200, "text/plain", "It works!!!");
}

/*
 * Handle received MQTT Messages
 * examples:
 * show message on Display  - JSON object = {"Cmd": "message", "MssgID": 4 , "ClrTxt":"Hello World" , "AddnTxt":"3:00"}       // mID as int
 * communicate with reader  - JSON object = {"Cmd": "sendPICC", "data": "0123456789ABCDEF"}                                   // data in ASCII
 * communicate with reader  - JSON object = {"Cmd": "haltPICC"}                                                              
 * recieve auth Key         - JSON object = {"Cmd": "Key", "data": 0123456789ABCDEF}                                          // data in ASCII 
 * Confirm User attendence  - JSON object = {"Cmd": "ConfirmUser"}
 */

void callback(char* topic, byte* payload, unsigned int length) {
int mID;  
int len;

  Serial.print("Received message [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
  
  DynamicJsonDocument doc(256);
  deserializeJson(doc, (char*)payload);  
  JsonObject root = doc.as<JsonObject>();


  
  if(!root["Cmd"].isNull()) {
    if(root["Cmd"] == "message") {                                // Message to display
      if(!root["MssgID"].isNull()) {
        mID=root["MssgID"];
        MessageToDisplay(mID, root["ClrTxt"], root["AddnTxt"]);   
      }
    }
    if(root["Cmd"] == "sendPICC") {                               // command for OTA communication with the PICC
      if(!root["data"].isNull()) {
        APDUtrancieve( root["data"]);
      }
    }
    if(root["Cmd"] == "haltPICC") {                               // command for ending OTA communication with the PICC
      rfid.PICC_HaltA();
      OTA=0;
    }
    if(root["Cmd"] == "Key"){                                     // set the auth key for Mifare Ultralight C or NTAG216 
      setAuthKey( root["data"]);     
    }
    if(root["Cmd"] == "ConfirmUser") {                            // start beeping and flashing -> user needs to confirm its presence at the equipment
      
    }
  }
}
     

/*
 * Reading the RFID Token
 * 
 * When a token is recocnized....
 * - read the UID and SAK-value
 * 
 * Mifare Classic without time stamp (Mode=0)
 * Mifare Classic Mode + timestamp (Mode=1)
 * DESFire - Mode (Mode=2) 
 * Ultralight C - Mode (Mode=3) 

 * 
 * CAUTION: The total MQTT message (topic + payload) must not be too long, otherwise the data and the connection will be lost!!!!
 * 
 */
     
void handleRFID(void) {

  // Try to access a RFID-token
  if (!rfid.PICC_IsNewCardPresent()) return;               // is there a RFID token at all?
  if (!rfid.PICC_ReadCardSerial()) return;                 // can I read it?

  Serial.print(F("Mod = "));
  Serial.println(RFID_Mod);

  if((RFID_Mod==0)||(RFID_Mod==1)){
    if((rfid.uid.sak != 0x08)&&(rfid.uid.sak != 0x09)&&(rfid.uid.sak != 0x18)){
      Serial.println(F("should be Mifare Classic, but isn't"));
      popRFIDerror();
    }
    else{
      if(!RequestMifare()){                   // test for Ultralight "C"
        popRFIDerror();
      }
    }
  }
  if(RFID_Mod==2){
    if(rfid.uid.sak != 0x20){                        // should be Mifare DESFire, but isn't
      Serial.println(F("should be Mifare DESFire, but isn't"));
      popRFIDerror();      
    }
    else{
      if(!HandleDESFire()){
        popRFIDerror();
      }
    }
  }
  if(RFID_Mod==3){
    if(rfid.uid.sak != 0x00){                        // should be Mifare Ultralight C, but isn't
      Serial.println(F("should be Mifare Ultralight C, but isn't"));
      popRFIDerror();
    }
    else{
      if(!RequestAuthUltralightC()){                   // test for Ultralight "C"
        popRFIDerror();
      }
    }
  }
  if(!OTA){                                           // close only when OTA there is no OTA process ongoing
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
  }
}


boolean HandleDESFire(void){
byte buffer[67];                                      // buffer for reading data max 63 bytes per frame + 2 bytes status + 2 bytes CRC 
byte bufferSize=sizeof(buffer);     
char UID[21];
    
  byte2charArray(rfid.uid.uidByte, UID, rfid.uid.size);
  Serial.print(F("UID: "));
  Serial.println(UID); 
    
    // start with ATS- Build command buffer
  buffer[0] = 0xE0; //PICC_CMD_RATS;
  buffer[1] = 0x50; // FSD=64, CID=0
  rfid.PCD_CalculateCRC(buffer, 2, &buffer[2]);
  status = rfid.PCD_TransceiveData(buffer, 4, buffer, &bufferSize, NULL, 0, true);
  if (status != MFRC522::STATUS_OK) {
    Serial.print(F("Failed to get ATS: "));
    Serial.println(rfid.GetStatusCodeName(status));
    return 0;
  }

  bufferSize=sizeof(buffer);

  byte cid=0x00;
  
  buffer[0] = 0xD0 | (cid & 0x0F);
  buffer[1] = 0x11;                   // PPS0: PPS1 will follow
  buffer[2] = 0x00;                   // PPS1: set to 106kBaud in both directions DSI and DRI
  rfid.PCD_CalculateCRC(buffer, 3, &buffer[3]);
  status = rfid.PCD_TransceiveData(buffer, 5, buffer, &bufferSize, NULL, 0, true);
  if (status == MFRC522::STATUS_OK) {
    // This is how my MFRC522 is by default.
    // Reading https://www.nxp.com/documents/data_sheet/MFRC522.pdf it seems CRC generation can only be disabled in this mode.
    // if (pps1 == 0x00) {  // =buffer[2] TODO why?
      rfid.PCD_WriteRegister(MFRC522::TxModeReg, 0x00);
      rfid.PCD_WriteRegister(MFRC522::RxModeReg, 0x00);
    //}
  }
  else
    return 0;

  OTA=1;                                // Over The Air (OTA) Communication enabled
  sendUID(UID, NULL, NULL);
  return 1;
}

/*
 * APDUtrancieve(char* in, char* out, byte len)
 * Sends the string "in" to the PICC and provides "out" as the response.
 * "in" and "out" are formatted as readable chars, and converted internally to bytes.
 * "len" provides the length of the provided string (twice the len of the real bytes transfered)
 * communicate with reader  - JSON object = {"Cmd": "readPICC", "data": "0123456789ABCDEF"}
 */

boolean APDUtrancieve(String in){
byte buffer[67];                                                                    // buffer for reading data max 63 bytes per frame + 2 bytes status + 2 bytes CRC 
byte len;
byte len_send;
char JSONmessageBuffer[200];                                                        // contains the full JSON message 
char topic[32];

  for(len=0; len<126; len++){                     
    if(in[len]==0x00)
      break; 
  }
  
  buffer[0]=0x0A;       // PCB - Protocol Control Byte 
  buffer[1]=0x00;       // CID - Card ID, always assumed as 0x00 as multicard reading is not implemented
                        // NAD - Node Address - not implemented and not required according to the setting of Protocol Control Byte (PCB)
  char2byteArray(in, &buffer[2], len);
  len=len/2;
  len=len+2;            // two first bytes
  status = rfid.PCD_CalculateCRC(buffer, len, &buffer[len]);
  if (status != rfid.STATUS_OK) {
    Serial.print(F("CRC-gen failed: "));
    Serial.println(rfid.GetStatusCodeName(status));
    return 0;
  }
    
  Serial.print(F("APDU-Command: "));
  dump_byte_array(&buffer[2], len-2);
  
  len_send=len+2;
  len=sizeof(buffer);  
  status = rfid.PCD_TransceiveData(buffer, len_send, buffer, &len, NULL, 0, true);
  if (status != rfid.STATUS_OK) {
    Serial.print(F("APDU-command failed: "));
    Serial.println(rfid.GetStatusCodeName(status));
    return 0;
  }

  len=len-4;                                  // two bytes at the beginning (status-bytes) and the two CRC bytes
  
  Serial.print(F("APDU-Response: "));
  dump_byte_array(&buffer[2], len);

  // communicate with reader  - JSON object = {"Cmd": "readPICC", "data": "0123456789ABCDEF"}                                   // data in ASCII

  snprintf(JSONmessageBuffer, sizeof(JSONmessageBuffer), R"({"Cmd":"readPICC","data":")");                                    // 27 characters
  byte2charArray(&buffer[2], &JSONmessageBuffer[26], len); 
  // org: snprintf(JSONmessageBuffer, sizeof(JSONmessageBuffer), R"({"UID":"%s","KeyOld":"%s","KeyNew":"%s"})", UID, KeyOld, KeyNew); 

  len=len*2;                    // as we have now a char array 00-AA instead of bytes 0x00-0xFF

  JSONmessageBuffer[26+len]='"';
  JSONmessageBuffer[26+len+1]='}';
  JSONmessageBuffer[26+len+2]=0x00;
    
  Serial.print(F("payload :"));
  Serial.println(JSONmessageBuffer);
  
  snprintf(topic, sizeof(topic), "/rfid_reader/%s", ReaderID);      // generat a topic with the ReaderID
  Serial.print(F("topic :"));
  Serial.println(topic);
  Serial.println("");
  MQTTclient.publish(topic, JSONmessageBuffer);                     // send it to the broker 

  return 1;
  
}

/*
 * Handle Mifare Classic cards
 */

boolean RequestMifare(void){
int i;

char UID [21];
char KeyOld [33];               // 16 bytes in HEX = 32 + 1 for 0x00
char KeyNew[33];                // 16 bytes in HEX = 32 + 1 for 0x00
byte sector         = 1;    
byte blockAddr      = 4;        // where to find the data
byte trailerBlock   = 7;        // block for autentification

byte buffer[18];                // buffer for reading data
byte size = sizeof(buffer);
unsigned long KeyGen;

  Serial.println(printHex(rfid.uid.uidByte, rfid.uid.size));        // found somthing  

  byte2charArray(rfid.uid.uidByte, UID, rfid.uid.size);
  Serial.print(F("UID: "));
  Serial.println(UID);

  if(RFID_Mod==1){                                                        // Timestamp enabled 
 
    Serial.println(F("Authenticating using key A..."));
    status = (MFRC522::StatusCode) rfid.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(rfid.uid));
    if (status != MFRC522::STATUS_OK) {
      Serial.print(F("PCD_Authenticate() failed: "));
      Serial.println(rfid.GetStatusCodeName(status));
      popRFIDerror();
      return 0;
    }

    // reading the first block
    Serial.print(F("Reading data from block ")); Serial.print(blockAddr);
    Serial.println(F(" ..."));
    status = (MFRC522::StatusCode) rfid.MIFARE_Read(blockAddr, buffer, &size);
    if (status != MFRC522::STATUS_OK) {
      Serial.print(F("MIFARE_Read() failed: "));                  
      Serial.println(rfid.GetStatusCodeName(status));
      popRFIDerror();
      rfid.PCD_PerformSelfTest();                                         // reset the reader
      return 0;
    }
    else{                                       // OK, Card can be accesse 
      Serial.print(F("Data in block ")); 
      Serial.print(blockAddr); 
      Serial.println(F(":"));
      dump_byte_array(buffer, 16); 
      
      byte2charArray(buffer, KeyOld, 8);            // Only the first 8 Bytes are used, otherwise the MQTT message will be too long
       
      KeyGen=millis();                          // generate a a new time stamp
      
      for(i=0; i<16; i++){                      // convert it to a byte array
        buffer[i]=KeyGen&0xFF;
        KeyGen=KeyGen/10;
      }

      byte2charArray(buffer, KeyNew, 8);            // Only the first 8 Bytes are used, otherwise the MQTT message will be too long

      Serial.print(F("Writing data into block ")); Serial.print(blockAddr);
      Serial.println(F(" ..."));
      dump_byte_array(buffer, 16); 
      status = (MFRC522::StatusCode) rfid.MIFARE_Write(blockAddr, buffer, 16);
      if (status != MFRC522::STATUS_OK) {
        Serial.print(F("MIFARE_Write() failed: "));
        Serial.println(rfid.GetStatusCodeName(status));
        popRFIDerror();
        return 0;
      }   
    }
  }  
  
  sendUID(UID, KeyOld, KeyNew);

  return 1;
}

/*
 * This small routine starts a request for encryption. 
 * for details see https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf
 */
boolean RequestAuthUltralightC(void){
int i;
  byte AuthBuffer[24] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};            // 
  byte AuthLength=24;   
  byte RndARndB[16];
  byte RndA[8]={0,0,0,0,0,0,0,0};
  byte dRndA[8]={0,0,0,0,0,0,0,0};                                                  // decrypted RndA
  byte iv_ar[8];                                                                    // this is the starting IV for decryption
  // byte buffer[24];
  // byte byteCount=sizeof(buffer);
  char UID [21];

  byte2charArray(rfid.uid.uidByte, UID, rfid.uid.size);
  Serial.print(F("UID: "));
  Serial.println(UID);


  // set IV to 0x00
  for(i=0 ; i<8 ; i++){
    iv_ar[i] = 0x00;
  }
  memcpy(&IV,iv_ar,8);                      // set IV 
  des.set_IV(IV);
  
  // Build command buffer
  AuthBuffer[0] = 0x1A; // CMD_3DES_AUTH -> Ultralight C 3DES Authentication.
  AuthBuffer[1] = 0x00; // 

  // Calculate CRC_A
  status = rfid.PCD_CalculateCRC(AuthBuffer, 2, &AuthBuffer[2]);
  if (status != rfid.STATUS_OK) {
    return 0;
  }

  AuthLength=sizeof(AuthBuffer);

  // Transmit the buffer and receive the response, validate CRC_A.
  status = rfid.PCD_TransceiveData(AuthBuffer, 4, AuthBuffer, &AuthLength, NULL, 0, true);
  if (status != rfid.STATUS_OK) {
    Serial.println("Ultralight C Auth failed");
    Serial.println(rfid.GetStatusCodeName(status));
    return 0;
  }

  memcpy(iv_ar,AuthBuffer+1,8);                             // use enc(RndB) as new IV for the next encryption.
  
  des.set_size(8);
  des.tdesCbcDecipher(AuthBuffer+1,RndARndB+8);             // decrypt enc(RndB) -> now we have RndB in the last 8 bytes of RndARndB

  randomSeed(AuthBuffer[1]);                                 
  int number;
  for(int i=0; i<8; i++){
    number=random(255);
    RndARndB[i]=number&0xFF;                                // write random Bytes to the first part of RndARndB 
  }

  rol(RndARndB+8,8);                                        // roll the "RndB"-part of RndARndB 
  memcpy(RndARndB,RndA,8);                                  // backup of RndA for later
  
  AuthBuffer[0] = 0xAF;                                     // set the PCD-command

  memcpy(&IV,iv_ar,8);                                      // set IV = to enk(RndB) for later encryption
  des.set_IV(IV);                           
  des.set_size(16);
  des.tdesCbcEncipher(RndARndB, &AuthBuffer[1]);            // careful! for some kind of reason RndARndB ist modified in this process
    
  status = rfid.PCD_CalculateCRC(AuthBuffer, 17, &AuthBuffer[17]);
  if (status != rfid.STATUS_OK) {
    return 0;
  }
  
  memcpy(&IV,&AuthBuffer[9],8);                              // set IV to decrypt reply from PICC enc(RndA') -> RndA 
  des.set_IV(IV);     
    
  status = rfid.PCD_TransceiveData(AuthBuffer, 19, AuthBuffer, &AuthLength, NULL, 0, true);
  if (status != rfid.STATUS_OK) {
    Serial.print(F("Auth failed failed: "));
    Serial.println(rfid.GetStatusCodeName(status));
    return 0;
  }
  else{                               
    if(AuthBuffer[0]==0x00){                                // reply from PICC should start with 0x00
      des.set_size(8);
      des.tdesCbcDecipher(AuthBuffer+1,dRndA);              // decrypt now we have decrypted RndA'

      rol(RndA,8);                                          // rotate orgiginal RndA to RndA'
      for(i=0; i<8; i++){                                   // compare RndA' and dRndA
        if(RndA[i] != dRndA[i]){
          i=9;
        }
      }
      if(i==8){
        Serial.println(F("Keys match :-)")); 
        sendUID(UID, NULL, NULL);
      }
      else{
        Serial.println(F("Keys do not match"));
        return 0;
      }
    }
    else{
      Serial.println(F("Wrong answer!!!"));
      return 0;
    }
  }  
  return 1;
} 

// Needed to create RndB' out of RndB
void rol(byte *data, int len){
    byte first = data[0];
    for (int i = 0; i < len-1; i++) {
        data[i] = data[i+1];
    }
    data[len-1] = first;
}



void sendUID(char *UID, char *KeyOld, char *KeyNew){
  
char JSONmessageBuffer[200];    // contains the full JSON message 
char topic[32];

  if (Format_Mod==0){                                               // JSON object
    if(RFID_Mod==1)                                                 // including timestamp
      snprintf(JSONmessageBuffer, sizeof(JSONmessageBuffer), R"({"UID":"%s","KeyOld":"%s","KeyNew":"%s"})", UID, KeyOld, KeyNew); 
    else                                                            // only UID
      snprintf(JSONmessageBuffer, sizeof(JSONmessageBuffer), R"({"UID":"%s"})", UID); 
  }
  else{                                                             // simple Text, sparated by ;
    if(RFID_Mod==1)
      snprintf(JSONmessageBuffer, sizeof(JSONmessageBuffer), "UID %s; KeyOld %s; KeyNew %s", UID, KeyOld, KeyNew); 
    else
      snprintf(JSONmessageBuffer, sizeof(JSONmessageBuffer), "UID %s" , UID); 
  }
  Serial.print(F("payload: "));
  Serial.println(JSONmessageBuffer);
  
  snprintf(topic, sizeof(topic), "/rfid_reader/%s", ReaderID);      // generat a topic with the ReaderID
  Serial.print(F("topic: "));
  Serial.println(topic);
  Serial.println("");
  MQTTclient.publish(topic, JSONmessageBuffer);                     // send it to the broker 
}


boolean setAuthKey(String Sdata){
int i;
byte C_key[24];
  
  for(i=0;i<32;i++){
    if(Sdata[i]==0x00){
      Serial.println(F("Key too short! (needs to be 32 digits)"));
      return 0;
    }
    else{
      if((i%2==0)){
        C_key[i/2]=char2byte(&Sdata[i]);
      }
    }
  }
  for(i=0;i<8;i++){
    C_key[i+16]=C_key[i];
  }
  des.change_key(C_key);
  Serial.println(F("key set successfully!"));
  return 1;
}





/* -------------------------------------------------------------------------------------------------
 *                                    Display stuff
 */

/*
 * Show a predefined message on the display. If the the message id is unknow, show the  
 * clear text message. The addText is used to provide variable information to a predefined 
 * text like "duration"
 */
void MessageToDisplay(int ID, String clearText, String addnText){
String dump = "";
String s1, s2;
int len, split=16;

  if(ID < maxMessage){                        // if a predefined message is used 
    split=SplitAt[ID];                        // look up where it should be splitted
    dump += Message_Disp[ID];                 // copy the predefined message
  }
  else{
    dump += clearText;                        // display the message provided as clear text
  }

  if(addnText!=""){
    dump += " ";                              // add a space between 
    dump += addnText;                         // add a potential flexible Message like "countdown" or "Username" to the message
  }
  
  len = dump.length();                        // how long is the message?
  
  if(len>32)                                  // if too long
    len=32;                                   // make it shorter
  
  if((len>split)&&(split>0)){                 // if pre defined message is too long for a single line
    s1 = dump.substring(0, split);
    s2 = dump.substring(split,(len));
  }
  else if(len>15){                            // if a user defined message is too long for a single line
    s1 = dump.substring(0, 15);
    s2 = dump.substring(15,len);
  }
  else{
    s1 = dump.substring(0, len);              // Just create a single line
  }
  s1 += "";                                   // terminate the strings properly
  s2 += "";                                   // same for the second line.
  display2lines(s1,s2);
  
  // save the lines in case an error needs to be prompted
  lastLine1 = "";
  lastLine1 += s1;                             
  lastLine2 = "";
  lastLine2 += s2;
  linesSaved=1;                            
}


/*
 * used to show reader generated status messages
 */
 
void StatusToDisplay(int item){
  #ifdef ESP32 
    EPaper.setFont(&FreeMonoBold18pt7b);
    fsize=18;
  #endif 
  display.setFont(ArialMT_Plain_24);
  
  if(item==CONNECT_TO_WIFI){
    display2lines("connecting...", "Wifi");
  }
  else if (item==CONNECT_TO_MQTT){
    display2lines("connecting...", "MQTT");
  }
  else if (item==PUSH_BUTTON){
    display2lines("For AP-Mode", "press button");
  }
  else if(item==CONNECT_TO_LAN){
    display2lines("LAN", "connecting...");                
  }
  else if(item==MQTT_RFID){
    display2linesRAW("MQTT", "RFID");
  }
  else if(item==IN_AP_MODE){
    display2linesRAW("AP-", "MODE");
  }
  else if(item==ERROR_RFID){
    display2linesRAW("RFID-", "ERROR");
  }
  else if(item==WRONG_RFID_TYPE){
    display2linesRAW("Read-", "ERROR");
  }
  else if(item==ERROR_DHCP){
    display2lines("DHCP-ERROR", "pls. reboot");
  }
}

void LastMessageDisplay(void){
  if(linesSaved){
    display.setFont(ArialMT_Plain_16);
    #ifdef ESP32 
      EPaper.setFont(&FreeMonoBold12pt7b);
      fsize=12;
    #endif  
    display2linesRAW(lastLine1, lastLine2);
  }
  else
    StatusToDisplay(MQTT_RFID);
}

void popRFIDerror(void){
  StatusToDisplay(ERROR_RFID);                  // show error
  delay(2000);        
  LastMessageDisplay();                         // show last message displayed
}  

/*
 * Displays 2 lines of text in size 16
 */

void display2lines(String str1, String str2) {
  display.setFont(ArialMT_Plain_16);
  #ifdef ESP32 
    EPaper.setFont(&FreeMonoBold12pt7b);
    fsize=12;
  #endif
  display2linesRAW(str1, str2);
}

/*
 * Displays 2 lines of text in any size defined by the calling function
 */

void display2linesRAW(String str1, String str2) {
  display.clear();
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  display.drawString(64, 13, str1);   
  display.drawString(64, 35, str2);
  display.setFont(ArialMT_Plain_10);                          
  display.drawString(64, 0, ReaderID);              // Show the reader ID on top of the screen
  display.display();
  #ifdef ESP32     
    EPaper.fillScreen(GxEPD_WHITE);
    int x1=(EPaper.width()/2-(str1.length()*fsize)/2);
    EPaper.setCursor(x1,20+2*fsize);
    EPaper.println(str1);
    int x2=(EPaper.width()/2-(str2.length()*fsize)/2);
    EPaper.setCursor(x2,50+2*fsize);
    EPaper.println(str2);
    EPaper.setFont(&FreeMonoBold9pt7b);
    EPaper.setCursor(EPaper.width()-8*9,12);
    EPaper.print(F("ID:"));
    EPaper.println(ReaderID);
    EPaper.updateWindow(0, 0, EPaper.width()-1, EPaper.height()-1, true);
    // EPaper.update();
  #endif
}


/*
 * Reading data from EEPROM
 */

void readConfig (void){
int i=0, j=0;
int item=0;                 // how many items to be read
char val;

  Wifi_Ether = EEPROM.read(0);
  RFID_Mod = EEPROM.read(1);
  Format_Mod = EEPROM.read(2);

  
  i=3;
  while(i<512){
    val=EEPROM.read(i);
    if(val!='\r'){
      if(item==0){            // host
        ssid[j]=val;
        ssid[j+1]=0x00;
      }
      else if(item==1){
        password[j]=val;
        password[j+1]=0x00;
      }
      else if(item==2){
        broker[j]=val;
        broker[j+1]=0x00;
      }      
      else if(item==3){
        ReaderID[j]=val;
        ReaderID[j+1]=0x00;
      }
      else if(item==4){
        if(val=='1')
          Wifi_Ether=1;
        else if(val == '0')
          Wifi_Ether=0;
      }
      else if(item==5){
        i=512;                                    // stop.
      }
      j++;
    }
    else if((val==0x00)||(val==0xFF)){             // momory not initialized
      i=512;
    }
    else{
      item++;
      j=0;  
    }
    i++;
  }
}


void char2byteArray(String in, byte *out, int len_in){
  for(int i=0; i<len_in; i++){
    out[i]=char2byte(&in[i*2]);  
  }
}


/*
 * convert 2 chars 00-FF to a byte 0x00-0xFF
 */
byte char2byte(char *s){
byte x = 0;
  for(int i=0; i <2 ; i++) {
    char c = *s;
    if (c >= '0' && c <= '9') {
      x *= 16;
      x += c - '0';
    }
    else if (c >= 'A' && c <= 'F') {
      x *= 16;
      x += (c - 'A') + 10;
    }
    else if (c >= 'a' && c <= 'f') {
      x *= 16;
      x += (c - 'a') + 10;
    }
    s++;
  }
  return x;
}





// convert byte array to HEX-character array
void byte2charArray(byte *in, char *out, int len_in){ 
char translate[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 
  
  for(int i=0;i<len_in;i++){
    byte value = in[i];
    out[2*i]=translate[value/16];
    out[(2*i)+1]=translate[value%16];
  }
  out[(2*len_in)]=0x00;                                      // terminate the array properly
}


     
String printHex(byte *buffer, byte bufferSize) {
String id = "";
  for (byte i = 0; i < bufferSize; i++) {
    id += buffer[i] < 0x10 ? "0" : "";
    id += String(buffer[i], HEX);
  }
  return id;
}

/**
 * Helper routine to dump a byte array as hex values to Serial.
 */
void dump_byte_array(byte *buffer, byte bufferSize) {
  for (byte i = 0; i < bufferSize; i++) {
    Serial.print(buffer[i] < 0x10 ? " 0" : " ");
    Serial.print(buffer[i], HEX);
  }
  Serial.println();
}
