/* xsns_80_mfrc522.ino - Support for MFRC522 (SPI) NFC Tag Reader on Tasmota Copyright (C) 2021 Theo Arends This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef USE_SPI #ifdef USE_RC522 /*********************************************************************************************\ * MFRC522 - 13.56 MHz RFID reader * * Connections: * MFRC522 ESP8266 Tasmota * ------- -------------- ---------- * SDA GPIO0..5,15,16 RC522 CS * SCK GPIO14 SPI CLK * MOSI GPIO13 SPI MOSI * MISO GPIO12 SPI MISO * IRQ not used * Gnd Gnd * Rst GPIO0..5,15,16 RC522 Rst * 3V3 3V3 \*********************************************************************************************/ #define XSNS_80 80 //#define USE_RC522_DATA_FUNCTION // Add support for reading data block content (+0k4 code) //#define USE_RC522_TYPE_INFORMATION // Add support for showing card type (+0k4 code) #include MFRC522 *Mfrc522; struct RC522 { char uids[21]; // Number of bytes in the UID. 4, 7 or 10 bool present = false; uint8_t scantimer = 16; } Rc522; void RC522ScanForTag(void) { // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle. And if present, select one. if (!Mfrc522->PICC_IsNewCardPresent() || !Mfrc522->PICC_ReadCardSerial()) { return; } ToHex_P((unsigned char*)Mfrc522->uid.uidByte, Mfrc522->uid.size, Rc522.uids, sizeof(Rc522.uids)); ResponseTime_P(PSTR(",\"RC522\":{\"UID\":\"%s\""), Rc522.uids); MFRC522::PICC_Type picc_type = Mfrc522->PICC_GetType(Mfrc522->uid.sak); #ifdef USE_RC522_DATA_FUNCTION if ( picc_type == MFRC522::PICC_TYPE_MIFARE_MINI || picc_type == MFRC522::PICC_TYPE_MIFARE_1K || picc_type == MFRC522::PICC_TYPE_MIFARE_4K) { MFRC522::MIFARE_Key key; for (uint32_t i = 0; i < 6; i++) { key.keyByte[i] = 0xFF; } MFRC522::StatusCode status = Mfrc522->PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 1, &key, &(Mfrc522->uid)); if (status == MFRC522::STATUS_OK) { uint8_t buffer[18]; // The buffer must be at least 18 bytes because a CRC_A is also returned uint8_t size = sizeof(buffer); status = (MFRC522::StatusCode) Mfrc522->MIFARE_Read(1, buffer, &size); if (status == MFRC522::STATUS_OK) { char card_datas[34]; for (uint32_t i = 0; i < size -2; i++) { if ((isalpha(buffer[i])) || ((isdigit(buffer[i])))) { card_datas[i] = char(buffer[i]); } else { card_datas[i] = '\0'; } } ResponseAppend_P(PSTR(",\"" D_JSON_DATA "\":\"%s\""), card_datas); } } } #endif // USE_RC522_DATA_FUNCTION #ifdef USE_RC522_TYPE_INFORMATION ResponseAppend_P(PSTR(",\"" D_JSON_TYPE "\":\"%s\""), Mfrc522->PICC_GetTypeName(picc_type)); #endif // USE_RC522_TYPE_INFORMATION ResponseJsonEndEnd(); MqttPublishTeleSensor(); Mfrc522->PICC_HaltA(); // Halt PICC Mfrc522->PCD_StopCrypto1(); // Stop encryption on PCD Rc522.scantimer = 7; // Ignore tags found for two seconds } void RC522Init(void) { if (PinUsed(GPIO_RC522_CS) && PinUsed(GPIO_RC522_RST) && TasmotaGlobal.spi_enabled) { Mfrc522 = new MFRC522(Pin(GPIO_RC522_CS), Pin(GPIO_RC522_RST)); SPI.begin(); Mfrc522->PCD_Init(); // if (Mfrc522->PCD_PerformSelfTest()) { // Saves 0k5 code uint8_t v = Mfrc522->PCD_ReadRegister(Mfrc522->VersionReg); char ver[8] = { 0 }; switch (v) { case 0x92: strcpy_P(ver, PSTR("v2.0")); break; case 0x91: strcpy_P(ver, PSTR("v1.0")); break; case 0x88: strcpy_P(ver, PSTR("clone")); break; case 0x00: case 0xFF: strcpy_P(ver, PSTR("fail")); break; } uint8_t empty_uid[4] = { 0 }; ToHex_P((unsigned char*)empty_uid, sizeof(empty_uid), Rc522.uids, sizeof(Rc522.uids)); AddLog(LOG_LEVEL_INFO, PSTR("MFR: RC522 Rfid Reader detected %s"), ver); Rc522.present = true; // } // Mfrc522->PCD_Init(); // Re-init as SelfTest blows init } } #ifdef USE_WEBSERVER void RC522Show(void) { WSContentSend_PD(PSTR("{s}RC522 UID{m}%s{e}"), Rc522.uids); } #endif // USE_WEBSERVER /*********************************************************************************************\ * Supported commands for Sensor80: * * Sensor80 1 - Show antenna gain * Sensor80 1 - Set antenna gain 0..7 (default 4) \*********************************************************************************************/ bool RC522Command(void) { bool serviced = true; char argument[XdrvMailbox.data_len]; for (uint32_t ca = 0; ca < XdrvMailbox.data_len; ca++) { if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; } } switch (XdrvMailbox.payload) { case 1: // Antenna gain uint8_t gain; if (strchr(XdrvMailbox.data, ',') != nullptr) { gain = strtol(ArgV(argument, 2), nullptr, 10) & 0x7; Mfrc522->PCD_SetAntennaGain(gain << 4); } gain = Mfrc522->PCD_GetAntennaGain() >> 4; // 0..7 Response_P(PSTR("{\"Sensor80\":{\"Gain\":%d}}"), gain); break; } return serviced; } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xsns80(uint32_t function) { bool result = false; if (FUNC_INIT == function) { RC522Init(); } else if (Rc522.present) { switch (function) { case FUNC_EVERY_250_MSECOND: if (Rc522.scantimer) { Rc522.scantimer--; } else { RC522ScanForTag(); } break; case FUNC_COMMAND_SENSOR: if (XSNS_80 == XdrvMailbox.index) { result = RC522Command(); } break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: RC522Show(); break; #endif // USE_WEBSERVER } } return result; } #endif // USE_RC522 #endif // USE_SPI