diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da43a0c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +Software/.DS_Store diff --git a/Software/.gitignore b/Software/.gitignore new file mode 100644 index 0000000..4d37315 --- /dev/null +++ b/Software/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +src/config.h \ No newline at end of file diff --git a/Software/.vscode/extensions.json b/Software/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/Software/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/Software/.vscode/settings.json b/Software/.vscode/settings.json new file mode 100644 index 0000000..1610e0a --- /dev/null +++ b/Software/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "files.associations": { + "random": "cpp", + "cstddef": "cpp", + "array": "cpp", + "string_view": "cpp", + "initializer_list": "cpp", + "ranges": "cpp", + "utility": "cpp", + "string": "cpp", + "functional": "cpp", + "*.desfire": "cpp", + "*.old": "cpp", + "*.deprecated": "cpp" + } +} \ No newline at end of file diff --git a/Software/README.txt b/Software/README.txt new file mode 100644 index 0000000..baed9cd --- /dev/null +++ b/Software/README.txt @@ -0,0 +1,30 @@ +# OTA +## Protocol: +READERID is 5 Digits +1. Hello: Topic="fabreader", Payload="READERID" +1. Start: Topic="fabreader/READERID/startOTA", Payload=UID of Card +1. Request: Topic="fabreader/READERID/requestOTA", Payload=256 Bytes APDU Command +1. Response: Topic="fabreader/READERID/responseOTA", Payload=256 Bytes APDU Response +1. Stop: Topic="fabreader/READERID/stopOTA", Payload=NULL +1. Cancel: Topic="fabreader/READERID/cancelOTA", Payload=UID of Card +1. Restart: Topic="fabreader/READERID/restartOTA", Payload=NULL + +## Procedure: +After Start Reader sends "Hello". + +After new Card is on Reader: +"Start" -> to Server +"Request" <- from Server +"Response" -> to Server + +Repeat Request and Response until: +"Stop" <- from Server +"Cancel" -> to Server + +If FabReader works as deadmen switch the OTA connection is not stopped and the next check can be started with restartOTA. +If DESFire Card uses Random UID the UID changes every new connection, so the connections stays active until the server restartsOTA and performs new OTA. + +# Display +## Protocol +1. Title: Topic="fabreader/READERID/display/title", Payload=Title Text +2. Info: Topic="fabreader/READERID/display/info", Payload=Info Text \ No newline at end of file diff --git a/Software/include/README b/Software/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/Software/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/Software/lib/README b/Software/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/Software/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/Software/platformio.ini b/Software/platformio.ini new file mode 100644 index 0000000..a3f3403 --- /dev/null +++ b/Software/platformio.ini @@ -0,0 +1,23 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp12e] +platform = espressif8266 +board = esp12e +framework = arduino +monitor_speed = 115200 +lib_deps = + knolleary/PubSubClient@^2.8 + thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.3.0 + miguelbalboa/MFRC522@^1.4.10 + thijse/ArduinoLog@^1.1.1 + uipethernet/UIPEthernet@^2.0.12 + bblanchon/ArduinoJson@^6.19.4 + mbed-feb11/Crypto@0.0.0+sha.f04410cef037 diff --git a/Software/src/Deprecated/config.cpp.old b/Software/src/Deprecated/config.cpp.old new file mode 100644 index 0000000..8e439b9 --- /dev/null +++ b/Software/src/Deprecated/config.cpp.old @@ -0,0 +1,37 @@ +#include "Config.h" +#include +#include + +Config::Config() +{ + EEPROM.begin(512); +} + +Config::Config(int ID, char mqtt_broker[], int mqtt_port, char mqtt_username[], char mqtt_password[], char wlan_ssid[], char wlan_password[]) +{ + Data.ID = ID; + strcpy(Data.MQTT_Broker, mqtt_broker); + Data.MQTT_Port = mqtt_port; + strcpy(Data.MQTT_Username, mqtt_username); + strcpy(Data.MQTT_Password, mqtt_password); + strcpy(Data.WLAN_SSID, wlan_ssid); + strcpy(Data.WLAN_Password, wlan_password); + + EEPROM.begin(512); +} + +void Config::Load() +{ + EEPROM.get(0, this->Data); +} + +void Config::Save() +{ + EEPROM.put(0, this->Data); + EEPROM.commit(); +} + +bool Config::IsEmpty() +{ + return this->Data.ID == 0; +} \ No newline at end of file diff --git a/Software/src/Deprecated/config.h.old b/Software/src/Deprecated/config.h.old new file mode 100644 index 0000000..0a16c61 --- /dev/null +++ b/Software/src/Deprecated/config.h.old @@ -0,0 +1,32 @@ +#ifndef CONFIG_H +#define CONFIG_H + +struct Config_Data +{ + int ID = 0; + + char MQTT_Broker[256]; + int MQTT_Port = 1883; + char MQTT_Username[64]; + char MQTT_Password[64]; + + char WLAN_SSID[65]; + char WLAN_Password[64]; +}; + +class Config { + public: + const char WLANAP_SSID[32] = "FabReader"; + const char WLANAP_Password[64] = "FabReader"; + + Config_Data Data; + + Config(); + Config(int ID, char mqtt_broker[], int mqtt_port, char mqtt_username[], char mqtt_password[], char wlan_ssid[], char wlan_password[]); + + void Load(); + void Save(); + + bool IsEmpty(); +}; +#endif \ No newline at end of file diff --git a/Software/src/Deprecated/website.h b/Software/src/Deprecated/website.h new file mode 100644 index 0000000..6a0e3e2 --- /dev/null +++ b/Software/src/Deprecated/website.h @@ -0,0 +1,60 @@ +#ifndef WEBSITE_H +#define WEBSITE_H +const struct Website +{ + const char INDEX[] = + "" + "" + "" + "" + "" + "Verbindungsdaten RFID-Reader" + "" + "" + "" + "

Verbindungsdaten RFID-Reader

" + "
" + "

" + "
" + "
" + "
" + "
" + "Connection
" + "Ehternet
" + "WiFi
" + "RFID-Type
" + "Mifare Classic
" + "Mifare Classic + Timestamp
" + "Mifare DESFire
" + "Mifar Ultralight C
" + "Data Package Format
" + "JSON
" + "Text
" + " " + "

" + "
" + "" + ""; + + const char SAVED[] = + "" + "" + "" + "" + "" + "Verbindungsdaten RFID-Reader" + "" + "" + "" + "

Saved

" + "

go back

" + "" + ""; + }; +#endif \ No newline at end of file diff --git a/Software/src/Display.cpp b/Software/src/Display.cpp new file mode 100644 index 0000000..9be1013 --- /dev/null +++ b/Software/src/Display.cpp @@ -0,0 +1,100 @@ +#include "display.h" +#include + +Display::Display(int sda, int scl, int readerid) +{ + this->readerid = readerid; + display = new SSD1306Wire(0x3c, sda, scl); + display->init(); + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->flipScreenVertically(); +} + +void Display::clearReaderInfo() +{ + char buffer [DISPLAY_BUFFER_SIZE]; + sprintf(buffer, "%05d", readerid); + writeReaderInfo(buffer); +} + +void Display::createReaderInfo(char state[]) +{ + if(strlen(state) < DISPLAY_BUFFER_SIZE - 1 - 6) + { + char buffer [DISPLAY_BUFFER_SIZE]; + sprintf(buffer, "%s %05d", state, readerid); + writeReaderInfo(buffer); + } +} + +void Display::writeReaderInfo(char text[]) +{ + if(strlen(text) < DISPLAY_BUFFER_SIZE - 1) + { + strcpy(ReaderInfo, text); + } + updateDisplay(); +} + +void Display::writeTitle(char text[]) +{ + if(strlen(text) < DISPLAY_BUFFER_SIZE - 1) + { + strcpy(Title, text); + } + updateDisplay(); +} + +void Display::writeInfo(char text[]) +{ + if(strlen(text) < DISPLAY_BUFFER_SIZE - 1) + { + strcpy(Info, text); + } + + updateDisplay(); +} + +void Display::updateDisplay() +{ + display->clear(); + + display->setFont(ArialMT_Plain_10); + display->drawString(64, 0, ReaderInfo); + + display->setFont(ArialMT_Plain_24); + display->drawString(64, 13, Title); + + display->setFont(ArialMT_Plain_16); + display->drawString(64, 40, Info); + + display->display(); +} + +void Display::updateByMQTT(char* topic, byte* payload, unsigned int length) +{ + char topic_displayTitle[] = "fabreader/00000/display/title"; + sprintf(topic_displayTitle, "fabreader/%05d/display/title", readerid); + + char topic_displayInfo[] = "fabreader/00000/display/info"; + sprintf(topic_displayInfo, "fabreader/%05d/display/info", readerid); + + char topic_stopOTA[] = "fabreader/00000/stopOTA"; + sprintf(topic_stopOTA, "fabreader/%05d/stopOTA", readerid); + + char buffer[length + 1] = {0}; + memcpy(buffer, payload, length); + + if(!strcmp(topic, topic_displayTitle)) + { + writeTitle(buffer); + } + else if(!strcmp(topic, topic_displayInfo)) + { + writeInfo(buffer); + } + else if(!strcmp(topic, topic_stopOTA)) + { + clearReaderInfo(); + } +} \ No newline at end of file diff --git a/Software/src/Display.h b/Software/src/Display.h new file mode 100644 index 0000000..2514fb1 --- /dev/null +++ b/Software/src/Display.h @@ -0,0 +1,26 @@ +#ifndef DISPLAY_H +#define DISPLAY_H +#include + +#define DISPLAY_BUFFER_SIZE 30 + +class Display +{ + private: + SSD1306Wire* display; + int readerid; + char ReaderInfo[DISPLAY_BUFFER_SIZE] = ""; + char Title[DISPLAY_BUFFER_SIZE] = "Boot ..."; + char Info[DISPLAY_BUFFER_SIZE] = ""; + + public: + Display(int sda, int scl, int readerid); + void createReaderInfo(char* state); + void clearReaderInfo(); + void writeReaderInfo(char* text); + void writeTitle(char* text); + void writeInfo(char* text); + void updateDisplay(); + void updateByMQTT(char* topic, byte* payload, unsigned int length); +}; +#endif \ No newline at end of file diff --git a/Software/src/NFC.cpp b/Software/src/NFC.cpp new file mode 100644 index 0000000..63324cd --- /dev/null +++ b/Software/src/NFC.cpp @@ -0,0 +1,144 @@ +#include "nfc.h" +#include +#include +#include +#include "helpers.h" + +NFC::NFC(int pin_ss, int pin_rst) +{ + rfid = new DESFire(pin_ss, pin_rst); + + SPI.begin(); + rfid->PCD_Init(); + rfid->PCD_DumpVersionToSerial(); +} + +MFRC522::Uid NFC::getUID() +{ + return uid; +} + +bool NFC::hasCardSelected() +{ + return cardSelected; +} + +bool NFC::testNFC() +{ + return rfid->PCD_PerformSelfTest(); +} + +bool NFC::checkforCard() +{ + // RequestA + if ( ! rfid->PICC_IsNewCardPresent()) + { + return false; + } + + // PICC_Select ??? + if ( ! rfid->PICC_ReadCardSerial()) + { + return false; + } + + // Check for DESFire + if (rfid->uid.sak != 0x20) + { + return false; + } + + return true; +} + +bool NFC::connecttoCard() +{ + // RATS + byte ats[16]; + byte atsLength = 16; + MFRC522::StatusCode state = rfid->PICC_RequestATS(ats, &atsLength); + if (state != MFRC522::STATUS_OK) { + Serial.println(F("Failed ATS")); + Serial.println(state); + rfid->PICC_HaltA(); + return false; + } + + // PPS + state = rfid->PICC_ProtocolAndParameterSelection(0x00, 0x11, 0x00); + if (state != MFRC522::STATUS_OK) + { + Serial.println("Failed PPS"); + Serial.println(state); + rfid->PICC_HaltA(); + return false; + } + + cardSelected = true; + uid = rfid->uid; + pcb = 0x0A; + return true; +} + +bool NFC::disconnectCard() +{ + MFRC522::StatusCode state = rfid->PICC_HaltA(); + if (state != MFRC522::STATUS_OK) + { + Serial.println(F("Failed PICC_HaltA")); + Serial.println(state); + return false; + } + + return true; +} + +bool NFC::testCard() +{ + return true; +} + +MFRC522::StatusCode NFC::Transceive(byte* command, byte command_len, byte* response, byte* response_len) +{ + if(command_len >= 0xFF - 4) + { + return MFRC522::STATUS_NO_ROOM; + } + + MFRC522::StatusCode state; + byte request_buffer[APDU_BUFFER_SIZE + 4]; // APDU Size + PCB + CID + 2x CRC + byte request_buffer_size = command_len + 4; + + request_buffer[0] = pcb; + request_buffer[1] = cid; + + memcpy(&request_buffer[2], command, command_len); + + // Update the PCB + if (pcb == 0x0A) + pcb = 0x0B; + else + pcb = 0x0A; + + // Calculate CRC_A + state = rfid->PCD_CalculateCRC(request_buffer, request_buffer_size - 2, &request_buffer[request_buffer_size - 2]); + if (state != MFRC522::STATUS_OK) + { + return state; + } + + byte response_buffer[APDU_BUFFER_SIZE + 4] = {0}; // APDU Size + PCB + CID + 2x CRC + byte response_buffer_size = 0xFF; + + printbytes(request_buffer, request_buffer_size); + state = rfid->PCD_TransceiveData(request_buffer, request_buffer_size, response_buffer, &response_buffer_size); + if (state != MFRC522::STATUS_OK) + { + return state; + } + + memcpy(response, response_buffer + 2, response_buffer_size - 4); + *response_len = response_buffer_size - 4; + + return state; +} \ No newline at end of file diff --git a/Software/src/NFC.h b/Software/src/NFC.h new file mode 100644 index 0000000..31dbb90 --- /dev/null +++ b/Software/src/NFC.h @@ -0,0 +1,31 @@ +#ifndef NFC_H +#define NFC_H + +#include +#include +#define APDU_BUFFER_SIZE 256 + +class NFC +{ + private: + + MFRC522::Uid uid; + byte pcb = 0x0A; + byte cid = 0x00; + bool cardSelected = false; + + public: + DESFire* rfid; + NFC(int pin_ss, int pin_rst); + bool testNFC(); + bool checkforCard(); + bool connecttoCard(); + bool disconnectCard(); + bool testCard(); + bool hasCardSelected(); + MFRC522::Uid getUID(); + + MFRC522::StatusCode Transceive(byte* command, byte command_len, byte* response, byte* response_len); +}; + +#endif diff --git a/Software/src/OTAProxy.cpp b/Software/src/OTAProxy.cpp new file mode 100644 index 0000000..2498b2b --- /dev/null +++ b/Software/src/OTAProxy.cpp @@ -0,0 +1,96 @@ +#include "otaproxy.h" +#include "nfc.h" +#include + +OTAProxy::OTAProxy(PubSubClient* mqttClient, NFC* nfc, int id) +{ + this->mqtt = mqttClient; + this->nfc = nfc; + this->id = id; +} + +bool OTAProxy::hasActiveOTA() +{ + return activeOTA; +} + +void OTAProxy::startOTA() +{ + if(!(nfc->hasCardSelected())) + { + return; + } + activeOTA = true; + + char topic[] = "fabreader/00000/startOTA"; + sprintf(topic, "fabreader/%05d/startOTA", id); + MFRC522::Uid uid = nfc->getUID(); + mqtt->publish(topic, uid.uidByte, uid.size); + + Serial.println("Start OTA"); +} + +void OTAProxy::continueOTA(char* topic, byte* payload, unsigned int length) +{ + char topic_requestOTA[] = "fabreader/00000/requestOTA"; + sprintf(topic_requestOTA, "fabreader/%05d/requestOTA", id); + + char topic_responseOTA[] = "fabreader/00000/responseOTA"; + sprintf(topic_responseOTA, "fabreader/%05d/responseOTA", id); + + char topic_stopOTA[] = "fabreader/00000/stopOTA"; + sprintf(topic_stopOTA, "fabreader/%05d/stopOTA", id); + + char topic_restartOTA[] = "fabreader/00000/restartOTA"; + sprintf(topic_restartOTA, "fabreader/%05d/restartOTA", id); + + if(!strcmp(topic, topic_requestOTA)) + { + Serial.println("Request OTA"); + byte response[APDU_BUFFER_SIZE] = {0}; + byte response_len; + + MFRC522::StatusCode status; + + Serial.println("Run Transceive"); + status = nfc->Transceive(payload, length, response, &response_len); + Serial.printf("PICC_Tranceive: 0x%02x\n", status); + + if(status != MFRC522::STATUS_OK) + { + cancelOTA(); + return; + } + + mqtt->publish(topic_responseOTA, response, response_len); + Serial.println("Response OTA"); + } + else if(!strcmp(topic, topic_stopOTA)) + { + Serial.println("Stop OTA"); + nfc->disconnectCard(); + activeOTA = false; + } + // else if(!strcmp(topic, topic_restartOTA)) + // { + // Serial.println("Restart OTA"); + // while(!(nfc->deselectCard())); + // // if(nfc->hasNewCard()) + // // { + // // startOTA(); + // // } + // } +} + +void OTAProxy::cancelOTA() +{ + char topic_cancelOTA[] = "fabreader/00000/cancelOTA"; + sprintf(topic_cancelOTA, "fabreader/%05d/cancelOTA", id); + MFRC522::Uid uid = nfc->getUID(); + mqtt->publish(topic_cancelOTA, uid.uidByte, uid.size); + + while(!(nfc->disconnectCard())); + activeOTA = false; + + Serial.println("Cancel OTA"); +} \ No newline at end of file diff --git a/Software/src/OTAProxy.h b/Software/src/OTAProxy.h new file mode 100644 index 0000000..8245b6d --- /dev/null +++ b/Software/src/OTAProxy.h @@ -0,0 +1,25 @@ +#ifndef OTAProxy_H +#define OTAProxy_H + +#include +#include "nfc.h" +#define MSG_BUFFER_SIZE 50 + +class OTAProxy +{ + private: + bool activeOTA = false; + PubSubClient* mqtt; + NFC* nfc; + int id; + char msg[MSG_BUFFER_SIZE]; + public: + OTAProxy(PubSubClient* mqtt, NFC* nfc, int id); + bool hasActiveOTA(); + + void startOTA(); + void continueOTA(char* topic, byte* payload, unsigned int length); + void cancelOTA(); +}; + +#endif diff --git a/Software/src/Pins.h b/Software/src/Pins.h new file mode 100644 index 0000000..958c6f8 --- /dev/null +++ b/Software/src/Pins.h @@ -0,0 +1,18 @@ +#ifndef PINS_H +#define PINS_H + +// Pins for ESP8266 +#define PIN_SDA 4 +#define PIN_SCL 5 + +#define PIN_SPI_MOSI 13 +#define PIN_SPI_MISO 12 +#define PIN_SPI_SCK 14 + +#define PIN_RFID_RST 0 +#define PIN_RFID_SPI_SS 2 +#define PIN_ETH_SPI_SS 15 + +#define PIN_BUZZER 16 +#define PIN_BUTTON A0 +#endif \ No newline at end of file diff --git a/Software/src/config.h.example b/Software/src/config.h.example new file mode 100644 index 0000000..7869dd1 --- /dev/null +++ b/Software/src/config.h.example @@ -0,0 +1,6 @@ +#define WLAN_SSID "" +#define WLAN_PASS "" +#define MQTT_BROKER "" +#define MQTT_USERNAME "" +#define MQTT_PASSWORD "" +#define FABREADERID 1 \ No newline at end of file diff --git a/Software/src/helpers.cpp b/Software/src/helpers.cpp new file mode 100644 index 0000000..ae7a7fc --- /dev/null +++ b/Software/src/helpers.cpp @@ -0,0 +1,71 @@ +#include "helpers.h" + +void char2byte(char* str, byte* array) +{ + for(int i=0; i <2 ; i++) + { + char c = *str; + if (c >= '0' && c <= '9') + { + *array *= 16; + *array += c - '0'; + } + else if (c >= 'A' && c <= 'F') + { + *array *= 16; + *array += (c - 'A') + 10; + } + else if (c >= 'a' && c <= 'f') + { + *array *= 16; + *array += (c - 'a') + 10; + } + str++; + } +} + +void chars2bytes(char* str, byte* array, bool msb) +{ + int len = strlen(str); + for(int i = 0; i < len; i += 2) + { + if(msb) + { + char2byte(&str[i], &array[i/2]); + } + else + { + char2byte(&str[i], &array[len-2 - i/2]); + } + } +} + +void byte2char(byte* array, char* str) +{ + sprintf(str, "%02x", *array); +} + +void bytes2chars(byte* array, byte array_len, char* str, bool msb) +{ + for(int i = 0; i < array_len; i++) + { + if(msb) + { + byte2char(&array[i], &str[i*2]); + } + else + { + byte2char(&array[array_len-2 - i], &str[i*2]); + } + } +} + +void printbytes(byte* array, byte array_len) +{ + Serial.print("0x"); + for(int i = 0; i < array_len; i++) + { + Serial.printf("%02x", array[i]); + } + Serial.println(); +} \ No newline at end of file diff --git a/Software/src/helpers.h b/Software/src/helpers.h new file mode 100644 index 0000000..c6e3b42 --- /dev/null +++ b/Software/src/helpers.h @@ -0,0 +1,13 @@ +#ifndef HELPERS_H +#define HELPERS_H + +#include +void char2byte(char* str, byte* array); +void chars2bytes(char* str, byte* array, bool msb); + +void byte2char(byte* array, char* str); +void bytes2chars(byte* array, byte array_len, char* str, bool msb); + +void printbytes(byte* array, byte array_len); + +#endif \ No newline at end of file diff --git a/Software/src/main.cpp b/Software/src/main.cpp new file mode 100644 index 0000000..2170d84 --- /dev/null +++ b/Software/src/main.cpp @@ -0,0 +1,181 @@ +#include +#include + +#include "config.h" +#include "pins.h" +#include "nfc.h" +#include "otaproxy.h" +#include "helpers.h" +#include "Desfire.h" +#include "display.h" + +#include +#include + +#include +#include +#include + +WiFiClient espClient; +//Config_Data config; + +PubSubClient* mqtt; +NFC* nfc; +OTAProxy* ota; +Display* display; + +unsigned long otatimeout = 3000; +unsigned long lastotatime; + +void setup_wifi() +{ + delay(10); + Serial.println("Connecting Wifi ..."); + + WiFi.mode(WIFI_STA); + WiFi.begin(WLAN_SSID, WLAN_PASS); + + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + } + + randomSeed(micros()); + Serial.println("WiFi connected"); +} + +void reconnect() +{ + while (!mqtt->connected()) + { + String clientId = "FabReader_"; + clientId += String(FABREADERID); + + Serial.println("Connecting MQTT ..."); + bool connected = false; + if(MQTT_USERNAME == "") + { + connected = mqtt->connect(clientId.c_str()); + } + else + { + connected = mqtt->connect(clientId.c_str(), MQTT_USERNAME, MQTT_PASSWORD); + } + + if (connected) + { + Serial.println("MQTT connected"); + char id[6] = "00000"; + sprintf(id, "%05d", FABREADERID); + mqtt->publish("fabreader", id); + + char topic_requestOTA[] = "fabreader/00000/requestOTA"; + sprintf(topic_requestOTA, "fabreader/%05d/requestOTA", FABREADERID); + mqtt->subscribe(topic_requestOTA); + + char topic_stopOTA[] = "fabreader/00000/stopOTA"; + sprintf(topic_stopOTA, "fabreader/%05d/stopOTA", FABREADERID); + mqtt->subscribe(topic_stopOTA); + + char topic_display[] = "fabreader/00000/display/#"; + sprintf(topic_display, "fabreader/%05d/display/#", FABREADERID); + mqtt->subscribe(topic_display); + + display->writeTitle("Connected"); + display->writeInfo(""); + } + else + { + display->writeTitle("Reconnect"); + display->writeInfo("MQTT"); + + Serial.print("failed, rc="); + Serial.print(mqtt->state()); + Serial.println(" try again in 5 seconds"); + delay(5000); + } + } +} + +void callback(char* topic, byte* payload, unsigned int length) +{ + Serial.println("Receive Message"); + Serial.println(topic); + if(ota->hasActiveOTA()) + { + ota->continueOTA(topic, payload, length); + } + display->updateByMQTT(topic, payload, length); +} + +void setup() +{ + Serial.begin(115200); + Serial.print("\n\n\n"); + Serial.println("Booting ..."); + + pinMode(PIN_BUZZER, OUTPUT); + pinMode(PIN_BUTTON, INPUT); + pinMode(PIN_ETH_SPI_SS, OUTPUT); + digitalWrite(PIN_ETH_SPI_SS, HIGH); + + display = new Display(PIN_SDA, PIN_SCL, FABREADERID); + display->clearReaderInfo(); + + display->writeInfo("Start NFC ..."); + Serial.println("Connecting NFC ..."); + nfc = new NFC(PIN_RFID_SPI_SS, PIN_RFID_RST); + if(!(nfc->rfid->PCD_PerformSelfTest())) + { + Serial.println("NFC Test failed"); + } + Serial.println("NFC connected"); + + display->writeInfo("Start WIFI ..."); + setup_wifi(); + + display->writeInfo("Start MQTT ..."); + mqtt = new PubSubClient(espClient); + mqtt->setServer(MQTT_BROKER, 1883); + mqtt->setCallback(callback); + + display->writeInfo("Start OTA ..."); + ota = new OTAProxy(mqtt, nfc, FABREADERID); +} + +void loop() +{ + if (!mqtt->connected()) + { + reconnect(); + } + mqtt->loop(); + + if(!ota->hasActiveOTA()) + { + if(nfc->checkforCard()) + { + Serial.println("Card detected"); + if(nfc->connecttoCard()) + { + Serial.println("Card connected"); + lastotatime = millis(); + + display->createReaderInfo("Run OTA"); + ota->startOTA(); + } + else + { + display->createReaderInfo("Retry Card"); + } + } + } + if(ota->hasActiveOTA()) + { + if(millis() - lastotatime > otatimeout) + { + ota->cancelOTA(); + display->clearReaderInfo(); + } + } +} \ No newline at end of file diff --git a/Software/src/main.cpp.deprecated b/Software/src/main.cpp.deprecated new file mode 100644 index 0000000..0d526ea --- /dev/null +++ b/Software/src/main.cpp.deprecated @@ -0,0 +1,1418 @@ + +/* + 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 // ESP32 + #include // ESP32 + #include // ESP32 + #include + #include // E-Paper + //#include // 2.13" b/w + //#include // 2.13" b/w new panel + #include // E-Paper + #include + #include + #include + #include + #include + #include +#else + #include // EPS8266 + #include // ESP8266 + #include // ESP8266 + // #include /// +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// #include // 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[] = +"" +"" +"" +"" +"" +"Verbindungsdaten RFID-Reader" +"" +"" +"" +"

Verbindungsdaten RFID-Reader

" +"
" +"

" +"
" +"
" +"
" +"
" +"Connection
" +"Ehternet
" +"WiFi
" +"RFID-Type
" +"Mifare Classic
" +"Mifare Classic + Timestamp
" +"Mifare DESFire
" +"Mifar Ultralight C
" +"Data Package Format
" +"JSON
" +"Text
" +" " +"

" +"
" +"" +""; + + +// 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="

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 +="

go home


"; + + // 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(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(); + + + + 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= '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 +#include +#include + +#define RST_PIN 9 // Configurable, see typical pin layout above +#define SS_PIN 10 // Configurable, see typical pin layout above + +#include "Pins.h" + +DESFire mfrc522(PIN_RFID_SPI_SS, PIN_RFID_RST); // Create MFRC522 instance + +void setup() { + Serial.begin(115200); // Initialize serial communications with the PC + while (!Serial); // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4) + SPI.begin(); // Init SPI bus + mfrc522.PCD_Init(); // Init MFRC522 + mfrc522.PCD_DumpVersionToSerial(); // Show details of PCD - MFRC522 Card Reader details + mfrc522.PCD_PerformSelfTest(); + Serial.println(F("Scan PICC to see UID, SAK, type, and data blocks...")); +} + +void loop() { + // Look for new cards + if ( ! mfrc522.PICC_IsNewCardPresent()) { + return; + } + + // Select one of the cards + if ( ! mfrc522.PICC_ReadCardSerial()) { + return; + } + + if (mfrc522.uid.sak != 0x20) { + // Dump debug info about the card; PICC_HaltA() is automatically called + mfrc522.PICC_DumpToSerial(&(mfrc522.uid)); + return; + } + + // Show an extra line + Serial.println(); + + DESFire::mifare_desfire_tag tag; + DESFire::StatusCode response; + + tag.pcb = 0x0A; + tag.cid = 0x00; + memset(tag.selected_application, 0, 3); + + // Make sure none DESFire status codes have DESFireStatus code to OK + response.desfire = DESFire::MF_OPERATION_OK; + + byte ats[16]; + byte atsLength = 16; + response.mfrc522 = mfrc522.PICC_RequestATS(ats, &atsLength); + if ( ! mfrc522.IsStatusCodeOK(response)) { + Serial.println(F("Failed to get ATS!")); + Serial.println(mfrc522.GetStatusCodeName(response)); + Serial.println(response.mfrc522); + + mfrc522.PICC_HaltA(); + return; + } + + // TODO: Should do checks but since I know my DESFire allows and requires PPS... + // PPS1 is ommitted and, therefore, 0x00 is used (106kBd) + response.mfrc522 = mfrc522.PICC_ProtocolAndParameterSelection(0x00, 0x11); + if ( ! mfrc522.IsStatusCodeOK(response)) { + Serial.println(F("Failed to perform protocol and parameter selection (PPS)!")); + Serial.println(mfrc522.GetStatusCodeName(response)); + mfrc522.PICC_HaltA(); + return; + } + + // MIFARE DESFire should respond to a GetVersion command + DESFire::MIFARE_DESFIRE_Version_t desfireVersion; + response = mfrc522.MIFARE_DESFIRE_GetVersion(&tag, &desfireVersion); + if ( ! mfrc522.IsStatusCodeOK(response)) { + Serial.println(F("Failed to get a response for GetVersion!")); + Serial.println(mfrc522.GetStatusCodeName(response)); + mfrc522.PICC_HaltA(); + return; + } + + // Dump MIFARE DESFire version information. + // NOTE: KEEP YOUR CARD CLOSE TO THE READER! + // This method takes some time and the card will be read + // once output ends! If you remove the card too fast + // a timeout will occur! + mfrc522.PICC_DumpMifareDesfireVersion(&tag, &desfireVersion); + + mfrc522.PICC_DumpMifareDesfireMasterKey(&tag); + + DESFire::mifare_desfire_aid_t aids[MIFARE_MAX_APPLICATION_COUNT]; + byte applicationCount = 0; + response = mfrc522.MIFARE_DESFIRE_GetApplicationIds(&tag, aids, &applicationCount); + if ( ! mfrc522.IsStatusCodeOK(response)) { + Serial.println(F("Failed to get application IDs!")); + Serial.println(mfrc522.GetStatusCodeName(response)); + mfrc522.PICC_HaltA(); + return; + } + + // Dump all applications + for (byte aidIndex = 0; aidIndex < applicationCount; aidIndex++) { + mfrc522.PICC_DumpMifareDesfireApplication(&tag, &(aids[aidIndex])); + } + + // Call PICC_HaltA() + mfrc522.PICC_HaltA(); + Serial.println(); +} \ No newline at end of file diff --git a/Software/src/main.cpp.display b/Software/src/main.cpp.display new file mode 100644 index 0000000..b1393b9 --- /dev/null +++ b/Software/src/main.cpp.display @@ -0,0 +1,189 @@ +/** + The MIT License (MIT) + + Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + Copyright (c) 2018 by Fabrice Weinberg + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + ThingPulse invests considerable time and money to develop these open source libraries. + Please support us by buying our products (and not the clones) from + https://thingpulse.com + +*/ + +// Include the correct display library + +// For a connection via I2C using the Arduino Wire include: +#include // Only needed for Arduino 1.6.5 and earlier +#include "SSD1306Wire.h" // legacy: #include "SSD1306.h" +// OR #include "SH1106Wire.h" // legacy: #include "SH1106.h" + +// For a connection via I2C using brzo_i2c (must be installed) include: +// #include // Only needed for Arduino 1.6.5 and earlier +// #include "SSD1306Brzo.h" +// OR #include "SH1106Brzo.h" + +// For a connection via SPI include: +// #include // Only needed for Arduino 1.6.5 and earlier +// #include "SSD1306Spi.h" +// OR #include "SH1106SPi.h" + + + +// Initialize the OLED display using Arduino Wire: +SSD1306Wire display(0x3c, SDA, SCL); // ADDRESS, SDA, SCL - SDA and SCL usually populate automatically based on your board's pins_arduino.h e.g. https://github.com/esp8266/Arduino/blob/master/variants/nodemcu/pins_arduino.h +// SSD1306Wire display(0x3c, D3, D5); // ADDRESS, SDA, SCL - If not, they can be specified manually. +// SSD1306Wire display(0x3c, SDA, SCL, GEOMETRY_128_32); // ADDRESS, SDA, SCL, OLEDDISPLAY_GEOMETRY - Extra param required for 128x32 displays. +// SH1106Wire display(0x3c, SDA, SCL); // ADDRESS, SDA, SCL + +// Initialize the OLED display using brzo_i2c: +// SSD1306Brzo display(0x3c, D3, D5); // ADDRESS, SDA, SCL +// or +// SH1106Brzo display(0x3c, D3, D5); // ADDRESS, SDA, SCL + +// Initialize the OLED display using SPI: +// D5 -> CLK +// D7 -> MOSI (DOUT) +// D0 -> RES +// D2 -> DC +// D8 -> CS +// SSD1306Spi display(D0, D2, D8); // RES, DC, CS +// or +// SH1106Spi display(D0, D2); // RES, DC + + +#define DEMO_DURATION 3000 +typedef void (*Demo)(void); + +int demoMode = 0; +int counter = 1; + +void setup() { + Serial.begin(115200); + Serial.println(); + Serial.println(); + + + // Initialising the UI will init the display too. + display.init(); + + display.flipScreenVertically(); + display.setFont(ArialMT_Plain_10); + +} + +void drawFontFaceDemo() { + // Font Demo1 + // create more fonts at http://oleddisplay.squix.ch/ + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.setFont(ArialMT_Plain_10); + display.drawString(0, 0, "Hello world"); + display.setFont(ArialMT_Plain_16); + display.drawString(0, 10, "Hello world"); + display.setFont(ArialMT_Plain_24); + display.drawString(0, 26, "Hello world"); +} + +void drawTextFlowDemo() { + display.setFont(ArialMT_Plain_10); + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.drawStringMaxWidth(0, 0, 128, + "Lorem ipsum\n dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore." ); +} + +void drawTextAlignmentDemo() { + // Text alignment demo + display.setFont(ArialMT_Plain_10); + + // The coordinates define the left starting point of the text + display.setTextAlignment(TEXT_ALIGN_LEFT); + display.drawString(0, 10, "Left aligned (0,10)"); + + // The coordinates define the center of the text + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.drawString(64, 22, "Center aligned (64,22)"); + + // The coordinates define the right end of the text + display.setTextAlignment(TEXT_ALIGN_RIGHT); + display.drawString(128, 33, "Right aligned (128,33)"); +} + +void drawRectDemo() { + // Draw a pixel at given position + for (int i = 0; i < 10; i++) { + display.setPixel(i, i); + display.setPixel(10 - i, i); + } + display.drawRect(12, 12, 20, 20); + + // Fill the rectangle + display.fillRect(14, 14, 17, 17); + + // Draw a line horizontally + display.drawHorizontalLine(0, 40, 20); + + // Draw a line horizontally + display.drawVerticalLine(40, 0, 20); +} + +void drawCircleDemo() { + for (int i = 1; i < 8; i++) { + display.setColor(WHITE); + display.drawCircle(32, 32, i * 3); + if (i % 2 == 0) { + display.setColor(BLACK); + } + display.fillCircle(96, 32, 32 - i * 3); + } +} + +void drawProgressBarDemo() { + int progress = (counter / 5) % 100; + // draw the progress bar + display.drawProgressBar(0, 32, 120, 10, progress); + + // draw the percentage as String + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.drawString(64, 15, String(progress) + "%"); +} + +Demo demos[] = {drawFontFaceDemo, drawTextFlowDemo, drawTextAlignmentDemo, drawRectDemo, drawCircleDemo, drawProgressBarDemo}; +int demoLength = (sizeof(demos) / sizeof(Demo)); +long timeSinceLastModeSwitch = 0; + +void loop() { + // clear the display + display.clear(); + // draw the current demo method + demos[demoMode](); + + display.setFont(ArialMT_Plain_10); + display.setTextAlignment(TEXT_ALIGN_RIGHT); + display.drawString(128, 54, String(millis())); + // write the buffer to the display + display.display(); + + if (millis() - timeSinceLastModeSwitch > DEMO_DURATION) { + demoMode = (demoMode + 1) % demoLength; + timeSinceLastModeSwitch = millis(); + } + counter++; + delay(10); +} diff --git a/Software/src/main.cpp.test b/Software/src/main.cpp.test new file mode 100644 index 0000000..78c4df5 --- /dev/null +++ b/Software/src/main.cpp.test @@ -0,0 +1,58 @@ +#include +#include +#include + +#include "nfc.h" +#include "pins.h" +#include "helpers.h" + +NFC* nfc; + +void setup() +{ + Serial.begin(115200); // Initialize serial communications with the PC + while (!Serial); // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4) + + nfc = new NFC(PIN_RFID_SPI_SS, PIN_RFID_RST); +} + +void loop() +{ + if(nfc->checkforCard()) + { + Serial.println("Card detected"); + if(nfc->connecttoCard()) + { + Serial.println("Card connected"); + + MFRC522::StatusCode state; + + byte request_buffer[APDU_BUFFER_SIZE] = {0x90, 0x5A, 0x00, 0x00, 0x03, 0x42, 0x41, 0x46, 0x00}; + byte request_buffer_size = 9; + byte response_buffer[APDU_BUFFER_SIZE] = {0}; + byte response_buffer_size; + printbytes(request_buffer, request_buffer_size); + + state = nfc->Transceive(request_buffer, request_buffer_size, response_buffer, &response_buffer_size); + if (state != MFRC522::STATUS_OK) + { + Serial.println("Data Exchange failed"); + Serial.println(state); + } + Serial.println("Data Exchange complete"); + printbytes(response_buffer, response_buffer_size); + + state = nfc->Transceive(request_buffer, request_buffer_size, response_buffer, &response_buffer_size); + if (state != MFRC522::STATUS_OK) + { + Serial.println("Data Exchange failed"); + Serial.println(state); + } + Serial.println("Data Exchange complete"); + printbytes(response_buffer, response_buffer_size); + + nfc->disconnectCard(); + Serial.println("Card disconnected"); + } + } +} \ No newline at end of file diff --git a/Software/test/README b/Software/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/Software/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html