// for retreiving the MAC adress
+
+
+// Definition der Pins
+#ifdef ESP32
+ #define SS_PIN 17 // GPIO17
+ #define RST_PIN 16 // GPIO16
+ #define BEEP_PIN 14 // GPIO16
+ #define BUTTON_PIN 13 // GPIO2
+#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
+#endif
+
+#define CS 15
+
+// 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.
+const char* subTopic = "/cmnd/reader"; // MQTT Topic where to find commands for the reader
+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
+
+String Message_Disp[] = {"verfügbar", // 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
+ "Nutzungsgebühr ", // 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
+ "außerhalb der Nutzungzeit", // 14 - 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 bestätigen"}; // 8 - confirm presence // 19
+
+int SplitAt[]={0,0,13,0,0,8,15,13,11,14,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
+*/
+
+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[] =
+""
+""
+""
+""
+""
+"Verbindungsdaten RFID-Reader"
+""
+""
+""
+"Verbindungsdaten RFID-Reader
"
+""
+""
+"";
+
+
+// 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
+
+ SPI.begin();
+ rfid.PCD_Init(); // RC522 initialize
+ // rfid.PCD_PerformSelfTest();
+
+ Serial.begin(115200);
+ Serial.println();
+ Serial.println("Booting...");
+
+ des.init(C_key,IV);
+
+ 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);
+ 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="SSID: ";
+ response += httpServer.arg("ssid"); // max 32 characters
+ response +="
";
+ response +="Password: ";
+ response +=httpServer.arg("Passwort"); // max 64 characters
+ response +="
";
+ response +="The broker IP-Address is: ";
+ response +=httpServer.arg("BrokerIP"); // max 16 characters
+ response +="
";
+ response +="Hostname is: "; // max 128 characters
+ response +="rfid_reader_"; // TO DO -> complete argument for the Hostname
+ response +=httpServer.arg("ReaderID"); // max 3 characters
+ response +="
";
+ response +="Topic name is: ";
+ response +="/rfid_reader/"; // TO DO -> complete argument for the topic
+ response +=httpServer.arg("ReaderID");
+ response +="
";
+ response +="connection via: ";
+ if(httpServer.arg("ConType")=="1"){
+ response +="Ethernet";
+ Wifi_Ether=1;
+ }
+ else{
+ response +="Wifi";
+ Wifi_Ether=0;
+ }
+ response +="
";
+ 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 +="
";
+ response +="MQTT-Data Format: ";
+ if (httpServer.arg("DatFormat")=="1"){
+ response +="Text";
+ Format_Mod=1;
+ }
+ else{
+ response +="JSON";
+ Format_Mod=0;
+ }
+
+
+ response +="
";
+ response +="
";
+
+ // 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;ngo home
";
+ 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(host, mqttUser, mqttPass)) {
+ Serial.print("failed, rc=");
+ Serial.println(MQTTclient.state());
+ }
+ }
+ MQTTclient.subscribe(subTopic);
+ Serial.println("connected!");
+ 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();
+
+
+
+ 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){
+ 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){
+ display.setFont(ArialMT_Plain_24);
+ display2linesRAW("MQTT", "RFID");
+ }
+ else if(item==IN_AP_MODE){
+ display.setFont(ArialMT_Plain_24);
+ display2linesRAW("AP-", "MODE");
+ }
+ else if(item==ERROR_RFID){
+ display.setFont(ArialMT_Plain_24);
+ display2linesRAW("RFID-", "ERROR");
+ }
+ else if(item==WRONG_RFID_TYPE){
+ display.setFont(ArialMT_Plain_24);
+ display2linesRAW("Read-", "ERROR");
+ }
+ else if(item==ERROR_DHCP){
+ display2lines("DHCP-ERROR", "pls. reboot");
+ }
+}
+
+void LastMessageDisplay(void){
+ if(linesSaved){
+ display.setFont(ArialMT_Plain_16);
+ 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);
+ 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(66, 18, str1);
+ display.drawString(66, 40, str2);
+ display.setFont(ArialMT_Plain_10);
+ display.drawString(64, 0, ReaderID); // Show the reader ID on top of the screen
+ display.display();
+}
+
+
+/*
+ * 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= '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