diff --git a/components/auth/CMakeLists.txt b/components/auth/CMakeLists.txt index 2177a47..cd68d93 100755 --- a/components/auth/CMakeLists.txt +++ b/components/auth/CMakeLists.txt @@ -1,4 +1,4 @@ -set(srcs "src/auth.c" "src/rc522_2.c" "src/wiegand.c" "src/main_wiegand.c") +set(srcs "src/auth.c" "src/wiegand.c" "src/wiegand_reader.c") idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "include" diff --git a/components/auth/include/auth.h b/components/auth/include/auth.h old mode 100644 new mode 100755 index 5c9528e..81ac0ed --- a/components/auth/include/auth.h +++ b/components/auth/include/auth.h @@ -1,9 +1,17 @@ #ifndef AUTH_H #define AUTH_H +#pragma once + #include -void auth_set_enable(bool value); -bool auth_get_enable(void); +void auth_init(void); // Iniciar serviço +void auth_set_enabled(bool enabled); // Habilitar ou desabilitar leitura +bool auth_is_enabled(void); // Verificar estado + +bool auth_add_tag(const char *tag); // Adiciona uma tag válida +bool auth_remove_tag(const char *tag); // Remove tag +bool auth_tag_exists(const char *tag); // Verifica se tag é válida +void auth_list_tags(void); // Opcional: listar no log #endif // AUTH_H diff --git a/components/auth/include/rc522_2.h b/components/auth/include/rc522_2.h deleted file mode 100644 index 4044a60..0000000 --- a/components/auth/include/rc522_2.h +++ /dev/null @@ -1,333 +0,0 @@ -/* - * rc522.h - * - * Created on: - * Author: - */ - -#ifndef MAIN_RC522_H_ -#define MAIN_RC522_H_ - -#include -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_system.h" -#include "driver/spi_master.h" -#include "soc/gpio_struct.h" -#include "driver/gpio.h" -#include "esp_timer.h" - -#include - -#define null 0 - -#define F(x) x -#define printfln(x) printf(x) - - -#define PIN_NUM_MISO GPIO_NUM_27 -#define PIN_NUM_MOSI GPIO_NUM_14 -#define PIN_NUM_CLK GPIO_NUM_12 -#define PIN_NUM_CS GPIO_NUM_18 -#define PIN_NUM_RST GPIO_NUM_35 - -//#define PIN_NUM_MISO 25 -//#define PIN_NUM_MOSI 23 -//#define PIN_NUM_CLK 19 -//#define PIN_NUM_CS 22 -//#define PIN_NUM_RST 14 - - -#define MFRC522_SPICLOCK SPI_CLOCK_DIV4 // MFRC522 accept upto 10MHz - -typedef uint8_t byte; -// Firmware data for self-test -// Reference values based on firmware version -// Hint: if needed, you can remove unused self-test data to save flash memory -// -// Version 0.0 (0x90) -// Philips Semiconductors; Preliminary Specification Revision 2.0 - 01 August 2005; 16.1 self-test - -// MFRC522 registers. Described in chapter 9 of the datasheet. -// When using SPI all addresses are shifted one bit left in the "SPI address byte" (section 8.1.2.3) -typedef enum PCD_Register { - // Page 0: Command and status - // 0x00 // reserved for future use - CommandReg = 0x01 << 1, // starts and stops command execution - ComIEnReg = 0x02 << 1, // enable and disable interrupt request control bits - DivIEnReg = 0x03 << 1, // enable and disable interrupt request control bits - ComIrqReg = 0x04 << 1, // interrupt request bits - DivIrqReg = 0x05 << 1, // interrupt request bits - ErrorReg = 0x06 << 1, // error bits showing the error status of the last command executed - Status1Reg = 0x07 << 1, // communication status bits - Status2Reg = 0x08 << 1, // receiver and transmitter status bits - FIFODataReg = 0x09 << 1, // input and output of 64 byte FIFO buffer - FIFOLevelReg = 0x0A << 1, // number of bytes stored in the FIFO buffer - WaterLevelReg = 0x0B << 1, // level for FIFO underflow and overflow warning - ControlReg = 0x0C << 1, // miscellaneous control registers - BitFramingReg = 0x0D << 1, // adjustments for bit-oriented frames - CollReg = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface - // 0x0F // reserved for future use - - // Page 1: Command - // 0x10 // reserved for future use - ModeReg = 0x11 << 1, // defines general modes for transmitting and receiving - TxModeReg = 0x12 << 1, // defines transmission data rate and framing - RxModeReg = 0x13 << 1, // defines reception data rate and framing - TxControlReg = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2 - TxASKReg = 0x15 << 1, // controls the setting of the transmission modulation - TxSelReg = 0x16 << 1, // selects the internal sources for the antenna driver - RxSelReg = 0x17 << 1, // selects internal receiver settings - RxThresholdReg = 0x18 << 1, // selects thresholds for the bit decoder - DemodReg = 0x19 << 1, // defines demodulator settings - // 0x1A // reserved for future use - // 0x1B // reserved for future use - MfTxReg = 0x1C << 1, // controls some MIFARE communication transmit parameters - MfRxReg = 0x1D << 1, // controls some MIFARE communication receive parameters - // 0x1E // reserved for future use - SerialSpeedReg = 0x1F << 1, // selects the speed of the serial UART interface - - // Page 2: Configuration - // 0x20 // reserved for future use - CRCResultRegH = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation - CRCResultRegL = 0x22 << 1, - // 0x23 // reserved for future use - ModWidthReg = 0x24 << 1, // controls the ModWidth setting? - // 0x25 // reserved for future use - RFCfgReg = 0x26 << 1, // configures the receiver gain - GsNReg = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation - CWGsPReg = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation - ModGsPReg = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation - TModeReg = 0x2A << 1, // defines settings for the internal timer - TPrescalerReg = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg. - TReloadRegH = 0x2C << 1, // defines the 16-bit timer reload value - TReloadRegL = 0x2D << 1, - TCounterValueRegH = 0x2E << 1, // shows the 16-bit timer value - TCounterValueRegL = 0x2F << 1, - - // Page 3: Test Registers - // 0x30 // reserved for future use - TestSel1Reg = 0x31 << 1, // general test signal configuration - TestSel2Reg = 0x32 << 1, // general test signal configuration - TestPinEnReg = 0x33 << 1, // enables pin output driver on pins D1 to D7 - TestPinValueReg = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus - TestBusReg = 0x35 << 1, // shows the status of the internal test bus - AutoTestReg = 0x36 << 1, // controls the digital self-test - VersionReg = 0x37 << 1, // shows the software version - AnalogTestReg = 0x38 << 1, // controls the pins AUX1 and AUX2 - TestDAC1Reg = 0x39 << 1, // defines the test value for TestDAC1 - TestDAC2Reg = 0x3A << 1, // defines the test value for TestDAC2 - TestADCReg = 0x3B << 1 // shows the value of ADC I and Q channels - // 0x3C // reserved for production tests - // 0x3D // reserved for production tests - // 0x3E // reserved for production tests - // 0x3F // reserved for production tests -}PCD_Register; - -// MFRC522 commands. Described in chapter 10 of the datasheet. -typedef enum PCD_Command { - PCD_Idle = 0x00, // no action, cancels current command execution - PCD_Mem = 0x01, // stores 25 bytes into the internal buffer - PCD_GenerateRandomID = 0x02, // generates a 10-byte random ID number - PCD_CalcCRC = 0x03, // activates the CRC coprocessor or performs a self-test - PCD_Transmit = 0x04, // transmits data from the FIFO buffer - PCD_NoCmdChange = 0x07, // no command change, can be used to modify the CommandReg register bits without affecting the command, for example, the PowerDown bit - PCD_Receive = 0x08, // activates the receiver circuits - PCD_Transceive = 0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission - PCD_MFAuthent = 0x0E, // performs the MIFARE standard authentication as a reader - PCD_SoftReset = 0x0F // resets the MFRC522 -}PCD_Command; - -// MFRC522 RxGain[2:0] masks, defines the receiver's signal voltage gain factor (on the PCD). -// Described in 9.3.3.6 / table 98 of the datasheet at http://www.nxp.com/documents/data_sheet/MFRC522.pdf -typedef enum PCD_RxGain { - RxGain_18dB = 0x00 << 4, // 000b - 18 dB, minimum - RxGain_23dB = 0x01 << 4, // 001b - 23 dB - RxGain_18dB_2 = 0x02 << 4, // 010b - 18 dB, it seems 010b is a duplicate for 000b - RxGain_23dB_2 = 0x03 << 4, // 011b - 23 dB, it seems 011b is a duplicate for 001b - RxGain_33dB = 0x04 << 4, // 100b - 33 dB, average, and typical default - RxGain_38dB = 0x05 << 4, // 101b - 38 dB - RxGain_43dB = 0x06 << 4, // 110b - 43 dB - RxGain_48dB = 0x07 << 4, // 111b - 48 dB, maximum - RxGain_min = 0x00 << 4, // 000b - 18 dB, minimum, convenience for RxGain_18dB - RxGain_avg = 0x04 << 4, // 100b - 33 dB, average, convenience for RxGain_33dB - RxGain_max = 0x07 << 4 // 111b - 48 dB, maximum, convenience for RxGain_48dB -}PCD_RxGain; - -// Commands sent to the PICC. -typedef enum PICC_Command { - // The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4) - PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. - PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. - PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision. - PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1 - PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 - PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 - PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT. - PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset. - // The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9) - // Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on the sector. - // The read/write commands can also be used for MIFARE Ultralight. - PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A - PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B - PICC_CMD_MF_READ = 0x30, // Reads one 16 byte block from the authenticated sector of the PICC. Also used for MIFARE Ultralight. - PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 byte block to the authenticated sector of the PICC. Called "COMPATIBILITY WRITE" for MIFARE Ultralight. - PICC_CMD_MF_DECREMENT = 0xC0, // Decrements the contents of a block and stores the result in the internal data register. - PICC_CMD_MF_INCREMENT = 0xC1, // Increments the contents of a block and stores the result in the internal data register. - PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register. - PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block. - // The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6) - // The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight. - PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 byte page to the PICC. -}PICC_Command; - -// MIFARE constants that does not fit anywhere else -enum MIFARE_Misc { - MF_ACK = 0xA, // The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK. - MF_KEY_SIZE = 6 // A Mifare Crypto1 key is 6 bytes. -}; - -// PICC types we can detect. Remember to update PICC_GetTypeName() if you add more. -// last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered -typedef enum PICC_Type { - PICC_TYPE_UNKNOWN , - PICC_TYPE_ISO_14443_4 , // PICC compliant with ISO/IEC 14443-4 - PICC_TYPE_ISO_18092 , // PICC compliant with ISO/IEC 18092 (NFC) - PICC_TYPE_MIFARE_MINI , // MIFARE Classic protocol, 320 bytes - PICC_TYPE_MIFARE_1K , // MIFARE Classic protocol, 1KB - PICC_TYPE_MIFARE_4K , // MIFARE Classic protocol, 4KB - PICC_TYPE_MIFARE_UL , // MIFARE Ultralight or Ultralight C - PICC_TYPE_MIFARE_PLUS , // MIFARE Plus - PICC_TYPE_MIFARE_DESFIRE, // MIFARE DESFire - PICC_TYPE_TNP3XXX , // Only mentioned in NXP AN 10833 MIFARE Type Identification Procedure - PICC_TYPE_NOT_COMPLETE = 0xff // SAK indicates UID is not complete. -}PICC_Type; - -// Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more. -// last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered -typedef enum StatusCode { - STATUS_OK , // Success - STATUS_ERROR , // Error in communication - STATUS_COLLISION , // Collission detected - STATUS_TIMEOUT , // Timeout in communication. - STATUS_NO_ROOM , // A buffer is not big enough. - STATUS_INTERNAL_ERROR , // Internal error in the code. Should not happen ;-) - STATUS_INVALID , // Invalid argument. - STATUS_CRC_WRONG , // The CRC_A does not match - STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK. -}StatusCode; - -// A struct used for passing the UID of a PICC. -typedef struct { - byte size; // Number of bytes in the UID. 4, 7 or 10. - byte uidByte[10]; - byte sak; // The SAK (Select acknowledge) byte returned from the PICC after successful selection. -} Uid; - -// A struct used for passing a MIFARE Crypto1 key -typedef struct { - byte keyByte[MF_KEY_SIZE]; -} MIFARE_Key; - - -///////////////////////////////////////////////////////////////////////////////////// -// Basic interface functions for communicating with the MFRC522 -///////////////////////////////////////////////////////////////////////////////////// -void PCD_WriteRegister(uint8_t Register , uint8_t value); -void PCD_WriteRegisterMany(uint8_t Register, uint8_t count, uint8_t *values); -uint8_t PCD_ReadRegister(uint8_t Register); -void PCD_ReadRegisterMany(uint8_t Register,uint8_t count,uint8_t *values,uint8_t rxAlign); -void PCD_SetRegisterBitMask(uint8_t reg, byte mask); -void PCD_ClearRegisterBitMask(uint8_t reg, byte mask); -StatusCode PCD_CalculateCRC(byte *data, byte length, byte *result); - -///////////////////////////////////////////////////////////////////////////////////// -// Functions for manipulating the MFRC522 -///////////////////////////////////////////////////////////////////////////////////// -void PCD_Init(); -void PCD_Reset(); -void PCD_AntennaOn(); -void PCD_AntennaOff(); -byte PCD_GetAntennaGain(); -void PCD_SetAntennaGain(byte mask); -bool PCD_PerformSelfTest(); - -///////////////////////////////////////////////////////////////////////////////////// -// Power control functions -///////////////////////////////////////////////////////////////////////////////////// -void PCD_SoftPowerDown(); -void PCD_SoftPowerUp(); - -///////////////////////////////////////////////////////////////////////////////////// -// Functions for communicating with PICCs -///////////////////////////////////////////////////////////////////////////////////// -uint8_t PCD_TransceiveData(byte *sendData, byte sendLen, byte *backData, byte *backLen, byte *validBits, byte rxAlign, bool checkCRC); -uint8_t PCD_CommunicateWithPICC(byte command, byte waitIRq, byte *sendData, byte sendLen, byte *backData, byte *backLen, byte *validBits, byte rxAlign, bool checkCRC); -uint8_t PICC_RequestA(byte *bufferATQA, byte *bufferSize); -uint8_t PICC_WakeupA(byte *bufferATQA, byte *bufferSize); -uint8_t PICC_REQA_or_WUPA(uint8_t command,uint8_t *bufferATQA,uint8_t *bufferSize); -StatusCode PICC_Select(Uid *uid, byte validBits); -StatusCode PICC_HaltA(); - -///////////////////////////////////////////////////////////////////////////////////// -// Functions for communicating with MIFARE PICCs -///////////////////////////////////////////////////////////////////////////////////// -StatusCode PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key *key, Uid *uid); -void PCD_StopCrypto1(); -StatusCode MIFARE_Read(byte blockAddr, byte *buffer, byte *bufferSize); -StatusCode MIFARE_Write(byte blockAddr, byte *buffer, byte bufferSize); -StatusCode MIFARE_Ultralight_Write(byte page, byte *buffer, byte bufferSize); -StatusCode MIFARE_Decrement(byte blockAddr, int32_t delta); -StatusCode MIFARE_Increment(byte blockAddr, int32_t delta); -StatusCode MIFARE_Restore(byte blockAddr); -StatusCode MIFARE_Transfer(byte blockAddr); -StatusCode MIFARE_GetValue(byte blockAddr, int32_t *value); -StatusCode MIFARE_SetValue(byte blockAddr, int32_t value); -StatusCode PCD_NTAG216_AUTH(byte *passWord, byte pACK[]); - -///////////////////////////////////////////////////////////////////////////////////// -// Support functions -///////////////////////////////////////////////////////////////////////////////////// -StatusCode PCD_MIFARE_Transceive(byte *sendData, byte sendLen, bool acceptTimeout); -// old function used too much memory, now name moved to flash; if you need char, copy from flash to memory -//const char *GetStatusCodeName(byte code); -const char *GetStatusCodeName(StatusCode code); -PICC_Type PICC_GetType(byte sak); -// old function used too much memory, now name moved to flash; if you need char, copy from flash to memory -//const char *PICC_GetTypeName(byte type); -const char *PICC_GetTypeName(PICC_Type type); - -// Support functions for debuging -void PCD_DumpVersionToSerial(); -void PICC_DumpToSerial(Uid *uid); -void PICC_DumpDetailsToSerial(Uid *uid); -void PICC_DumpMifareClassicToSerial(Uid *uid, PICC_Type piccType, MIFARE_Key *key); -void PICC_DumpMifareClassicSectorToSerial(Uid *uid, MIFARE_Key *key, byte sector); -void PICC_DumpMifareUltralightToSerial(); - -// Advanced functions for MIFARE - -void MIFARE_SetAccessBits(byte *accessBitBuffer, byte g0, byte g1, byte g2, byte g3); - -bool MIFARE_OpenUidBackdoor(bool logErrors); - -bool MIFARE_SetUid(byte *newUid, byte uidSize, bool logErrors); - -bool MIFARE_UnbrickUidSector(bool logErrors); - -///////////////////////////////////////////////////////////////////////////////////// -// Convenience functions - does not add extra functionality -///////////////////////////////////////////////////////////////////////////////////// -bool PICC_IsNewCardPresent(); -bool PICC_ReadCardSerial(); - - -//byte _chipSelectPin; // Arduino pin connected to MFRC522's SPI slave select input (Pin 24, NSS, active low) -//byte _resetPowerDownPin; // Arduino pin connected to MFRC522's reset and power down input (Pin 6, NRSTPD, active low) -StatusCode MIFARE_TwoStepHelper(byte command, byte blockAddr, int32_t data); - -void initRc522(); - -#endif /* MAIN_RC522_H_ */ diff --git a/components/auth/include/wiegand.h b/components/auth/include/wiegand.h old mode 100644 new mode 100755 diff --git a/components/auth/include/main_wiegand.h b/components/auth/include/wiegand_reader.h old mode 100644 new mode 100755 similarity index 100% rename from components/auth/include/main_wiegand.h rename to components/auth/include/wiegand_reader.h diff --git a/components/auth/src/auth.c b/components/auth/src/auth.c old mode 100644 new mode 100755 index 524afb9..a278d57 --- a/components/auth/src/auth.c +++ b/components/auth/src/auth.c @@ -1,11 +1,108 @@ #include "auth.h" +#include "wiegand_reader.h" +#include "wiegand_handler.h" +#include +#include +#include +#include -static bool enable = false; +#define MAX_TAGS 50 +#define TAG_LEN 20 -void auth_set_enable(bool value) { - enable = value; +static const char *TAG = "Auth"; +static bool enabled = true; +static char valid_tags[MAX_TAGS][TAG_LEN]; +static int tag_count = 0; + +static bool is_tag_valid(const char *tag) { + for (int i = 0; i < tag_count; i++) { + if (strncmp(valid_tags[i], tag, TAG_LEN) == 0) { + return true; + } + } + return false; } -bool auth_get_enable(void) { - return enable; +void auth_set_enabled(bool value) { + enabled = value; + ESP_LOGI(TAG, "Wiegand reader %s", enabled ? "ENABLED" : "DISABLED"); +} + +bool auth_is_enabled(void) { + return enabled; +} + +bool auth_add_tag(const char *tag) { + if (tag_count >= MAX_TAGS) return false; + if (auth_tag_exists(tag)) return true; + + strncpy(valid_tags[tag_count], tag, TAG_LEN - 1); + valid_tags[tag_count][TAG_LEN - 1] = '\0'; + tag_count++; + ESP_LOGI(TAG, "Tag added: %s", tag); + return true; +} + +bool auth_remove_tag(const char *tag) { + for (int i = 0; i < tag_count; i++) { + if (strncmp(valid_tags[i], tag, TAG_LEN) == 0) { + for (int j = i; j < tag_count - 1; j++) { + strncpy(valid_tags[j], valid_tags[j + 1], TAG_LEN); + } + tag_count--; + ESP_LOGI(TAG, "Tag removed: %s", tag); + return true; + } + } + return false; +} + +bool auth_tag_exists(const char *tag) { + return is_tag_valid(tag); +} + +void auth_list_tags(void) { + ESP_LOGI(TAG, "Registered Tags (%d):", tag_count); + for (int i = 0; i < tag_count; i++) { + ESP_LOGI(TAG, "- %s", valid_tags[i]); + } +} + +// Função de callback para o reader +static void on_card_read(const wiegand_packet_t *packet) { + if (!enabled) { + ESP_LOGW(TAG, "Ignoring Wiegand data: reader is disabled"); + return; + } + + char tag[TAG_LEN]; + memset(tag, 0, TAG_LEN); + + if (packet->bits == 26) { + snprintf(tag, TAG_LEN, "%03d%03d%03d", packet->data[0], packet->data[1], packet->data[2]); + } else if (packet->bits == 34) { + snprintf(tag, TAG_LEN, "%03d%03d%03d%03d", packet->data[0], packet->data[1], packet->data[2], packet->data[3]); + } else { + ESP_LOGW(TAG, "Unsupported bit length: %d", packet->bits); + return; + } + + ESP_LOGI(TAG, "Tag read: %s", tag); + + if (is_tag_valid(tag)) { + ESP_LOGI(TAG, "Authorized tag."); + evse_authorize(); + + if (ocpp_is_TransactionActive()) { + ocpp_end_transaction(tag); + } else { + ocpp_begin_transaction(tag); + } + } else { + ESP_LOGW(TAG, "Unauthorized tag."); + } +} + +void auth_init(void) { + wiegand_reader_init(19, 18, on_card_read); } diff --git a/components/auth/src/rc522_2.c b/components/auth/src/rc522_2.c deleted file mode 100755 index 7ca1e7c..0000000 --- a/components/auth/src/rc522_2.c +++ /dev/null @@ -1,2369 +0,0 @@ -/* - * rc522.c - * - * Created on: - * Author: - */ - -#include "rc522_2.h" -#include "esp_log.h" -#include "evse_api.h" -#include "auth.h" - -const static char *TAG = "AUTH"; - -spi_device_handle_t spi; -// Member variables -Uid uid; // Used by PICC_ReadCardSerial(). - -const byte MFRC522_firmware_referenceV0_0[] = { - 0x00, 0x87, 0x98, 0x0f, 0x49, 0xFF, 0x07, 0x19, - 0xBF, 0x22, 0x30, 0x49, 0x59, 0x63, 0xAD, 0xCA, - 0x7F, 0xE3, 0x4E, 0x03, 0x5C, 0x4E, 0x49, 0x50, - 0x47, 0x9A, 0x37, 0x61, 0xE7, 0xE2, 0xC6, 0x2E, - 0x75, 0x5A, 0xED, 0x04, 0x3D, 0x02, 0x4B, 0x78, - 0x32, 0xFF, 0x58, 0x3B, 0x7C, 0xE9, 0x00, 0x94, - 0xB4, 0x4A, 0x59, 0x5B, 0xFD, 0xC9, 0x29, 0xDF, - 0x35, 0x96, 0x98, 0x9E, 0x4F, 0x30, 0x32, 0x8D}; -// Version 1.0 (0x91) -// NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test -const byte MFRC522_firmware_referenceV1_0[] = { - 0x00, 0xC6, 0x37, 0xD5, 0x32, 0xB7, 0x57, 0x5C, - 0xC2, 0xD8, 0x7C, 0x4D, 0xD9, 0x70, 0xC7, 0x73, - 0x10, 0xE6, 0xD2, 0xAA, 0x5E, 0xA1, 0x3E, 0x5A, - 0x14, 0xAF, 0x30, 0x61, 0xC9, 0x70, 0xDB, 0x2E, - 0x64, 0x22, 0x72, 0xB5, 0xBD, 0x65, 0xF4, 0xEC, - 0x22, 0xBC, 0xD3, 0x72, 0x35, 0xCD, 0xAA, 0x41, - 0x1F, 0xA7, 0xF3, 0x53, 0x14, 0xDE, 0x7E, 0x02, - 0xD9, 0x0F, 0xB5, 0x5E, 0x25, 0x1D, 0x29, 0x79}; -// Version 2.0 (0x92) -// NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test -const byte MFRC522_firmware_referenceV2_0[] = { - 0x00, 0xEB, 0x66, 0xBA, 0x57, 0xBF, 0x23, 0x95, - 0xD0, 0xE3, 0x0D, 0x3D, 0x27, 0x89, 0x5C, 0xDE, - 0x9D, 0x3B, 0xA7, 0x00, 0x21, 0x5B, 0x89, 0x82, - 0x51, 0x3A, 0xEB, 0x02, 0x0C, 0xA5, 0x00, 0x49, - 0x7C, 0x84, 0x4D, 0xB3, 0xCC, 0xD2, 0x1B, 0x81, - 0x5D, 0x48, 0x76, 0xD5, 0x71, 0x61, 0x21, 0xA9, - 0x86, 0x96, 0x83, 0x38, 0xCF, 0x9D, 0x5B, 0x6D, - 0xDC, 0x15, 0xBA, 0x3E, 0x7D, 0x95, 0x3B, 0x2F}; -// Clone -// Fudan Semiconductor FM17522 (0x88) -const byte FM17522_firmware_reference[] = { - 0x00, 0xD6, 0x78, 0x8C, 0xE2, 0xAA, 0x0C, 0x18, - 0x2A, 0xB8, 0x7A, 0x7F, 0xD3, 0x6A, 0xCF, 0x0B, - 0xB1, 0x37, 0x63, 0x4B, 0x69, 0xAE, 0x91, 0xC7, - 0xC3, 0x97, 0xAE, 0x77, 0xF4, 0x37, 0xD7, 0x9B, - 0x7C, 0xF5, 0x3C, 0x11, 0x8F, 0x15, 0xC3, 0xD7, - 0xC1, 0x5B, 0x00, 0x2A, 0xD0, 0x75, 0xDE, 0x9E, - 0x51, 0x64, 0xAB, 0x3E, 0xE9, 0x15, 0xB5, 0xAB, - 0x56, 0x9A, 0x98, 0x82, 0x26, 0xEA, 0x2A, 0x62}; - -// Size of the MFRC522 FIFO -// static byte FIFO_SIZE = 64; // The FIFO is 64 bytes. -// Default value for unused pin -// static uint8_t UNUSED_PIN = UINT8_MAX; -///////////////////////////////////////////////////////////////////////////////////// -// Basic interface functions for communicating with the MFRC522 -///////////////////////////////////////////////////////////////////////////////////// -static byte bufferSector[70]; -/** - * Writes a byte to the specified register in the MFRC522 chip. - * The interface is described in the datasheet section 8.1.2. - */ -void PCD_Version() -{ - - uint8_t ver = PCD_ReadRegister(VersionReg); - - if (ver == 0x92 || ver == 178) - { - printf("MFRC522 Version 2 detected.\n"); - } - else if (ver == 0x91) - { - printf("MFRC522 Version 1 detected.\n"); - } - else - { - printf("Version %d\n", ver); - printf("Is connected device MFRC522 ? If yes, check the wiring again.\n"); - /* - - for (int i = 5; i >= 0; i--) - { - printf("Restarting in %d seconds...\n", i); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - - printf("Restarting Now.\n"); - fflush(stdout); - esp_restart(); - */ - } -} -void PCD_WriteRegister(uint8_t Register, uint8_t value) -{ - - esp_err_t ret; - uint8_t data[2]; - - data[0] = Register; - data[1] = value; - - static spi_transaction_t t1; - - memset(&t1, 0, sizeof(t1)); // Zero out the transaction - t1.length = 16; - t1.tx_buffer = &data; - - ret = spi_device_transmit(spi, &t1); - assert(ret == ESP_OK); - vTaskDelay(1 / portTICK_PERIOD_MS); -} - -void PCD_WriteRegisterMany(uint8_t Register, uint8_t count, uint8_t *values) -{ - - esp_err_t ret; - uint8_t *total = malloc(sizeof(uint8_t) * (count + 1)); - - total[0] = Register; - memcpy(total + 1, values, count); - - static spi_transaction_t t1; - memset(&t1, 0, sizeof(t1)); // Zero out the transaction - t1.length = 8 * (count + 1); - t1.tx_buffer = total; - - ret = spi_device_transmit(spi, &t1); - assert(ret == ESP_OK); - vTaskDelay(1 / portTICK_PERIOD_MS); -} - -uint8_t PCD_ReadRegister(uint8_t Register) -{ - - esp_err_t ret; - uint8_t reg[2]; - uint8_t val[2]; - - reg[0] = Register | 0x80; - reg[1] = 0; - - spi_transaction_t t; - memset(&t, 0, sizeof(t)); // Zero out the transaction - t.length = 16; - t.tx_buffer = ® - t.rx_buffer = &val; - ret = spi_device_transmit(spi, &t); - assert(ret == ESP_OK); - - // t.tx_buffer=(uint8_t*)0; - // ret = spi_device_transmit(spi,&t); - // assert(ret==ESP_OK); - - vTaskDelay(1 / portTICK_PERIOD_MS); - // printf("0x%x 0x%x\n",val[0],val[1]); - return val[1]; -} - -void PCD_ReadRegisterMany( - uint8_t Register, ///< The register to read from. One of the PCD_Register enums. - uint8_t count, ///< The number of bytes to read - uint8_t *values, ///< Byte array to store the values in. - uint8_t rxAlign ///< Only bit positions rxAlign..7 in values[0] are updated. -) -{ - if (count == 0) - { - return; - } - - esp_err_t ret; - uint8_t reg[2]; - uint8_t val[2]; - - reg[0] = Register | 0x80; - - spi_transaction_t t; - - uint8_t index = 0; // Index in values array. - count--; // One read is performed outside of the loop - - memset(&t, 0, sizeof(t)); // Zero out the transaction - t.length = 8; - t.tx_buffer = ® - t.rx_buffer = &val; - ret = spi_device_transmit(spi, &t); - assert(ret == ESP_OK); - - if (rxAlign) - { // Only update bit positions rxAlign..7 in values[0] - // Create bit mask for bit positions rxAlign..7 - byte mask = (0xFF << rxAlign) & 0xFF; - // Read value and tell that we want to read the same address again. - ret = spi_device_transmit(spi, &t); - byte value = val[0]; - // Apply mask to both current value of values[0] and the new data in value. - values[0] = (values[0] & ~mask) | (value & mask); - index++; - } - while (index < count) - { - ret = spi_device_transmit(spi, &t); - values[index] = val[0]; - index++; - } - t.tx_buffer = 0; - ret = spi_device_transmit(spi, &t); - values[index] = val[0]; // Read the final byte. Send 0 to stop reading. -} - -/** - * Sets the bits given in mask in register reg. - */ - -void PCD_SetRegisterBitMask(uint8_t reg, uint8_t mask) -{ - uint8_t tmp; - tmp = PCD_ReadRegister(reg); - PCD_WriteRegister(reg, tmp | mask); // set bit mask -} - -/** - * Clears the bits given in mask from register reg. - */ -void PCD_ClearRegisterBitMask( - uint8_t reg, ///< The register to update. One of the PCD_Register enums. - uint8_t mask ///< The bits to clear. -) -{ - uint8_t tmp; - tmp = PCD_ReadRegister(reg); - PCD_WriteRegister(reg, tmp & (~mask)); // clear bit mask -} - -/** - * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode PCD_CalculateCRC(byte *data, ///< In: Pointer to the data to transfer to the FIFO for CRC calculation. - byte length, ///< In: The number of bytes to transfer. - byte *result ///< Out: Pointer to result buffer. Result is written to result[0..1], low byte first. -) -{ - PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. - PCD_WriteRegister(DivIrqReg, 0x04); // Clear the CRCIRq interrupt request bit - PCD_WriteRegister(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization - PCD_WriteRegisterMany(FIFODataReg, length, data); // Write data to the FIFO - PCD_WriteRegister(CommandReg, PCD_CalcCRC); // Start the calculation - - // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73μs. - // TODO check/modify for other architectures than Arduino Uno 16bit - - vTaskDelay(11 / portTICK_PERIOD_MS); - // Wait for the CRC calculation to complete. Each iteration of the while-loop takes 17.73us. - for (uint32_t i = 100000; i > 0; i--) - { - // DivIrqReg[7..0] bits are: Set2 reserved reserved MfinActIRq reserved CRCIRq reserved reserved - byte n = PCD_ReadRegister(DivIrqReg); - if (n & 0x04) - { // CRCIRq bit set - calculation done - PCD_WriteRegister(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. - // Transfer the result from the registers to the result buffer - result[0] = PCD_ReadRegister(CRCResultRegL); - result[1] = PCD_ReadRegister(CRCResultRegH); - return STATUS_OK; - } - } - // 89ms passed and nothing happend. Communication with the MFRC522 might be down. - return STATUS_TIMEOUT; -} // End PCD_CalculateCRC() - -///////////////////////////////////////////////////////////////////////////////////// -// Functions for manipulating the MFRC522 -///////////////////////////////////////////////////////////////////////////////////// - -/** - * Initializes the MFRC522 chip. - */ -void PCD_Init() -{ - - PCD_Version(); - /* - gpio_pad_select_gpio(PIN_NUM_RST); - gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT); - - // Hard Reset RFID - gpio_set_level(PIN_NUM_RST, 0); - vTaskDelay(50 / portTICK_PERIOD_MS); - gpio_set_level(PIN_NUM_RST, 1); - vTaskDelay(50 / portTICK_PERIOD_MS); - */ - - // Reset baud rates - PCD_WriteRegister(TxModeReg, 0x00); - PCD_WriteRegister(RxModeReg, 0x00); - // Reset ModWidthReg - PCD_WriteRegister(ModWidthReg, 0x26); - - // When communicating with a PICC we need a timeout if something goes wrong. - // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo]. - // TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg. - - PCD_WriteRegister(TModeReg, 0x80); // TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds - PCD_WriteRegister(TPrescalerReg, 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs. - PCD_WriteRegister(TReloadRegH, 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. - PCD_WriteRegister(TReloadRegL, 0xE8); - PCD_WriteRegister(TxASKReg, 0x40); // Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting - PCD_WriteRegister(ModeReg, 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4) - PCD_AntennaOn(); // Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset) - printf("Initialization successful.\n"); -} - -/** - * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. - */ -void PCD_Reset() -{ - PCD_WriteRegister(CommandReg, PCD_SoftReset); // Issue the SoftReset command. - // The datasheet does not mention how long the SoftRest command takes to complete. - // But the MFRC522 might have been in soft power-down mode (triggered by bit 4 of CommandReg) - // Section 8.8.2 in the datasheet says the oscillator start-up time is the start up time of the crystal + 37,74μs. Let us be generous: 50ms. - uint8_t count = 0; - do - { - // Wait for the PowerDown bit in CommandReg to be cleared (max 3x50ms) - vTaskDelay(50 / portTICK_PERIOD_MS); - } while ((PCD_ReadRegister(CommandReg) & (1 << 4)) && (++count) < 3); -} // End PCD_Reset() - -/** - * Turns the antenna on by enabling pins TX1 and TX2. - * After a reset these pins are disabled. - */ -void PCD_AntennaOn() -{ - uint8_t value = PCD_ReadRegister(TxControlReg); - if ((value & 0x03) != 0x03) - { - PCD_WriteRegister(TxControlReg, value | 0x03); - } - printf("Antenna turned on.\n"); -} - -/** - * Turns the antenna off by disabling pins TX1 and TX2. - */ -void PCD_AntennaOff() -{ - PCD_ClearRegisterBitMask(TxControlReg, 0x03); -} // End PCD_AntennaOff() - -/** - * Get the current MFRC522 Receiver Gain (RxGain[2:0]) value. - * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf - * NOTE: Return value scrubbed with (0x07<<4)=01110000b as RCFfgReg may use reserved bits. - * - * @return Value of the RxGain, scrubbed to the 3 bits used. - */ -byte PCD_GetAntennaGain() -{ - return PCD_ReadRegister(RFCfgReg) & (0x07 << 4); -} // End PCD_GetAntennaGain() - -/** - * Set the MFRC522 Receiver Gain (RxGain) to value specified by given mask. - * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf - * NOTE: Given mask is scrubbed with (0x07<<4)=01110000b as RCFfgReg may use reserved bits. - */ -void PCD_SetAntennaGain(byte mask) -{ - if (PCD_GetAntennaGain() != mask) - { // only bother if there is a change - PCD_ClearRegisterBitMask(RFCfgReg, (0x07 << 4)); // clear needed to allow 000 pattern - PCD_SetRegisterBitMask(RFCfgReg, mask & (0x07 << 4)); // only set RxGain[2:0] bits - } -} // End PCD_SetAntennaGain() - -/** - * Performs a self-test of the MFRC522 - * See 16.1.1 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf - * - * @return Whether or not the test passed. Or false if no firmware reference is available. - */ -bool PCD_PerformSelfTest() -{ - // This follows directly the steps outlined in 16.1.1 - // 1. Perform a soft reset. - PCD_Reset(); - - // 2. Clear the internal buffer by writing 25 bytes of 00h - byte ZEROES[25] = { - 0x00}; - PCD_WriteRegister(FIFOLevelReg, 0x80); // flush the FIFO buffer - PCD_WriteRegisterMany(FIFODataReg, 25, ZEROES); // write 25 bytes of 00h to FIFO - PCD_WriteRegister(CommandReg, PCD_Mem); // transfer to internal buffer - - // 3. Enable self-test - PCD_WriteRegister(AutoTestReg, 0x09); - - // 4. Write 00h to FIFO buffer - PCD_WriteRegister(FIFODataReg, 0x00); - - // 5. Start self-test by issuing the CalcCRC command - PCD_WriteRegister(CommandReg, PCD_CalcCRC); - - // 6. Wait for self-test to complete - byte n; - for (uint8_t i = 0; i < 0xFF; i++) - { - // The datasheet does not specify exact completion condition except - // that FIFO buffer should contain 64 bytes. - // While selftest is initiated by CalcCRC command - // it behaves differently from normal CRC computation, - // so one can't reliably use DivIrqReg to check for completion. - // It is reported that some devices does not trigger CRCIRq flag - // during selftest. - n = PCD_ReadRegister(FIFOLevelReg); - if (n >= 64) - { - break; - } - } - PCD_WriteRegister(CommandReg, PCD_Idle); // Stop calculating CRC for new content in the FIFO. - - // 7. Read out resulting 64 bytes from the FIFO buffer. - byte result[64]; - PCD_ReadRegisterMany(FIFODataReg, 64, result, 0); - - // Auto self-test done - // Reset AutoTestReg register to be 0 again. Required for normal operation. - PCD_WriteRegister(AutoTestReg, 0x00); - - // Determine firmware version (see section 9.3.4.8 in spec) - byte version = PCD_ReadRegister(VersionReg); - - // Pick the appropriate reference values - const byte *reference; - switch (version) - { - case 0x88: // Fudan Semiconductor FM17522 clone - reference = FM17522_firmware_reference; - break; - case 0x90: // Version 0.0 - reference = MFRC522_firmware_referenceV0_0; - break; - case 0x91: // Version 1.0 - reference = MFRC522_firmware_referenceV1_0; - break; - case 0x92: // Version 2.0 - reference = MFRC522_firmware_referenceV2_0; - break; - default: // Unknown version - return false; // abort test - } - - // Verify that the results match up to our expectations - for (uint8_t i = 0; i < 64; i++) - { - if (result[i] != reference[i]) - { - return false; - } - } - - // Test passed; all is good. - return true; -} // End PCD_PerformSelfTest() - -///////////////////////////////////////////////////////////////////////////////////// -// Power control -///////////////////////////////////////////////////////////////////////////////////// - -// IMPORTANT NOTE!!!! -// Calling any other function that uses CommandReg will disable soft power down mode !!! -// For more details about power control, refer to the datasheet - page 33 (8.6) - -void PCD_SoftPowerDown() -{ // Note : Only soft power down mode is available throught software - byte val = PCD_ReadRegister(CommandReg); // Read state of the command register - val |= (1 << 4); // set PowerDown bit ( bit 4 ) to 1 - PCD_WriteRegister(CommandReg, val); // write new value to the command register -} - -void PCD_SoftPowerUp() -{ - byte val = PCD_ReadRegister(CommandReg); // Read state of the command register - val &= ~(1 << 4); // set PowerDown bit ( bit 4 ) to 0 - PCD_WriteRegister(CommandReg, val); // write new value to the command register - // wait until PowerDown bit is cleared (this indicates end of wake up procedure) - // const uint32_t timeout = (uint32_t) millis() + 500; // create timer for timeout (just in case) - vTaskDelay(500 / portTICK_PERIOD_MS); - // while (millis() <= timeout) - { // set timeout to 500 ms - val = PCD_ReadRegister(CommandReg); // Read state of the command register - // if (!(val &(1 << 4))) { // if powerdown bit is 0 - // break; // wake up procedure is finished - // } - } -} - -///////////////////////////////////////////////////////////////////////////////////// -// Functions for communicating with PICCs -///////////////////////////////////////////////////////////////////////////////////// - -/** - * Executes the Transceive command. - * CRC validation can only be done if backData and backLen are specified. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -uint8_t PCD_TransceiveData( - uint8_t *sendData, ///< Pointer to the data to transfer to the FIFO. - uint8_t sendLen, ///< Number of uint8_ts to transfer to the FIFO. - uint8_t *backData, ///< nullptr or pointer to buffer if data should be read back after executing the command. - uint8_t *backLen, ///< In: Max number of bytes to write to *backData. Out: The number of bytes returned. - uint8_t *validBits, ///< In/Out: The number of valid bits in the last uint8_t. 0 for 8 valid bits. Default nullptr. - uint8_t rxAlign, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool checkCRC ///< In: True => The last two uint8_ts of the response is assumed to be a CRC_A that must be validated. -) -{ - uint8_t waitIRq = 0x30; // RxIRq and IdleIRq - return PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, sendData, sendLen, backData, backLen, validBits, rxAlign, checkCRC); -} - -/** - * Transfers data to the MFRC522 FIFO, executes a command, waits for completion and transfers data back from the FIFO. - * CRC validation can only be done if backData and backLen are specified. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -uint8_t PCD_CommunicateWithPICC( - uint8_t command, ///< The command to execute. One of the PCD_Command enums. - uint8_t waitIRq, ///< The bits in the ComIrqReg register that signals successful completion of the command. - uint8_t *sendData, ///< Pointer to the data to transfer to the FIFO. - uint8_t sendLen, ///< Number of bytes to transfer to the FIFO. - uint8_t *backData, ///< nullptr or pointer to buffer if data should be read back after executing the command. - uint8_t *backLen, ///< In: Max number of bytess to write to *backData. Out: The number of uint8_ts returned. - uint8_t *validBits, ///< In/Out: The number of valid bits in the last byte. 0 for 8 valid bits. - uint8_t rxAlign, ///< In: Defines the bit position in backData[0] for the first bit received. Default 0. - bool checkCRC ///< In: True => The last two bytes of the response is assumed to be a CRC_A that must be validated. -) -{ - // bool bTimeOutFlag = false; - // uint8_t n = 0; - - // Prepare values for BitFramingReg - uint8_t txLastBits = validBits ? *validBits : 0; - uint8_t bitFraming = (rxAlign << 4) + txLastBits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] - - PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. - PCD_WriteRegister(ComIrqReg, 0x7F); // Clear all seven interrupt request bits - PCD_WriteRegister(FIFOLevelReg, 0x80); // FlushBuffer = 1, FIFO initialization - PCD_WriteRegisterMany(FIFODataReg, sendLen, sendData); // Write sendData to the FIFO - vTaskDelay(10 / portTICK_PERIOD_MS); - PCD_WriteRegister(BitFramingReg, bitFraming); // Bit adjustments - PCD_WriteRegister(CommandReg, command); // Execute the command - if (command == PCD_Transceive) - { - PCD_SetRegisterBitMask(BitFramingReg, 0x80); // StartSend=1, transmission of data starts - } - - // Wait for the command to complete. - // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer automatically starts when the PCD stops transmitting. - // Each iteration of the do-while-loop takes 17.86μs. - // TODO check/modify for other architectures than Arduino Uno 16bit - - volatile unsigned long i = 1; - for (i = 20000; i > 0; i--) - { - uint8_t n = PCD_ReadRegister(ComIrqReg); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq HiAlertIRq LoAlertIRq ErrIRq TimerIRq - if ((n & 0xFF) & waitIRq) - { // One of the interrupts that signal success has been set. - // printf("\n\nPCD_CommunicateWithPICC: --INTERRUPT- command %d, waitIRq 0x%x, n 0x%x, txLastBits 0x%x, rxAlign 0x%x\n",command,waitIRq,n,txLastBits,rxAlign); - break; - } - if ((n & 0xFF) & 0x01) - { // Timer interrupt - nothing received in 25ms - // printf("\n\nPCD_CommunicateWithPICC: --STATUS_TIMEOUT- command %d, waitIRq 0x%x, n 0x%x, txLastBits 0x%x, rxAlign 0x%x\n",command,waitIRq,n,txLastBits,rxAlign); - return STATUS_TIMEOUT; - } - } - // printf("----------Loop break-------------------\n"); - // 35.7ms and nothing happend. Communication with the MFRC522 might be down. - if (i == 0) - { - return STATUS_TIMEOUT; - } - - // Stop now if any errors except collisions were detected. - uint8_t errorRegValue = PCD_ReadRegister(ErrorReg); // ErrorReg[7..0] bits are: WrErr TempErr reserved BufferOvfl CollErr CRCErr ParityErr ProtocolErr - if (errorRegValue & 0x13) - { // BufferOvfl ParityErr ProtocolErr - return STATUS_ERROR; - } - - uint8_t _validBits = 0; - - // If the caller wants data back, get it from the MFRC522. - if (backData && backLen) - { - uint8_t n = PCD_ReadRegister(FIFOLevelReg); // Number of bytes in the FIFO - // printf("Number of bytes in the FIFO n %d \n\n",n); - if (n > *backLen) - { - - return STATUS_NO_ROOM; - } - *backLen = n; // Number of bytes returned - PCD_ReadRegisterMany(FIFODataReg, n, backData, rxAlign); // Get received data from FIFO - _validBits = PCD_ReadRegister(ControlReg) & 0x07; // RxLastBits[2:0] indicates the number of valid bits in the last received uint8_t. If this value is 000b, the whole uint8_t is valid. - if (validBits) - { - *validBits = _validBits; - } - } - - // Tell about collisions - if (errorRegValue & 0x08) - { // CollErr - return STATUS_COLLISION; - } - - // Perform CRC_A validation if requested. - /*if (backData && backLen && checkCRC) { - // In this case a MIFARE Classic NAK is not OK. - if (*backLen == 1 && _validBits == 4) { - return STATUS_MIFARE_NACK; - } - // We need at least the CRC_A value and all 8 bits of the last uint8_t must be received. - if (*backLen < 2 || _validBits != 0) { - return STATUS_CRC_WRONG; - } - // Verify CRC_A - do our own calculation and store the control in controlBuffer. - uint8_t controlBuffer[2]; - uint8_t status = PCD_CalculateCRC(&backData[0], *backLen - 2, &controlBuffer[0]); - if (status != STATUS_OK) { - return status; - } - if ((backData[*backLen - 2] != controlBuffer[0]) || (backData[*backLen - 1] != controlBuffer[1])) { - return STATUS_CRC_WRONG; - } - }*/ - return STATUS_OK; -} // End PCD_CommunicateWithPICC() - -/** - * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame. - * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -uint8_t PICC_RequestA(uint8_t *bufferATQA, uint8_t *bufferSize) -{ - return PICC_REQA_or_WUPA(PICC_CMD_REQA, bufferATQA, bufferSize); -} - -/** - * Transmits a Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. - * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -uint8_t PICC_WakeupA(byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in - byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes returned if STATUS_OK. -) -{ - return PICC_REQA_or_WUPA(PICC_CMD_WUPA, bufferATQA, bufferSize); -} // End PICC_WakeupA() - -/** - * Transmits REQA or WUPA commands. - * Beware: When two PICCs are in the field at the same time I often get STATUS_TIMEOUT - probably due do bad antenna design. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -uint8_t PICC_REQA_or_WUPA(uint8_t command, uint8_t *bufferATQA, uint8_t *bufferSize) -{ - uint8_t validBits; - uint8_t status; - if (bufferATQA == NULL || *bufferSize < 2) - { - return STATUS_NO_ROOM; - } - PCD_ClearRegisterBitMask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. - validBits = 7; // For REQA and WUPA we need the short frame format - transmit only 7 bits of the last (and only) uint8_t. TxLastBits = [2..0] - status = PCD_TransceiveData(&command, 1, bufferATQA, bufferSize, &validBits, 0, false); - if (status != STATUS_OK) - { - return status; - } - if (*bufferSize != 2 || validBits != 0) - { // ATQA must be exactly 16 bits. - return STATUS_ERROR; - } - return STATUS_OK; -} - -/** - * Transmits SELECT/ANTICOLLISION commands to select a single PICC. - * Before calling this function the PICCs must be placed in the READY(*) state by calling PICC_RequestA() or PICC_WakeupA(). - * On success: - * - The chosen PICC is in state ACTIVE(*) and all other PICCs have returned to state IDLE/HALT. (Figure 7 of the ISO/IEC 14443-3 draft.) - * - The UID size and value of the chosen PICC is returned in *uid along with the SAK. - * - * A PICC UID consists of 4, 7 or 10 bytes. - * Only 4 bytes can be specified in a SELECT command, so for the longer UIDs two or three iterations are used: - * UID size Number of UID bytes Cascade levels Example of PICC - * ======== =================== ============== =============== - * single 4 1 MIFARE Classic - * double 7 2 MIFARE Ultralight - * triple 10 3 Not currently in use? - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode PICC_Select(Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used to supply a known UID. - byte validBits ///< The number of known UID bits supplied in *uid. Normally 0. If set you must also supply uid->size. -) -{ - bool uidComplete; - bool selectDone; - bool useCascadeTag; - byte cascadeLevel = 1; - StatusCode result; - byte count; - byte index; - byte uidIndex; // The first index in uid->uidByte[] that is used in the current Cascade Level. - int8_t currentLevelKnownBits; // The number of known UID bits in the current Cascade Level. - byte buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A - byte bufferUsed; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO. - byte rxAlign; // Used in BitFramingReg. Defines the bit position for the first bit received. - byte txLastBits; // Used in BitFramingReg. The number of valid bits in the last transmitted byte. - byte *responseBuffer = 0; - byte responseLength; - - // Description of buffer structure: - // Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 - // Byte 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete bytes, Low nibble: Extra bits. - // Byte 2: UID-data or CT See explanation below. CT means Cascade Tag. - // Byte 3: UID-data - // Byte 4: UID-data - // Byte 5: UID-data - // Byte 6: BCC Block Check Character - XOR of bytes 2-5 - // Byte 7: CRC_A - // Byte 8: CRC_A - // The BCC and CRC_A are only transmitted if we know all the UID bits of the current Cascade Level. - // - // Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) - // UID size Cascade level Byte2 Byte3 Byte4 Byte5 - // ======== ============= ===== ===== ===== ===== - // 4 bytes 1 uid0 uid1 uid2 uid3 - // 7 bytes 1 CT uid0 uid1 uid2 - // 2 uid3 uid4 uid5 uid6 - // 10 bytes 1 CT uid0 uid1 uid2 - // 2 CT uid3 uid4 uid5 - // 3 uid6 uid7 uid8 uid9 - - // Sanity checks - if (validBits > 80) - { - return STATUS_INVALID; - } - - // Prepare MFRC522 - PCD_ClearRegisterBitMask(CollReg, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. - - // Repeat Cascade Level loop until we have a complete UID. - uidComplete = false; - while (!uidComplete) - { - // Set the Cascade Level in the SEL byte, find out if we need to use the Cascade Tag in byte 2. - switch (cascadeLevel) - { - case 1: - buffer[0] = PICC_CMD_SEL_CL1; - uidIndex = 0; - useCascadeTag = validBits && uid->size > 4; // When we know that the UID has more than 4 bytes - break; - - case 2: - buffer[0] = PICC_CMD_SEL_CL2; - uidIndex = 3; - useCascadeTag = validBits && uid->size > 7; // When we know that the UID has more than 7 bytes - break; - - case 3: - buffer[0] = PICC_CMD_SEL_CL3; - uidIndex = 6; - useCascadeTag = false; // Never used in CL3. - break; - - default: - return STATUS_INTERNAL_ERROR; - break; - } - - // How many UID bits are known in this Cascade Level? - currentLevelKnownBits = validBits - (8 * uidIndex); - if (currentLevelKnownBits < 0) - { - currentLevelKnownBits = 0; - } - // Copy the known bits from uid->uidByte[] to buffer[] - index = 2; // destination index in buffer[] - if (useCascadeTag) - { - buffer[index++] = PICC_CMD_CT; - } - byte bytesToCopy = currentLevelKnownBits / 8 + (currentLevelKnownBits % 8 ? 1 : 0); // The number of bytes needed to represent the known bits for this level. - if (bytesToCopy) - { - byte maxBytes = useCascadeTag ? 3 : 4; // Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag - if (bytesToCopy > maxBytes) - { - bytesToCopy = maxBytes; - } - for (count = 0; count < bytesToCopy; count++) - { - buffer[index++] = uid->uidByte[uidIndex + count]; - } - } - // Now that the data has been copied we need to include the 8 bits in CT in currentLevelKnownBits - if (useCascadeTag) - { - currentLevelKnownBits += 8; - } - - // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. - selectDone = false; - while (!selectDone) - { - // Find out how many bits and bytes to send and receive. - if (currentLevelKnownBits >= 32) - { // All UID bits in this Cascade Level are known. This is a SELECT. - // Serial.print(F("SELECT: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); - buffer[1] = 0x70; // NVB - Number of Valid Bits: Seven whole bytes - // Calculate BCC - Block Check Character - buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; - // Calculate CRC_A - result = PCD_CalculateCRC(buffer, 7, &buffer[7]); - if (result != STATUS_OK) - { - return result; - } - txLastBits = 0; // 0 => All 8 bits are valid. - bufferUsed = 9; - // Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx) - responseBuffer = &buffer[6]; - responseLength = 3; - } - else - { // This is an ANTICOLLISION. - // Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); Serial.println(currentLevelKnownBits, DEC); - txLastBits = currentLevelKnownBits % 8; - count = currentLevelKnownBits / 8; // Number of whole bytes in the UID part. - index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs - buffer[1] = (index << 4) + txLastBits; // NVB - Number of Valid Bits - bufferUsed = index + (txLastBits ? 1 : 0); - // Store response in the unused part of buffer - responseBuffer = &buffer[index]; - responseLength = sizeof(buffer) - index; - } - - // Set bit adjustments - rxAlign = txLastBits; // Having a separate variable is overkill. But it makes the next line easier to read. - PCD_WriteRegister(BitFramingReg, (rxAlign << 4) + txLastBits); // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] - - // Transmit the buffer and receive the response. - result = PCD_TransceiveData(buffer, bufferUsed, responseBuffer, &responseLength, &txLastBits, rxAlign, false); - if (result == STATUS_COLLISION) - { // More than one PICC in the field => collision. - byte valueOfCollReg = PCD_ReadRegister(CollReg); // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] - if (valueOfCollReg & 0x20) - { // CollPosNotValid - return STATUS_COLLISION; // Without a valid collision position we cannot continue - } - byte collisionPos = valueOfCollReg & 0x1F; // Values 0-31, 0 means bit 32. - if (collisionPos == 0) - { - collisionPos = 32; - } - if (collisionPos <= currentLevelKnownBits) - { // No progress - should not happen - return STATUS_INTERNAL_ERROR; - } - // Choose the PICC with the bit set. - currentLevelKnownBits = collisionPos; - count = (currentLevelKnownBits - 1) % 8; // The bit to modify - index = 1 + (currentLevelKnownBits / 8) + (count ? 1 : 0); // First byte is index 0. - buffer[index] |= (1 << count); - } - else if (result != STATUS_OK) - { - return result; - } - else - { // STATUS_OK - if (currentLevelKnownBits >= 32) - { // This was a SELECT. - selectDone = true; // No more anticollision - // We continue below outside the while. - } - else - { // This was an ANTICOLLISION. - // We now have all 32 bits of the UID in this Cascade Level - currentLevelKnownBits = 32; - // Run loop again to do the SELECT. - } - } - } // End of while (!selectDone) - - // We do not check the CBB - it was constructed by us above. - - // Copy the found UID bytes from buffer[] to uid->uidByte[] - index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] - bytesToCopy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; - for (count = 0; count < bytesToCopy; count++) - { - uid->uidByte[uidIndex + count] = buffer[index++]; - } - - // Check response SAK (Select Acknowledge) - if (responseLength != 3 || txLastBits != 0) - { // SAK must be exactly 24 bits (1 byte + CRC_A). - return STATUS_ERROR; - } - // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed anymore. - result = PCD_CalculateCRC(responseBuffer, 1, &buffer[2]); - if (result != STATUS_OK) - { - return result; - } - if ((buffer[2] != responseBuffer[1]) || (buffer[3] != responseBuffer[2])) - { - return STATUS_CRC_WRONG; - } - if (responseBuffer[0] & 0x04) - { // Cascade bit set - UID not complete yes - cascadeLevel++; - } - else - { - uidComplete = true; - uid->sak = responseBuffer[0]; - } - } // End of while (!uidComplete) - - // Set correct uid->size - uid->size = 3 * cascadeLevel + 1; - - // IF SAK bit 6 = 1 then it is ISO/IEC 14443-4 (T=CL) - // A Request ATS command should be sent - // We also check SAK bit 3 is cero, as it stands for UID complete (1 would tell us it is incomplete) - - return STATUS_OK; -} // End PICC_Select() - -/** - * Instructs a PICC in state ACTIVE(*) to go to state HALT. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode PICC_HaltA() -{ - StatusCode result; - byte buffer[4]; - - // Build command buffer - buffer[0] = PICC_CMD_HLTA; - buffer[1] = 0; - // Calculate CRC_A - result = PCD_CalculateCRC(buffer, 2, &buffer[2]); - if (result != STATUS_OK) - { - return result; - } - - // Send the command. - // The standard says: - // If the PICC responds with any modulation during a period of 1 ms after the end of the frame containing the - // HLTA command, this response shall be interpreted as 'not acknowledge'. - // We interpret that this way: Only STATUS_TIMEOUT is a success. - result = PCD_TransceiveData(buffer, sizeof(buffer), null, 0, null, 0, false); - if (result == STATUS_TIMEOUT) - { - return STATUS_OK; - } - if (result == STATUS_OK) - { // That is ironically NOT ok in this case ;-) - return STATUS_ERROR; - } - return result; -} // End PICC_HaltA() - -///////////////////////////////////////////////////////////////////////////////////// -// Functions for communicating with MIFARE PICCs -///////////////////////////////////////////////////////////////////////////////////// - -/** - * Executes the MFRC522 MFAuthent command. - * This command manages MIFARE authentication to enable a secure communication to any MIFARE Mini, MIFARE 1K and MIFARE 4K card. - * The authentication is described in the MFRC522 datasheet section 10.3.1.9 and http://www.nxp.com/documents/data_sheet/MF1S503x.pdf section 10.1. - * For use with MIFARE Classic PICCs. - * The PICC must be selected - ie in state ACTIVE(*) - before calling this function. - * Remember to call PCD_StopCrypto1() after communicating with the authenticated PICC - otherwise no new communications can start. - * - * All keys are set to FFFFFFFFFFFFh at chip delivery. - * - * @return STATUS_OK on success, STATUS_??? otherwise. Probably STATUS_TIMEOUT if you supply the wrong key. - */ -StatusCode PCD_Authenticate(byte command, ///< PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B - byte blockAddr, ///< The block number. See numbering in the comments in the .h file. - MIFARE_Key *key, ///< Pointer to the Crypteo1 key to use (6 bytes) - Uid *uid ///< Pointer to Uid struct. The first 4 bytes of the UID is used. -) -{ - StatusCode retVal; - byte waitIRq = 0x10; // IdleIRq - - // Build command buffer - byte sendData[12]; - sendData[0] = command; - sendData[1] = blockAddr; - for (byte i = 0; i < MF_KEY_SIZE; i++) - { // 6 key bytes - sendData[2 + i] = key->keyByte[i]; - } - // Use the last uid bytes as specified in http://cache.nxp.com/documents/application_note/AN10927.pdf - // section 3.2.5 "MIFARE Classic Authentication". - // The only missed case is the MF1Sxxxx shortcut activation, - // but it requires cascade tag (CT) byte, that is not part of uid. - for (byte i = 0; i < 4; i++) - { // The last 4 bytes of the UID - sendData[8 + i] = uid->uidByte[i + uid->size - 4]; - } - - // Start the authentication. - retVal = PCD_CommunicateWithPICC(PCD_MFAuthent, waitIRq, &sendData[0], sizeof(sendData), null, null, null, 0, false); - return retVal; -} // End PCD_Authenticate() - -/** - * Used to exit the PCD from its authenticated state. - * Remember to call this function after communicating with an authenticated PICC - otherwise no new communications can start. - */ -void PCD_StopCrypto1() -{ - // Clear MFCrypto1On bit - PCD_ClearRegisterBitMask(Status2Reg, 0x08); // Status2Reg[7..0] bits are: TempSensClear I2CForceHS reserved reserved MFCrypto1On ModemState[2:0] -} // End PCD_StopCrypto1() - -/** - * Reads 16 bytes (+ 2 bytes CRC_A) from the active PICC. - * - * For MIFARE Classic the sector containing the block must be authenticated before calling this function. - * - * For MIFARE Ultralight only addresses 00h to 0Fh are decoded. - * The MF0ICU1 returns a NAK for higher addresses. - * The MF0ICU1 responds to the READ command by sending 16 bytes starting from the page address defined by the command argument. - * For example; if blockAddr is 03h then pages 03h, 04h, 05h, 06h are returned. - * A roll-back is implemented: If blockAddr is 0Eh, then the contents of pages 0Eh, 0Fh, 00h and 01h are returned. - * - * The buffer must be at least 18 bytes because a CRC_A is also returned. - * Checks the CRC_A before returning STATUS_OK. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode MIFARE_Read(byte blockAddr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The first page to return data from. - byte *buffer, ///< The buffer to store the data in - byte *bufferSize ///< Buffer size, at least 18 bytes. Also number of bytes returned if STATUS_OK. -) -{ - StatusCode result; - - // Sanity check - if (buffer == null || *bufferSize < 18) - { - return STATUS_NO_ROOM; - } - - // Build command buffer - buffer[0] = PICC_CMD_MF_READ; - buffer[1] = blockAddr; - // Calculate CRC_A - result = PCD_CalculateCRC(buffer, 2, &buffer[2]); - if (result != STATUS_OK) - { - return result; - } - - result = PCD_TransceiveData(buffer, 4, buffer, bufferSize, null, 0, true); - // Transmit the buffer and receive the response, validate CRC_A. - return result; -} // End MIFARE_Read() - -/** - * Writes 16 bytes to the active PICC. - * - * For MIFARE Classic the sector containing the block must be authenticated before calling this function. - * - * For MIFARE Ultralight the operation is called "COMPATIBILITY WRITE". - * Even though 16 bytes are transferred to the Ultralight PICC, only the least significant 4 bytes (bytes 0 to 3) - * are written to the specified address. It is recommended to set the remaining bytes 04h to 0Fh to all logic 0. - * * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode MIFARE_Write(byte blockAddr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE Ultralight: The page (2-15) to write to. - byte *buffer, ///< The 16 bytes to write to the PICC - byte bufferSize ///< Buffer size, must be at least 16 bytes. Exactly 16 bytes are written. -) -{ - StatusCode result; - - // Sanity check - if (buffer == null || bufferSize < 16) - { - return STATUS_INVALID; - } - - // Mifare Classic protocol requires two communications to perform a write. - // Step 1: Tell the PICC we want to write to block blockAddr. - byte cmdBuffer[2]; - cmdBuffer[0] = PICC_CMD_MF_WRITE; - cmdBuffer[1] = blockAddr; - result = PCD_MIFARE_Transceive(cmdBuffer, 2, false); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) - { - return result; - } - - // Step 2: Transfer the data - result = PCD_MIFARE_Transceive(buffer, bufferSize, false); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) - { - return result; - } - - return STATUS_OK; -} // End MIFARE_Write() - -/** - * Writes a 4 byte page to the active MIFARE Ultralight PICC. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode MIFARE_Ultralight_Write(byte page, ///< The page (2-15) to write to. - byte *buffer, ///< The 4 bytes to write to the PICC - byte bufferSize ///< Buffer size, must be at least 4 bytes. Exactly 4 bytes are written. -) -{ - StatusCode result; - - // Sanity check - if (buffer == null || bufferSize < 4) - { - return STATUS_INVALID; - } - - // Build commmand buffer - byte cmdBuffer[6]; - cmdBuffer[0] = PICC_CMD_UL_WRITE; - cmdBuffer[1] = page; - memcpy(&cmdBuffer[2], buffer, 4); - - // Perform the write - result = PCD_MIFARE_Transceive(cmdBuffer, 6, false); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) - { - return result; - } - return STATUS_OK; -} // End MIFARE_Ultralight_Write() - -/** - * MIFARE Decrement subtracts the delta from the value of the addressed block, and stores the result in a volatile memory. - * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. - * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. - * Use MIFARE_Transfer() to store the result in a block. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode MIFARE_Decrement(byte blockAddr, ///< The block (0-0xff) number. - int32_t delta ///< This number is subtracted from the value of block blockAddr. -) -{ - return MIFARE_TwoStepHelper(PICC_CMD_MF_DECREMENT, blockAddr, delta); -} // End MIFARE_Decrement() - -/** - * MIFARE Increment adds the delta to the value of the addressed block, and stores the result in a volatile memory. - * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. - * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. - * Use MIFARE_Transfer() to store the result in a block. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode MIFARE_Increment(byte blockAddr, ///< The block (0-0xff) number. - int32_t delta ///< This number is added to the value of block blockAddr. -) -{ - return MIFARE_TwoStepHelper(PICC_CMD_MF_INCREMENT, blockAddr, delta); -} // End MIFARE_Increment() - -/** - * MIFARE Restore copies the value of the addressed block into a volatile memory. - * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. - * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. - * Use MIFARE_Transfer() to store the result in a block. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode MIFARE_Restore(byte blockAddr ///< The block (0-0xff) number. -) -{ - // The datasheet describes Restore as a two step operation, but does not explain what data to transfer in step 2. - // Doing only a single step does not work, so I chose to transfer 0L in step two. - return MIFARE_TwoStepHelper(PICC_CMD_MF_RESTORE, blockAddr, 0); -} // End MIFARE_Restore() - -/** - * Helper function for the two-step MIFARE Classic protocol operations Decrement, Increment and Restore. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode MIFARE_TwoStepHelper(byte command, ///< The command to use - byte blockAddr, ///< The block (0-0xff) number. - int32_t data ///< The data to transfer in step 2 -) -{ - StatusCode result; - byte cmdBuffer[2]; // We only need room for 2 bytes. - - // Step 1: Tell the PICC the command and block address - cmdBuffer[0] = command; - cmdBuffer[1] = blockAddr; - result = PCD_MIFARE_Transceive(cmdBuffer, 2, false); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) - { - return result; - } - - // Step 2: Transfer the data - result = PCD_MIFARE_Transceive((byte *)&data, 4, true); // Adds CRC_A and accept timeout as success. - if (result != STATUS_OK) - { - return result; - } - - return STATUS_OK; -} // End MIFARE_TwoStepHelper() - -/** - * MIFARE Transfer writes the value stored in the volatile memory into one MIFARE Classic block. - * For MIFARE Classic only. The sector containing the block must be authenticated before calling this function. - * Only for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode MIFARE_Transfer(byte blockAddr ///< The block (0-0xff) number. -) -{ - StatusCode result; - byte cmdBuffer[2]; // We only need room for 2 bytes. - - // Tell the PICC we want to transfer the result into block blockAddr. - cmdBuffer[0] = PICC_CMD_MF_TRANSFER; - cmdBuffer[1] = blockAddr; - result = PCD_MIFARE_Transceive(cmdBuffer, 2, false); // Adds CRC_A and checks that the response is MF_ACK. - if (result != STATUS_OK) - { - return result; - } - return STATUS_OK; -} // End MIFARE_Transfer() - -/** - * Helper routine to read the current value from a Value Block. - * - * Only for MIFARE Classic and only for blocks in "value block" mode, that - * is: with access bits [C1 C2 C3] = [110] or [001]. The sector containing - * the block must be authenticated before calling this function. - * - * @param[in] blockAddr The block (0x00-0xff) number. - * @param[out] value Current value of the Value Block. - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode MIFARE_GetValue(byte blockAddr, int32_t *value) -{ - StatusCode status; - byte buffer[18]; - byte size = sizeof(buffer); - - // Read the block - status = MIFARE_Read(blockAddr, buffer, &size); - if (status == STATUS_OK) - { - // Extract the value - *value = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | (buffer[0]); - } - return status; -} // End MIFARE_GetValue() - -/** - * Helper routine to write a specific value into a Value Block. - * - * Only for MIFARE Classic and only for blocks in "value block" mode, that - * is: with access bits [C1 C2 C3] = [110] or [001]. The sector containing - * the block must be authenticated before calling this function. - * - * @param[in] blockAddr The block (0x00-0xff) number. - * @param[in] value New value of the Value Block. - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode MIFARE_SetValue(byte blockAddr, int32_t value) -{ - byte buffer[18]; - - // Translate the int32_t into 4 bytes; repeated 2x in value block - buffer[0] = buffer[8] = (value & 0xFF); - buffer[1] = buffer[9] = (value & 0xFF00) >> 8; - buffer[2] = buffer[10] = (value & 0xFF0000) >> 16; - buffer[3] = buffer[11] = (value & 0xFF000000) >> 24; - // Inverse 4 bytes also found in value block - buffer[4] = ~buffer[0]; - buffer[5] = ~buffer[1]; - buffer[6] = ~buffer[2]; - buffer[7] = ~buffer[3]; - // Address 2x with inverse address 2x - buffer[12] = buffer[14] = blockAddr; - buffer[13] = buffer[15] = ~blockAddr; - - // Write the whole data block - return MIFARE_Write(blockAddr, buffer, 16); -} // End MIFARE_SetValue() - -/** - * Authenticate with a NTAG216. - * - * Only for NTAG216. First implemented by Gargantuanman. - * - * @param[in] passWord password. - * @param[in] pACK result success???. - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode PCD_NTAG216_AUTH(byte *passWord, byte pACK[]) // Authenticate with 32bit password -{ - // TODO: Fix cmdBuffer length and rxlength. They really should match. - // (Better still, rxlength should not even be necessary.) - - StatusCode result; - byte cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. - - cmdBuffer[0] = 0x1B; // Comando de autentificacion - - for (byte i = 0; i < 4; i++) - cmdBuffer[i + 1] = passWord[i]; - - result = PCD_CalculateCRC(cmdBuffer, 5, &cmdBuffer[5]); - - if (result != STATUS_OK) - { - return result; - } - - // Transceive the data, store the reply in cmdBuffer[] - byte waitIRq = 0x30; // RxIRq and IdleIRq - // byte cmdBufferSize = sizeof(cmdBuffer); - byte validBits = 0; - byte rxlength = 5; - result = PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, 7, cmdBuffer, &rxlength, &validBits, 0, false); - - pACK[0] = cmdBuffer[0]; - pACK[1] = cmdBuffer[1]; - - if (result != STATUS_OK) - { - return result; - } - - return STATUS_OK; -} // End PCD_NTAG216_AUTH() - -///////////////////////////////////////////////////////////////////////////////////// -// Support functions -///////////////////////////////////////////////////////////////////////////////////// - -/** - * Wrapper for MIFARE protocol communication. - * Adds CRC_A, executes the Transceive command and checks that the response is MF_ACK or a timeout. - * - * @return STATUS_OK on success, STATUS_??? otherwise. - */ -StatusCode PCD_MIFARE_Transceive(byte *sendData, ///< Pointer to the data to transfer to the FIFO. Do NOT include the CRC_A. - byte sendLen, ///< Number of bytes in sendData. - bool acceptTimeout ///< True => A timeout is also success -) -{ - StatusCode result; - byte cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. - - // Sanity check - if (sendData == null || sendLen > 16) - { - return STATUS_INVALID; - } - - // Copy sendData[] to cmdBuffer[] and add CRC_A - memcpy(cmdBuffer, sendData, sendLen); - result = PCD_CalculateCRC(cmdBuffer, sendLen, &cmdBuffer[sendLen]); - if (result != STATUS_OK) - { - return result; - } - sendLen += 2; - - // Transceive the data, store the reply in cmdBuffer[] - byte waitIRq = 0x30; // RxIRq and IdleIRq - byte cmdBufferSize = sizeof(cmdBuffer); - byte validBits = 0; - result = PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, sendLen, cmdBuffer, &cmdBufferSize, &validBits, 0, false); - if (acceptTimeout && result == STATUS_TIMEOUT) - { - return STATUS_OK; - } - if (result != STATUS_OK) - { - return result; - } - // The PICC must reply with a 4 bit ACK - if (cmdBufferSize != 1 || validBits != 4) - { - return STATUS_ERROR; - } - if (cmdBuffer[0] != MF_ACK) - { - return STATUS_MIFARE_NACK; - } - return STATUS_OK; -} // End PCD_MIFARE_Transceive() - -/** - * Returns a char pointer to a status code name. - * - * @return const char * - */ -const char *GetStatusCodeName(StatusCode code ///< One of the StatusCode enums. -) -{ - switch (code) - { - case STATUS_OK: - return F("Success."); - case STATUS_ERROR: - return F("Error in communication."); - case STATUS_COLLISION: - return F("Collission detected."); - case STATUS_TIMEOUT: - return F("Timeout in communication."); - case STATUS_NO_ROOM: - return F("A buffer is not big enough."); - case STATUS_INTERNAL_ERROR: - return F("Internal error in the code. Should not happen."); - case STATUS_INVALID: - return F("Invalid argument."); - case STATUS_CRC_WRONG: - return F("The CRC_A does not match."); - case STATUS_MIFARE_NACK: - return F("A MIFARE PICC responded with NAK."); - default: - return F("Unknown error"); - } -} // End GetStatusCodeName() - -/** - * Translates the SAK (Select Acknowledge) to a PICC type. - * - * @return PICC_Type - */ -PICC_Type PICC_GetType(byte sak ///< The SAK byte returned from PICC_Select(). -) -{ - // http://www.nxp.com/documents/application_note/AN10833.pdf - // 3.2 Coding of Select Acknowledge (SAK) - // ignore 8-bit (iso14443 starts with LSBit = bit 1) - // fixes wrong type for manufacturer Infineon (http://nfc-tools.org/index.php?title=ISO14443A) - sak &= 0x7F; - switch (sak) - { - case 0x04: - return PICC_TYPE_NOT_COMPLETE; // UID not complete - case 0x09: - return PICC_TYPE_MIFARE_MINI; - case 0x08: - return PICC_TYPE_MIFARE_1K; - case 0x18: - return PICC_TYPE_MIFARE_4K; - case 0x00: - return PICC_TYPE_MIFARE_UL; - case 0x10: - case 0x11: - return PICC_TYPE_MIFARE_PLUS; - case 0x01: - return PICC_TYPE_TNP3XXX; - case 0x20: - return PICC_TYPE_ISO_14443_4; - case 0x40: - return PICC_TYPE_ISO_18092; - default: - return PICC_TYPE_UNKNOWN; - } -} // End PICC_GetType() - -/** - * Returns a char pointer to the PICC type name. - * - * @return const char * - */ -const char *PICC_GetTypeName(PICC_Type piccType ///< One of the PICC_Type enums. -) -{ - switch (piccType) - { - case PICC_TYPE_ISO_14443_4: - return F("PICC compliant with ISO/IEC 14443-4"); - case PICC_TYPE_ISO_18092: - return F("PICC compliant with ISO/IEC 18092 (NFC)"); - case PICC_TYPE_MIFARE_MINI: - return F("MIFARE Mini, 320 bytes"); - case PICC_TYPE_MIFARE_1K: - return F("MIFARE 1KB"); - case PICC_TYPE_MIFARE_4K: - return F("MIFARE 4KB"); - case PICC_TYPE_MIFARE_UL: - return F("MIFARE Ultralight or Ultralight C"); - case PICC_TYPE_MIFARE_PLUS: - return F("MIFARE Plus"); - case PICC_TYPE_MIFARE_DESFIRE: - return F("MIFARE DESFire"); - case PICC_TYPE_TNP3XXX: - return F("MIFARE TNP3XXX"); - case PICC_TYPE_NOT_COMPLETE: - return F("SAK indicates UID is not complete."); - case PICC_TYPE_UNKNOWN: - default: - return F("Unknown type"); - } -} // End PICC_GetTypeName() - -/** - * Dumps debug info about the connected PCD to Serial. - * Shows all known firmware versions - */ -void PCD_DumpVersionToSerial() -{ - // Get the MFRC522 firmware version - byte v = PCD_ReadRegister(VersionReg); - printf(F("Firmware Version: 0x")); - printf("%x", v); - // Lookup which version - switch (v) - { - case 0x88: - printf(F(" = (clone)\n")); - break; - case 0x90: - printf(F(" = v0.0\n")); - break; - case 0x91: - printf(F(" = v1.0\n")); - break; - case 0x92: - printf(F(" = v2.0\n")); - break; - case 0x12: - printf(F(" = counterfeit chip\n")); - break; - default: - printf(F(" = (unknown)\n")); - } - // When 0x00 or 0xFF is returned, communication probably failed - if ((v == 0x00) || (v == 0xFF)) - printf(F("WARNING: Communication failure, is the MFRC522 properly connected?\n")); -} // End PCD_DumpVersionToSerial() - -/** - * Dumps debug info about the selected PICC to Serial. - * On success the PICC is halted after dumping the data. - * For MIFARE Classic the factory default key of 0xFFFFFFFFFFFF is tried. - * - * @DEPRECATED Kept for bakward compatibility - */ -void PICC_DumpToSerial(Uid *uid ///< Pointer to Uid struct returned from a successful PICC_Select(). -) -{ - MIFARE_Key key; - - // Dump UID, SAK and Type - PICC_DumpDetailsToSerial(uid); - - // Dump contents - PICC_Type piccType = PICC_GetType(uid->sak); - switch (piccType) - { - case PICC_TYPE_MIFARE_MINI: - case PICC_TYPE_MIFARE_1K: - case PICC_TYPE_MIFARE_4K: - // All keys are set to FFFFFFFFFFFFh at chip delivery from the factory. - for (byte i = 0; i < 6; i++) - { - key.keyByte[i] = 0xFF; - } - PICC_DumpMifareClassicToSerial(uid, piccType, &key); - break; - - case PICC_TYPE_MIFARE_UL: - PICC_DumpMifareUltralightToSerial(); - break; - - case PICC_TYPE_ISO_14443_4: - case PICC_TYPE_MIFARE_DESFIRE: - case PICC_TYPE_ISO_18092: - case PICC_TYPE_MIFARE_PLUS: - case PICC_TYPE_TNP3XXX: - printf(F("Dumping memory contents not implemented for that PICC type.\n")); - break; - - case PICC_TYPE_UNKNOWN: - case PICC_TYPE_NOT_COMPLETE: - default: - break; // No memory dump here - } - - printf("\n"); - PICC_HaltA(); // Already done if it was a MIFARE Classic PICC. -} // End PICC_DumpToSerial() - -/** - * Dumps card info (UID,SAK,Type) about the selected PICC to Serial. - * - * @DEPRECATED kept for backward compatibility - */ -void PICC_DumpDetailsToSerial(Uid *uid ///< Pointer to Uid struct returned from a successful PICC_Select(). -) -{ - // UID - printf(F("Card UID:")); - for (byte i = 0; i < uid->size; i++) - { - if (uid->uidByte[i] < 0x10) - printf(F(" 0")); - else - printf(F(" ")); - printf(" %x", uid->uidByte[i]); - } - printf("\n"); - - // SAK - printf(F("Card SAK: ")); - if (uid->sak < 0x10) - printf(F("0")); - printf("%x\n", uid->sak); - - // (suggested) PICC type - PICC_Type piccType = PICC_GetType(uid->sak); - printf(F("PICC type: ")); - printf("%s\n", PICC_GetTypeName(piccType)); -} // End PICC_DumpDetailsToSerial() - -/** - * Dumps memory contents of a MIFARE Classic PICC. - * On success the PICC is halted after dumping the data. - */ -void PICC_DumpMifareClassicToSerial(Uid *uid, ///< Pointer to Uid struct returned from a successful PICC_Select(). - PICC_Type piccType, ///< One of the PICC_Type enums. - MIFARE_Key *key ///< Key A used for all sectors. -) -{ - byte no_of_sectors = 0; - switch (piccType) - { - case PICC_TYPE_MIFARE_MINI: - // Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes. - no_of_sectors = 5; - break; - - case PICC_TYPE_MIFARE_1K: - // Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes. - no_of_sectors = 16; - break; - - case PICC_TYPE_MIFARE_4K: - // Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes. - no_of_sectors = 40; - break; - - default: // Should not happen. Ignore. - break; - } - - // Dump sectors, highest address first. - if (no_of_sectors) - { - // printf(F("Sector Block 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 AccessBits\n")); - // for (int8_t i = no_of_sectors - 1; i >= 0; i--) - for (int8_t i = 2; i >= 2; i--) - { - PICC_DumpMifareClassicSectorToSerial(uid, key, i); - } - } - PICC_HaltA(); // Halt the PICC before stopping the encrypted session. - PCD_StopCrypto1(); -} // End PICC_DumpMifareClassicToSerial() - -/** - * Dumps memory contents of a sector of a MIFARE Classic PICC. - * Uses PCD_Authenticate(), MIFARE_Read() and PCD_StopCrypto1. - * Always uses PICC_CMD_MF_AUTH_KEY_A because only Key A can always read the sector trailer access bits. - */ -void PICC_DumpMifareClassicSectorToSerial(Uid *uid, ///< Pointer to Uid struct returned from a successful PICC_Select(). - MIFARE_Key *key, ///< Key A for the sector. - byte sector ///< The sector to dump, 0..39. -) -{ - StatusCode status; - byte firstBlock; // Address of lowest address to dump actually last block dumped) - byte no_of_blocks; // Number of blocks in sector - bool isSectorTrailer; // Set to true while handling the "last" (ie highest address) in the sector. - - // The access bits are stored in a peculiar fashion. - // There are four groups: - // g[3] Access bits for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) - // g[2] Access bits for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) - // g[1] Access bits for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) - // g[0] Access bits for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) - // Each group has access bits [C1 C2 C3]. In this code C1 is MSB and C3 is LSB. - // The four CX bits are stored together in a nible cx and an inverted nible cx_. - byte c1, c2, c3; // Nibbles - byte c1_, c2_, c3_; // Inverted nibbles - bool invertedError; // True if one of the inverted nibbles did not match - byte g[4]; // Access bits for each of the four groups. - byte group; // 0-3 - active group for access bits - bool firstInGroup; // True for the first block dumped in the group - - // Determine position and size of sector. - if (sector < 32) - { // Sectors 0..31 has 4 blocks each - no_of_blocks = 4; - firstBlock = sector * no_of_blocks; - } - else if (sector < 40) - { // Sectors 32-39 has 16 blocks each - no_of_blocks = 16; - firstBlock = 128 + (sector - 32) * no_of_blocks; - } - else - { // Illegal input, no MIFARE Classic PICC has more than 40 sectors. - return; - } - - // Dump blocks, highest address first. - byte byteIndex = 0; - byte byteCount; - byte buffer[18]; - byte blockAddr; - isSectorTrailer = true; - invertedError = false; // Avoid "unused variable" warning. - for (int8_t blockOffset = no_of_blocks - 1; blockOffset >= 0; blockOffset--) - { - blockAddr = firstBlock + blockOffset; - - /* - // Sector number - only on first line - if (isSectorTrailer) - { - if (sector < 10) - printf(F(" ")); // Pad with spaces - else - printf(F(" ")); // Pad with spaces - printf("%d", sector); - printf(F(" ")); - } - else - { - printf(F(" ")); - } - // Block number - if (blockAddr < 10) - printf(F(" ")); // Pad with spaces - else - { - if (blockAddr < 100) - printf(F(" ")); // Pad with spaces - else - printf(F(" ")); // Pad with spaces - } - printf("%d", blockAddr); - printf(F(" ")); - - */ - // Establish encrypted communications before reading the first block - if (isSectorTrailer) - { - status = PCD_Authenticate(PICC_CMD_MF_AUTH_KEY_A, firstBlock, key, uid); - if (status != STATUS_OK) - { - printf(F("PCD_Authenticate() failed: ")); - printf("%s\n", GetStatusCodeName(status)); - return; - } - } - // Read block - byteCount = sizeof(buffer); - status = MIFARE_Read(blockAddr, buffer, &byteCount); - if (status != STATUS_OK) - { - printf(F("MIFARE_Read() failed: ")); - printf("%s\n", GetStatusCodeName(status)); - continue; - } - // Dump data - for (byte index = 0; index < 16; index++) - { - /* - if (buffer[index] < 0x10) - printf(F(" 0")); - else - printf(F(" ")); - printf("%x", buffer[index]); - if ((index % 4) == 3) - { - printf(F(" ")); - }*/ - - bufferSector[byteIndex++] = buffer[index]; - } - // Parse sector trailer data - if (isSectorTrailer) - { - c1 = buffer[7] >> 4; - c2 = buffer[8] & 0xF; - c3 = buffer[8] >> 4; - c1_ = buffer[6] & 0xF; - c2_ = buffer[6] >> 4; - c3_ = buffer[7] & 0xF; - invertedError = (c1 != (~c1_ & 0xF)) || (c2 != (~c2_ & 0xF)) || (c3 != (~c3_ & 0xF)); - g[0] = ((c1 & 1) << 2) | ((c2 & 1) << 1) | ((c3 & 1) << 0); - g[1] = ((c1 & 2) << 1) | ((c2 & 2) << 0) | ((c3 & 2) >> 1); - g[2] = ((c1 & 4) << 0) | ((c2 & 4) >> 1) | ((c3 & 4) >> 2); - g[3] = ((c1 & 8) >> 1) | ((c2 & 8) >> 2) | ((c3 & 8) >> 3); - isSectorTrailer = false; - } - - // Which access group is this block in? - if (no_of_blocks == 4) - { - group = blockOffset; - firstInGroup = true; - } - else - { - group = blockOffset / 5; - firstInGroup = (group == 3) || (group != (blockOffset + 1) / 5); - } - - if (firstInGroup) - { - // Print access bits - /* - printf(F(" [ ")); - printf("%d", (g[group] >> 2) & 1); - printf(F(" ")); - printf("%d", (g[group] >> 1) & 1); - printf(F(" ")); - printf("%d", (g[group] >> 0) & 1); - printf(F(" ] ")); - if (invertedError) - { - printf(F(" Inverted access bits did not match! \n")); - } */ - } - - if (group != 3 && (g[group] == 1 || g[group] == 6)) - { // Not a sector trailer, a value block - /* - int32_t value = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | (buffer[0]); - printf(F(" Value=0x")); - // printf("%x",value); - printf(F(" Adr=0x")); - printf("%x", buffer[12]); - */ - } - - // vTaskDelay(10 / portTICK_PERIOD_MS); - // printf("\n"); - } - - return; -} // End PICC_DumpMifareClassicSectorToSerial() - -/** - * Dumps memory contents of a MIFARE Ultralight PICC. - */ -void PICC_DumpMifareUltralightToSerial() -{ - StatusCode status; - byte byteCount; - byte buffer[18]; - byte i; - - printf(F("Page 0 1 2 3\n")); - // Try the mpages of the original Ultralight. Ultralight C has more pages. - for (byte page = 0; page < 16; page += 4) - { // Read returns data for 4 pages at a time. - // Read pages - byteCount = sizeof(buffer); - status = MIFARE_Read(page, buffer, &byteCount); - if (status != STATUS_OK) - { - printf(F("MIFARE_Read() failed: ")); - printf("%s\n", GetStatusCodeName(status)); - break; - } - // Dump data - for (byte offset = 0; offset < 4; offset++) - { - i = page + offset; - if (i < 10) - printf(F(" ")); // Pad with spaces - else - printf(F(" ")); // Pad with spaces - printf("%d", i); - printf(F(" ")); - for (byte index = 0; index < 4; index++) - { - i = 4 * offset + index; - if (buffer[i] < 0x10) - printf(F(" 0")); - else - printf(F(" ")); - printf("%x", buffer[i]); - } - printf("\n"); - } - } -} // End PICC_DumpMifareUltralightToSerial() - -/** - * Calculates the bit pattern needed for the specified access bits. In the [C1 C2 C3] tuples C1 is MSB (=4) and C3 is LSB (=1). - */ -void MIFARE_SetAccessBits(byte *accessBitBuffer, ///< Pointer to byte 6, 7 and 8 in the sector trailer. Bytes [0..2] will be set. - byte g0, ///< Access bits [C1 C2 C3] for block 0 (for sectors 0-31) or blocks 0-4 (for sectors 32-39) - byte g1, ///< Access bits C1 C2 C3] for block 1 (for sectors 0-31) or blocks 5-9 (for sectors 32-39) - byte g2, ///< Access bits C1 C2 C3] for block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) - byte g3 ///< Access bits C1 C2 C3] for the sector trailer, block 3 (for sectors 0-31) or block 15 (for sectors 32-39) -) -{ - byte c1 = ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2); - byte c2 = ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1); - byte c3 = ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0); - - accessBitBuffer[0] = (~c2 & 0xF) << 4 | (~c1 & 0xF); - accessBitBuffer[1] = c1 << 4 | (~c3 & 0xF); - accessBitBuffer[2] = c3 << 4 | c2; -} // End MIFARE_SetAccessBits() - -/** - * Performs the "magic sequence" needed to get Chinese UID changeable - * Mifare cards to allow writing to sector 0, where the card UID is stored. - * - * Note that you do not need to have selected the card through REQA or WUPA, - * this sequence works immediately when the card is in the reader vicinity. - * This means you can use this method even on "bricked" cards that your reader does - * not recognise anymore (see MIFARE_UnbrickUidSector). - * - * Of course with non-bricked devices, you're free to select them before calling this function. - */ -bool MIFARE_OpenUidBackdoor(bool logErrors) -{ - // Magic sequence: - // > 50 00 57 CD (HALT + CRC) - // > 40 (7 bits only) - // < A (4 bits only) - // > 43 - // < A (4 bits only) - // Then you can write to sector 0 without authenticating - - PICC_HaltA(); // 50 00 57 CD - - byte cmd = 0x40; - byte validBits = 7; - /* Our command is only 7 bits. After receiving card response, - this will contain amount of valid response bits. */ - byte response[32]; // Card's response is written here - byte received; - StatusCode status = PCD_TransceiveData(&cmd, (byte)1, response, &received, &validBits, (byte)0, false); // 40 - if (status != STATUS_OK) - { - if (logErrors) - { - printf(F("Card did not respond to 0x40 after HALT command. Are you sure it is a UID changeable one?\n")); - printf(F("Error name: ")); - printf("%s\n", GetStatusCodeName(status)); - } - return false; - } - if (received != 1 || response[0] != 0x0A) - { - if (logErrors) - { - printf(F("Got bad response on backdoor 0x40 command: ")); - printf("%x", response[0]); - printf(F(" (")); - printf("%d", validBits); - printf(F(" valid bits)\r\n")); - } - return false; - } - - cmd = 0x43; - validBits = 8; - status = PCD_TransceiveData(&cmd, (byte)1, response, &received, &validBits, (byte)0, false); // 43 - if (status != STATUS_OK) - { - if (logErrors) - { - printf(F("Error in communication at command 0x43, after successfully executing 0x40\n")); - printf(F("Error name: ")); - printf("%s\n", GetStatusCodeName(status)); - } - return false; - } - if (received != 1 || response[0] != 0x0A) - { - if (logErrors) - { - printf(F("Got bad response on backdoor 0x43 command: ")); - printf("%x", response[0]); - printf(F(" (")); - printf("%d", validBits); - printf(F(" valid bits)\r\n")); - } - return false; - } - - // You can now write to sector 0 without authenticating! - return true; -} // End MIFARE_OpenUidBackdoor() - -/** - * Reads entire block 0, including all manufacturer data, and overwrites - * that block with the new UID, a freshly calculated BCC, and the original - * manufacturer data. - * - * It assumes a default KEY A of 0xFFFFFFFFFFFF. - * Make sure to have selected the card before this function is called. - */ -bool MIFARE_SetUid(byte *newUid, byte uidSize, bool logErrors) -{ - - // UID + BCC byte can not be larger than 16 together - if (!newUid || !uidSize || uidSize > 15) - { - if (logErrors) - { - printf(F("New UID buffer empty, size 0, or size > 15 given\n")); - } - return false; - } - - // Authenticate for reading - MIFARE_Key key = { - {0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF}}; - StatusCode status = PCD_Authenticate(PICC_CMD_MF_AUTH_KEY_A, (byte)1, &key, &uid); - if (status != STATUS_OK) - { - - if (status == STATUS_TIMEOUT) - { - // We get a read timeout if no card is selected yet, so let's select one - - // Wake the card up again if sleeping - // byte atqa_answer[2]; - // byte atqa_size = 2; - // PICC_WakeupA(atqa_answer, &atqa_size); - - if (!PICC_IsNewCardPresent() || !PICC_ReadCardSerial()) - { - printf(F("No card was previously selected, and none are available. Failed to set UID.\n")); - return false; - } - - status = PCD_Authenticate(PICC_CMD_MF_AUTH_KEY_A, (byte)1, &key, &uid); - if (status != STATUS_OK) - { - // We tried, time to give up - if (logErrors) - { - printf(F("Failed to authenticate to card for reading, could not set UID: \n")); - printf("%s\n", GetStatusCodeName(status)); - } - return false; - } - } - else - { - if (logErrors) - { - printf(F("PCD_Authenticate() failed: ")); - printf("%s\n", GetStatusCodeName(status)); - } - return false; - } - } - - // Read block 0 - byte block0_buffer[18]; - byte byteCount = sizeof(block0_buffer); - status = MIFARE_Read((byte)0, block0_buffer, &byteCount); - if (status != STATUS_OK) - { - if (logErrors) - { - printf(F("MIFARE_Read() failed: ")); - printf("%s\n", GetStatusCodeName(status)); - printf(F("Are you sure your KEY A for sector 0 is 0xFFFFFFFFFFFF?\n")); - } - return false; - } - - // Write new UID to the data we just read, and calculate BCC byte - byte bcc = 0; - for (uint8_t i = 0; i < uidSize; i++) - { - block0_buffer[i] = newUid[i]; - bcc ^= newUid[i]; - } - - // Write BCC byte to buffer - block0_buffer[uidSize] = bcc; - - // Stop encrypted traffic so we can send raw bytes - PCD_StopCrypto1(); - - // Activate UID backdoor - if (!MIFARE_OpenUidBackdoor(logErrors)) - { - if (logErrors) - { - printf(F("Activating the UID backdoor failed.\n")); - } - return false; - } - - // Write modified block 0 back to card - status = MIFARE_Write((byte)0, block0_buffer, (byte)16); - if (status != STATUS_OK) - { - if (logErrors) - { - printf(F("MIFARE_Write() failed: ")); - printf("%s\n", GetStatusCodeName(status)); - } - return false; - } - - // Wake the card up again - byte atqa_answer[2]; - byte atqa_size = 2; - PICC_WakeupA(atqa_answer, &atqa_size); - - return true; -} - -/** - * Resets entire sector 0 to zeroes, so the card can be read again by readers. - */ -bool MIFARE_UnbrickUidSector(bool logErrors) -{ - MIFARE_OpenUidBackdoor(logErrors); - - byte block0_buffer[] = { - 0x01, - 0x02, - 0x03, - 0x04, - 0x04, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00}; - - // Write modified block 0 back to card - StatusCode status = MIFARE_Write((byte)0, block0_buffer, (byte)16); - if (status != STATUS_OK) - { - if (logErrors) - { - printf(F("MIFARE_Write() failed: ")); - printf("%s\n", GetStatusCodeName(status)); - } - return false; - } - return true; -} - -///////////////////////////////////////////////////////////////////////////////////// -// Convenience functions - does not add extra functionality -///////////////////////////////////////////////////////////////////////////////////// - -/** - * Returns true if a PICC responds to PICC_CMD_REQA. - * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are ignored. - * - * @return bool - */ - -bool PICC_IsNewCardPresent() -{ - static uint8_t bufferATQA[2] = {0, 0}; - uint8_t bufferSize = sizeof(bufferATQA); - // Reset baud rates - PCD_WriteRegister(TxModeReg, 0x00); - PCD_WriteRegister(RxModeReg, 0x00); - // Reset ModWidthReg - PCD_WriteRegister(ModWidthReg, 0x26); - uint8_t result = PICC_RequestA(bufferATQA, &bufferSize); - return (result == STATUS_OK || result == STATUS_COLLISION); -} - -void dump_byte_array(uint8_t *buffer, uint8_t bufferSize) -{ - - for (uint8_t i = 0; i < bufferSize; i++) - { - printf(" %x", buffer[i]); - } - printf("\n"); -} - -/** - * Simple wrapper around PICC_Select. - * Returns true if a UID could be read. - * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() first. - * The read UID is available in the class variable uid. - * - * @return bool - */ -bool PICC_ReadCardSerial() -{ - uint8_t result = PICC_Select(&uid, 0); - printf("PICC_ReadCardSerial: Status %s\n", GetStatusCodeName(result)); - return (result == STATUS_OK); -} // End PICC_ReadCardSerial() - -void rc522_main(void *vParam) -{ - // printf("%d\n", CommandReg); - - esp_err_t ret; - spi_bus_config_t buscfg = { - .miso_io_num = PIN_NUM_MISO, - .mosi_io_num = PIN_NUM_MOSI, - .sclk_io_num = PIN_NUM_CLK, - .quadwp_io_num = -1, - .quadhd_io_num = -1}; - spi_device_interface_config_t devcfg = { - .clock_speed_hz = 1 * 1000 * 1000, // Clock out at 10 MHz - .mode = 0, // SPI mode 0 - .spics_io_num = PIN_NUM_CS, // CS pin - .queue_size = 8, // We want to be able to queue 7 transactions at a time - //.pre_cb=ili_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line - }; - // Initialize the SPI bus - ret = spi_bus_initialize(VSPI_HOST, &buscfg, 1); - assert(ret == ESP_OK); - // Attach the RFID to the SPI bus - ret = spi_bus_add_device(VSPI_HOST, &devcfg, &spi); - assert(ret == ESP_OK); - - printf("test %x\n",PCD_PerformSelfTest()); - - /*printf("%x\n",PCD_ReadRegister(ModWidthReg)); - printf("%x\n",PCD_ReadRegister(TxControlReg)); - printf("%x\n",PCD_ReadRegister(TxASKReg)); - - printf("%x\n",PCD_ReadRegister(ModWidthReg)); - printf("%x\n",PCD_ReadRegister(TxControlReg)); - printf("%x\n",PCD_ReadRegister(TxASKReg));*/ - // PICC_HaltA(); - // PCD_StopCrypto1(); - - char codePass[7]; - char *passVerify = "BLIFRFID"; - - PCD_Init(); - uint8_t r; - while (1) - { - - if (evse_is_require_auth() && auth_get_enable()) - { - - r = PICC_IsNewCardPresent(); - if (r) - { - ESP_LOGI(TAG, "Card present - %s\n", GetStatusCodeName(r)); - if (PICC_ReadCardSerial()) - { - dump_byte_array(&uid.uidByte, uid.size); - PICC_DumpToSerial(&uid); - // ESP_LOGI(TAG, "---------card decoded sucess--------------\n"); - - for (int i = 0; i < 8; i++) - { - codePass[i] = (char)bufferSector[i + 32]; - } - - if (strcmp(codePass, passVerify) == 0) - { - evse_authorize(); - } - else - { - ESP_LOGI(TAG, "Error"); - } - - memset(codePass, 0, sizeof(codePass)); - } - // printf("\n-----------------------------------------\n\n\n"); - } - vTaskDelay(500 / portTICK_PERIOD_MS); - } - else - { - vTaskDelay(60000 / portTICK_PERIOD_MS); - } - } -} - -void initRc522() -{ - ESP_LOGI(TAG, "Starting rfid"); - xTaskCreate(&rc522_main, "rc522_main", 8192, NULL, 5, NULL); -} \ No newline at end of file diff --git a/components/auth/src/wiegand.c b/components/auth/src/wiegand.c old mode 100644 new mode 100755 index c9b14f4..083734e --- a/components/auth/src/wiegand.c +++ b/components/auth/src/wiegand.c @@ -1,112 +1,18 @@ -#include -#include -#include -#include -#include +#include "wiegand_reader.h" +#include "wiegand.h" #include -#include -#include -#include +#include -static const char *TAG = "Wiegand_reader"; - -#define WIEGAND_PIN_D0 19 -#define WIEGAND_PIN_D1 18 -#define WIEGAND_BUF_SIZE 50 -#define QUEUE_SIZE 10 +static const char *TAG = "WiegandReader"; static wiegand_reader_t reader; -static QueueHandle_t data_queue = NULL; -// Single data packet -typedef struct +void wiegand_reader_init(int pin_d0, int pin_d1, wiegand_callback_t callback) { - uint8_t data[WIEGAND_BUF_SIZE]; - size_t bits; -} data_packet_t; - -// callback on new data in reader -static void reader_callback(wiegand_reader_t *r) -{ - data_packet_t p; - p.bits = r->bits; - size_t data_size = (r->bits + 7) / 8; - memcpy(p.data, r->buf, data_size); - - if (xQueueSendToBack(data_queue, &p, portMAX_DELAY) != pdTRUE) { - ESP_LOGE(TAG, "Failed to send data to queue"); - } -} - -static void wiegand_task(void *arg) -{ - // Create queue - data_queue = xQueueCreate(QUEUE_SIZE, sizeof(data_packet_t)); - if (!data_queue) - { - ESP_LOGE(TAG, "Error creating queue"); - ESP_ERROR_CHECK(ESP_ERR_NO_MEM); - } - - // Initialize reader - esp_err_t err = wiegand_reader_init(&reader, WIEGAND_PIN_D0, WIEGAND_PIN_D1, - true, WIEGAND_BUF_SIZE, reader_callback, - WIEGAND_MSB_FIRST, WIEGAND_LSB_FIRST); + esp_err_t err = wiegand_reader_config(&reader, pin_d0, pin_d1, + callback, + WIEGAND_MSB_FIRST, WIEGAND_LSB_FIRST); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize Wiegand reader: %s", esp_err_to_name(err)); - return; - } - - data_packet_t p; - while (1) - { - ESP_LOGI(TAG, "Waiting for Wiegand data..."); - if (xQueueReceive(data_queue, &p, pdMS_TO_TICKS(1000)) != pdTRUE) { - ESP_LOGW(TAG, "No Wiegand data received within the timeout"); - continue; - } - - ESP_LOGI(TAG, "Bits received: %d", p.bits); - for (size_t i = 0; i < (p.bits + 7) / 8; i++) - ESP_LOGI(TAG, " 0x%02x", p.data[i]); - - char str[20]; - if (p.bits == 26 || p.bits == 34) - { - evse_authorize(); - - - - /*----26 - sprintf(str, "%03d%03d%03d", p.data[0], p.data[1], p.data[2]); - - if (ocpp_is_TransactionActive()) - { - ocpp_end_transaction(str); - } - else - { - ocpp_begin_transaction(str); - }*/ - /*----34 - sprintf(str, "%03d%03d%03d%03d", p.data[0], p.data[1], p.data[2], p.data[3]); - - if (ocpp_is_TransactionActive()) - { - ocpp_end_transaction(str); - } - else - { - ocpp_begin_transaction(str); - } - */ - - } + ESP_LOGE(TAG, "Failed to init Wiegand reader: %s", esp_err_to_name(err)); } } - -void initialize_wiegand_reader() -{ - ESP_LOGI(TAG, "Starting Wiegand reader"); - xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 5, NULL); -} diff --git a/components/auth/src/main_wiegand.c b/components/auth/src/wiegand_reader.c old mode 100644 new mode 100755 similarity index 100% rename from components/auth/src/main_wiegand.c rename to components/auth/src/wiegand_reader.c diff --git a/components/evse/evse_config.c b/components/evse/evse_config.c index b32c4c8..0e0af10 100755 --- a/components/evse/evse_config.c +++ b/components/evse/evse_config.c @@ -1,3 +1,5 @@ +#include // Include for PRI macros + #include "evse_config.h" #include "board_config.h" #include "evse_limits.h" @@ -8,7 +10,7 @@ static const char *TAG = "evse_config"; static nvs_handle_t nvs; -// Parâmetros configuráveis +// Configurable parameters static uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT; static uint8_t grid_max_current = MAX_GRID_CURRENT_LIMIT; static uint16_t charging_current; @@ -18,7 +20,8 @@ static uint8_t temp_threshold = 60; static bool require_auth; esp_err_t evse_config_init(void) { - ESP_LOGI(TAG, "Abrindo namespace NVS"); + ESP_LOGD(TAG, "Initializing NVS configuration..."); + ESP_LOGI(TAG, "Opening NVS namespace"); return nvs_open("evse", NVS_READWRITE, &nvs); } @@ -29,32 +32,40 @@ void evse_check_defaults(void) { uint32_t u32; bool needs_commit = false; + ESP_LOGD(TAG, "Checking default parameters..."); + // Max charging current err = nvs_get_u8(nvs, "max_chrg_curr", &u8); + ESP_LOGD(TAG, "Max charging current read: %d", u8); if (err != ESP_OK || u8 < MIN_CHARGING_CURRENT_LIMIT || u8 > MAX_CHARGING_CURRENT_LIMIT) { max_charging_current = MAX_CHARGING_CURRENT_LIMIT; nvs_set_u8(nvs, "max_chrg_curr", max_charging_current); needs_commit = true; + ESP_LOGD(TAG, "Max charging current adjusted to: %d", max_charging_current); } else { max_charging_current = u8; } // Grid max current err = nvs_get_u8(nvs, "grid_max_curr", &u8); + ESP_LOGD(TAG, "Grid max current read: %d", u8); if (err != ESP_OK || u8 < MIN_GRID_CURRENT_LIMIT || u8 > MAX_GRID_CURRENT_LIMIT) { grid_max_current = MAX_GRID_CURRENT_LIMIT; nvs_set_u8(nvs, "grid_max_curr", grid_max_current); needs_commit = true; + ESP_LOGD(TAG, "Grid max current adjusted to: %d", grid_max_current); } else { grid_max_current = u8; } // Charging current (decA) err = nvs_get_u16(nvs, "def_chrg_curr", &u16); + ESP_LOGD(TAG, "Charging current read: %d", u16); if (err != ESP_OK || u16 < (MIN_CHARGING_CURRENT_LIMIT * 10) || u16 > (max_charging_current * 10)) { charging_current = max_charging_current * 10; nvs_set_u16(nvs, "def_chrg_curr", charging_current); needs_commit = true; + ESP_LOGD(TAG, "Charging current adjusted to: %d", charging_current); } else { charging_current = u16; } @@ -64,6 +75,7 @@ void evse_check_defaults(void) { if (err != ESP_OK) { nvs_set_u8(nvs, "require_auth", require_auth); needs_commit = true; + ESP_LOGD(TAG, "Require auth adjusted to: %d", require_auth); } err = nvs_get_u8(nvs, "socket_outlet", &u8); @@ -71,6 +83,7 @@ void evse_check_defaults(void) { if (err != ESP_OK) { nvs_set_u8(nvs, "socket_outlet", socket_outlet); needs_commit = true; + ESP_LOGD(TAG, "Socket outlet adjusted to: %d", socket_outlet); } err = nvs_get_u8(nvs, "rcm", &u8); @@ -78,6 +91,7 @@ void evse_check_defaults(void) { if (err != ESP_OK) { nvs_set_u8(nvs, "rcm", rcm); needs_commit = true; + ESP_LOGD(TAG, "RCM adjusted to: %d", rcm); } err = nvs_get_u8(nvs, "temp_threshold", &u8); @@ -85,119 +99,181 @@ void evse_check_defaults(void) { if (err != ESP_OK) { nvs_set_u8(nvs, "temp_threshold", temp_threshold); needs_commit = true; + ESP_LOGD(TAG, "Temp threshold adjusted to: %d", temp_threshold); } - // Limites adicionais + // Additional limits if (nvs_get_u32(nvs, "def_cons_lim", &u32) == ESP_OK) { evse_set_consumption_limit(u32); + ESP_LOGD(TAG, "Consumption limit read and applied: %" PRIu32, u32); // Updated to PRIu32 } if (nvs_get_u32(nvs, "def_ch_time_lim", &u32) == ESP_OK) { evse_set_charging_time_limit(u32); + ESP_LOGD(TAG, "Charging time limit read and applied: %" PRIu32, u32); // Updated to PRIu32 } if (nvs_get_u16(nvs, "def_un_pwr_lim", &u16) == ESP_OK) { evse_set_under_power_limit(u16); + ESP_LOGD(TAG, "Under power limit read and applied: %d", u16); } if (needs_commit) { nvs_commit(nvs); + ESP_LOGD(TAG, "Changes committed to NVS."); } } -// Corrente -uint8_t evse_get_max_charging_current(void) { return max_charging_current; } +// Current +uint8_t evse_get_max_charging_current(void) { + ESP_LOGI(TAG, "Max charging current read: %d", max_charging_current); + return max_charging_current; +} + esp_err_t evse_set_max_charging_current(uint8_t value) { + ESP_LOGI(TAG, "Attempting to set max charging current: %d", value); if (value < MIN_CHARGING_CURRENT_LIMIT || value > MAX_CHARGING_CURRENT_LIMIT) return ESP_ERR_INVALID_ARG; max_charging_current = value; nvs_set_u8(nvs, "max_chrg_curr", value); - return nvs_commit(nvs); + nvs_commit(nvs); + ESP_LOGD(TAG, "Max charging current adjusted to: %d", max_charging_current); + return ESP_OK; +} + +uint8_t grid_get_max_current(void) { + ESP_LOGD(TAG, "Grid max current read: %d", grid_max_current); + return grid_max_current; } -uint8_t grid_get_max_current(void) { return grid_max_current; } esp_err_t grid_set_max_current(uint8_t value) { + ESP_LOGD(TAG, "Attempting to set grid max current: %d", value); if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT) return ESP_ERR_INVALID_ARG; grid_max_current = value; nvs_set_u8(nvs, "grid_max_curr", value); - return nvs_commit(nvs); + nvs_commit(nvs); + ESP_LOGD(TAG, "Grid max current adjusted to: %d", grid_max_current); + return ESP_OK; +} + +uint16_t evse_get_charging_current(void) { + ESP_LOGD(TAG, "Charging current read: %d", charging_current); + return charging_current; } -uint16_t evse_get_charging_current(void) { return charging_current; } esp_err_t evse_set_charging_current(uint16_t value) { + ESP_LOGD(TAG, "Attempting to set charging current: %d", value); if (value < (MIN_CHARGING_CURRENT_LIMIT * 10) || value > (max_charging_current * 10)) return ESP_ERR_INVALID_ARG; charging_current = value; nvs_set_u16(nvs, "def_chrg_curr", value); - return nvs_commit(nvs); + nvs_commit(nvs); + ESP_LOGD(TAG, "Charging current adjusted to: %d", charging_current); + return ESP_OK; } uint16_t evse_get_default_charging_current(void) { uint16_t value; nvs_get_u16(nvs, "def_chrg_curr", &value); + ESP_LOGD(TAG, "Default charging current read: %d", value); return value; } + esp_err_t evse_set_default_charging_current(uint16_t value) { + ESP_LOGD(TAG, "Attempting to set default charging current: %d", value); if (value < (MIN_CHARGING_CURRENT_LIMIT * 10) || value > (max_charging_current * 10)) return ESP_ERR_INVALID_ARG; nvs_set_u16(nvs, "def_chrg_curr", value); - return nvs_commit(nvs); + nvs_commit(nvs); + ESP_LOGD(TAG, "Default charging current adjusted to: %d", value); + return ESP_OK; } // Socket outlet -bool evse_get_socket_outlet(void) { return socket_outlet; } +bool evse_get_socket_outlet(void) { + ESP_LOGD(TAG, "Socket outlet read: %d", socket_outlet); + return socket_outlet; +} + esp_err_t evse_set_socket_outlet(bool value) { + ESP_LOGD(TAG, "Attempting to set socket outlet: %d", value); if (value && !board_config.proximity) return ESP_ERR_INVALID_ARG; socket_outlet = value; nvs_set_u8(nvs, "socket_outlet", value); - return nvs_commit(nvs); + nvs_commit(nvs); + ESP_LOGD(TAG, "Socket outlet adjusted to: %d", socket_outlet); + return ESP_OK; } // RCM -bool evse_is_rcm(void) { return rcm; } +bool evse_is_rcm(void) { + ESP_LOGD(TAG, "RCM read: %d", rcm); + return rcm; +} + esp_err_t evse_set_rcm(bool value) { + ESP_LOGD(TAG, "Attempting to set RCM: %d", value); if (value && !board_config.rcm) return ESP_ERR_INVALID_ARG; rcm = value; nvs_set_u8(nvs, "rcm", value); - return nvs_commit(nvs); + nvs_commit(nvs); + ESP_LOGD(TAG, "RCM adjusted to: %d", rcm); + return ESP_OK; +} + +// Temperature +uint8_t evse_get_temp_threshold(void) { + ESP_LOGD(TAG, "Temp threshold read: %d", temp_threshold); + return temp_threshold; } -// Temperatura -uint8_t evse_get_temp_threshold(void) { return temp_threshold; } esp_err_t evse_set_temp_threshold(uint8_t value) { + ESP_LOGI(TAG, "Attempting to set temp threshold: %d", value); if (value < 40 || value > 80) return ESP_ERR_INVALID_ARG; temp_threshold = value; nvs_set_u8(nvs, "temp_threshold", value); - return nvs_commit(nvs); + nvs_commit(nvs); + ESP_LOGI(TAG, "Temp threshold adjusted to: %d", temp_threshold); + return ESP_OK; +} + +// Authentication +bool evse_is_require_auth(void) { + ESP_LOGD(TAG, "Require auth read: %d", require_auth); + return require_auth; } -// Autenticação -bool evse_is_require_auth(void) { return require_auth; } void evse_set_require_auth(bool value) { + ESP_LOGI(TAG, "Attempting to set require auth: %d", value); require_auth = value; nvs_set_u8(nvs, "require_auth", value); nvs_commit(nvs); + ESP_LOGI(TAG, "Require auth adjusted to: %d", require_auth); } -// Disponibilidade +// Availability static bool is_available = true; bool evse_config_is_available(void) { + ESP_LOGD(TAG, "Checking availability: %d", is_available); return is_available; } void evse_config_set_available(bool available) { + ESP_LOGD(TAG, "Setting availability to: %d", available); is_available = available; } -// Ativação/desativação +// Enable/Disable static bool is_enabled = true; bool evse_config_is_enabled(void) { + ESP_LOGD(TAG, "Checking if enabled: %d", is_enabled); return is_enabled; } void evse_config_set_enabled(bool enabled) { + ESP_LOGD(TAG, "Setting enabled state to: %d", enabled); is_enabled = enabled; } diff --git a/components/evse/include/evse_config.h b/components/evse/include/evse_config.h index d47bf5c..22e615d 100755 --- a/components/evse/include/evse_config.h +++ b/components/evse/include/evse_config.h @@ -15,7 +15,7 @@ extern "C" { // Corrente máxima de carregamento (configurável pelo usuário) #define MIN_CHARGING_CURRENT_LIMIT 6 // A -#define MAX_CHARGING_CURRENT_LIMIT 16 // A +#define MAX_CHARGING_CURRENT_LIMIT 32 // A // Corrente máxima da rede elétrica (grid) #define MIN_GRID_CURRENT_LIMIT 6 // A diff --git a/components/protocols/src/rest.c b/components/protocols/src/rest.c index ac81708..919b8af 100755 --- a/components/protocols/src/rest.c +++ b/components/protocols/src/rest.c @@ -15,6 +15,8 @@ #include "esp_vfs.h" #include "cJSON.h" #include "rest.h" +#include "evse_api.h" + static const char *REST_TAG = "esp-rest"; #define REST_CHECK(a, str, goto_tag, ...) \ @@ -68,7 +70,7 @@ static struct { int energyLimit; int chargingTimeLimit; int temperatureLimit; -} settings_config = {32, 0, 0, 0, 60}; +} settings_config = {0, 0, 0, 0, 0}; // Estruturas para armazenar as configurações de autenticação e usuários @@ -76,7 +78,7 @@ static struct { bool RFID; bool App; bool Password; -} auth_methods = {false, false, true}; +} auth_methods = {false, false, false}; static struct { char username[128]; @@ -356,7 +358,9 @@ static esp_err_t config_settings_post_handler(httpd_req_t *req) // Atualizando as configurações cJSON *currentLimit = cJSON_GetObjectItem(json, "currentLimit"); - if (currentLimit) settings_config.currentLimit = currentLimit->valueint; + if (currentLimit) + evse_set_max_charging_current(currentLimit->valueint); + //settings_config.currentLimit = currentLimit->valueint; cJSON *powerLimit = cJSON_GetObjectItem(json, "powerLimit"); if (powerLimit) settings_config.powerLimit = powerLimit->valueint; @@ -368,7 +372,10 @@ static esp_err_t config_settings_post_handler(httpd_req_t *req) if (chargingTimeLimit) settings_config.chargingTimeLimit = chargingTimeLimit->valueint; cJSON *temperatureLimit = cJSON_GetObjectItem(json, "temperatureLimit"); - if (temperatureLimit) settings_config.temperatureLimit = temperatureLimit->valueint; + if (temperatureLimit) + evse_set_temp_threshold(temperatureLimit->valueint); + + //settings_config.temperatureLimit = temperatureLimit->valueint; cJSON_Delete(json); @@ -386,11 +393,14 @@ static esp_err_t config_settings_get_handler(httpd_req_t *req) // Criar objeto JSON para enviar as configurações atuais cJSON *config = cJSON_CreateObject(); - cJSON_AddNumberToObject(config, "currentLimit", settings_config.currentLimit); + + + cJSON_AddNumberToObject(config, "maxCurrentLimit", evse_get_max_charging_current()); + cJSON_AddNumberToObject(config, "currentLimit", evse_get_max_charging_current()); cJSON_AddNumberToObject(config, "powerLimit", settings_config.powerLimit); cJSON_AddNumberToObject(config, "energyLimit", settings_config.energyLimit); cJSON_AddNumberToObject(config, "chargingTimeLimit", settings_config.chargingTimeLimit); - cJSON_AddNumberToObject(config, "temperatureLimit", settings_config.temperatureLimit); + cJSON_AddNumberToObject(config, "temperatureLimit", evse_get_temp_threshold()); // Convertendo para string e enviando a resposta const char *config_str = cJSON_Print(config); @@ -583,7 +593,7 @@ static esp_err_t config_auth_methods_get_handler(httpd_req_t *req) // Criar objeto JSON com as configurações de métodos de autenticação cJSON *config = cJSON_CreateObject(); - cJSON_AddBoolToObject(config, "RFID", auth_methods.RFID); + cJSON_AddBoolToObject(config, "RFID", evse_is_require_auth()); cJSON_AddBoolToObject(config, "App", auth_methods.App); cJSON_AddBoolToObject(config, "Password", auth_methods.Password); @@ -618,7 +628,7 @@ static esp_err_t config_auth_methods_post_handler(httpd_req_t *req) // Atualizando as configurações de autenticação cJSON *RFID = cJSON_GetObjectItem(json, "RFID"); - if (RFID) auth_methods.RFID = RFID->valueint; + if (RFID) evse_set_require_auth(RFID->valueint != 0); cJSON *App = cJSON_GetObjectItem(json, "App"); if (App) auth_methods.App = App->valueint; diff --git a/components/protocols/webfolder/assets/index-3W2ZKmTa.js b/components/protocols/webfolder/assets/index-3W2ZKmTa.js new file mode 100755 index 0000000..2101af7 --- /dev/null +++ b/components/protocols/webfolder/assets/index-3W2ZKmTa.js @@ -0,0 +1,51 @@ +(function(){const o=document.createElement("link").relList;if(o&&o.supports&&o.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))r(d);new MutationObserver(d=>{for(const v of d)if(v.type==="childList")for(const x of v.addedNodes)x.tagName==="LINK"&&x.rel==="modulepreload"&&r(x)}).observe(document,{childList:!0,subtree:!0});function s(d){const v={};return d.integrity&&(v.integrity=d.integrity),d.referrerPolicy&&(v.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?v.credentials="include":d.crossOrigin==="anonymous"?v.credentials="omit":v.credentials="same-origin",v}function r(d){if(d.ep)return;d.ep=!0;const v=s(d);fetch(d.href,v)}})();function kd(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var Tf={exports:{}},Du={};/** + * @license React + * react-jsx-runtime.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var jd;function Hy(){if(jd)return Du;jd=1;var i=Symbol.for("react.transitional.element"),o=Symbol.for("react.fragment");function s(r,d,v){var x=null;if(v!==void 0&&(x=""+v),d.key!==void 0&&(x=""+d.key),"key"in d){v={};for(var z in d)z!=="key"&&(v[z]=d[z])}else v=d;return d=v.ref,{$$typeof:i,type:r,key:x,ref:d!==void 0?d:null,props:v}}return Du.Fragment=o,Du.jsx=s,Du.jsxs=s,Du}var Cd;function By(){return Cd||(Cd=1,Tf.exports=Hy()),Tf.exports}var y=By(),Af={exports:{}},le={};/** + * @license React + * react.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Ud;function qy(){if(Ud)return le;Ud=1;var i=Symbol.for("react.transitional.element"),o=Symbol.for("react.portal"),s=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),v=Symbol.for("react.consumer"),x=Symbol.for("react.context"),z=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),N=Symbol.for("react.lazy"),B=Symbol.iterator;function M(g){return g===null||typeof g!="object"?null:(g=B&&g[B]||g["@@iterator"],typeof g=="function"?g:null)}var L={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},U=Object.assign,q={};function Q(g,H,Z){this.props=g,this.context=H,this.refs=q,this.updater=Z||L}Q.prototype.isReactComponent={},Q.prototype.setState=function(g,H){if(typeof g!="object"&&typeof g!="function"&&g!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,g,H,"setState")},Q.prototype.forceUpdate=function(g){this.updater.enqueueForceUpdate(this,g,"forceUpdate")};function w(){}w.prototype=Q.prototype;function V(g,H,Z){this.props=g,this.context=H,this.refs=q,this.updater=Z||L}var G=V.prototype=new w;G.constructor=V,U(G,Q.prototype),G.isPureReactComponent=!0;var ee=Array.isArray,J={H:null,A:null,T:null,S:null,V:null},he=Object.prototype.hasOwnProperty;function Te(g,H,Z,Y,$,fe){return Z=fe.ref,{$$typeof:i,type:g,key:H,ref:Z!==void 0?Z:null,props:fe}}function Ne(g,H){return Te(g.type,H,void 0,void 0,void 0,g.props)}function Se(g){return typeof g=="object"&&g!==null&&g.$$typeof===i}function Je(g){var H={"=":"=0",":":"=2"};return"$"+g.replace(/[=:]/g,function(Z){return H[Z]})}var st=/\/+/g;function Xe(g,H){return typeof g=="object"&&g!==null&&g.key!=null?Je(""+g.key):H.toString(36)}function El(){}function Tl(g){switch(g.status){case"fulfilled":return g.value;case"rejected":throw g.reason;default:switch(typeof g.status=="string"?g.then(El,El):(g.status="pending",g.then(function(H){g.status==="pending"&&(g.status="fulfilled",g.value=H)},function(H){g.status==="pending"&&(g.status="rejected",g.reason=H)})),g.status){case"fulfilled":return g.value;case"rejected":throw g.reason}}throw g}function Qe(g,H,Z,Y,$){var fe=typeof g;(fe==="undefined"||fe==="boolean")&&(g=null);var te=!1;if(g===null)te=!0;else switch(fe){case"bigint":case"string":case"number":te=!0;break;case"object":switch(g.$$typeof){case i:case o:te=!0;break;case N:return te=g._init,Qe(te(g._payload),H,Z,Y,$)}}if(te)return $=$(g),te=Y===""?"."+Xe(g,0):Y,ee($)?(Z="",te!=null&&(Z=te.replace(st,"$&/")+"/"),Qe($,H,Z,"",function(Wt){return Wt})):$!=null&&(Se($)&&($=Ne($,Z+($.key==null||g&&g.key===$.key?"":(""+$.key).replace(st,"$&/")+"/")+te)),H.push($)),1;te=0;var et=Y===""?".":Y+":";if(ee(g))for(var xe=0;xe>>1,g=_[ge];if(0>>1;ged(Y,P))$d(fe,Y)?(_[ge]=fe,_[$]=P,ge=$):(_[ge]=Y,_[Z]=P,ge=Z);else if($d(fe,P))_[ge]=fe,_[$]=P,ge=$;else break e}}return X}function d(_,X){var P=_.sortIndex-X.sortIndex;return P!==0?P:_.id-X.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var v=performance;i.unstable_now=function(){return v.now()}}else{var x=Date,z=x.now();i.unstable_now=function(){return x.now()-z}}var p=[],h=[],N=1,B=null,M=3,L=!1,U=!1,q=!1,Q=!1,w=typeof setTimeout=="function"?setTimeout:null,V=typeof clearTimeout=="function"?clearTimeout:null,G=typeof setImmediate<"u"?setImmediate:null;function ee(_){for(var X=s(h);X!==null;){if(X.callback===null)r(h);else if(X.startTime<=_)r(h),X.sortIndex=X.expirationTime,o(p,X);else break;X=s(h)}}function J(_){if(q=!1,ee(_),!U)if(s(p)!==null)U=!0,he||(he=!0,Xe());else{var X=s(h);X!==null&&Qe(J,X.startTime-_)}}var he=!1,Te=-1,Ne=5,Se=-1;function Je(){return Q?!0:!(i.unstable_now()-Se_&&Je());){var ge=B.callback;if(typeof ge=="function"){B.callback=null,M=B.priorityLevel;var g=ge(B.expirationTime<=_);if(_=i.unstable_now(),typeof g=="function"){B.callback=g,ee(_),X=!0;break t}B===s(p)&&r(p),ee(_)}else r(p);B=s(p)}if(B!==null)X=!0;else{var H=s(h);H!==null&&Qe(J,H.startTime-_),X=!1}}break e}finally{B=null,M=P,L=!1}X=void 0}}finally{X?Xe():he=!1}}}var Xe;if(typeof G=="function")Xe=function(){G(st)};else if(typeof MessageChannel<"u"){var El=new MessageChannel,Tl=El.port2;El.port1.onmessage=st,Xe=function(){Tl.postMessage(null)}}else Xe=function(){w(st,0)};function Qe(_,X){Te=w(function(){_(i.unstable_now())},X)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(_){_.callback=null},i.unstable_forceFrameRate=function(_){0>_||125<_?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):Ne=0<_?Math.floor(1e3/_):5},i.unstable_getCurrentPriorityLevel=function(){return M},i.unstable_next=function(_){switch(M){case 1:case 2:case 3:var X=3;break;default:X=M}var P=M;M=X;try{return _()}finally{M=P}},i.unstable_requestPaint=function(){Q=!0},i.unstable_runWithPriority=function(_,X){switch(_){case 1:case 2:case 3:case 4:case 5:break;default:_=3}var P=M;M=_;try{return X()}finally{M=P}},i.unstable_scheduleCallback=function(_,X,P){var ge=i.unstable_now();switch(typeof P=="object"&&P!==null?(P=P.delay,P=typeof P=="number"&&0ge?(_.sortIndex=P,o(h,_),s(p)===null&&_===s(h)&&(q?(V(Te),Te=-1):q=!0,Qe(J,P-ge))):(_.sortIndex=g,o(p,_),U||L||(U=!0,he||(he=!0,Xe()))),_},i.unstable_shouldYield=Je,i.unstable_wrapCallback=function(_){var X=M;return function(){var P=M;M=X;try{return _.apply(this,arguments)}finally{M=P}}}}(Mf)),Mf}var qd;function Yy(){return qd||(qd=1,Of.exports=Ly()),Of.exports}var Nf={exports:{}},Ke={};/** + * @license React + * react-dom.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var wd;function Gy(){if(wd)return Ke;wd=1;var i=Cf();function o(p){var h="https://react.dev/errors/"+p;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(o){console.error(o)}}return i(),Nf.exports=Gy(),Nf.exports}/** + * @license React + * react-dom-client.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Yd;function Qy(){if(Yd)return _u;Yd=1;var i=Yy(),o=Cf(),s=Xy();function r(e){var t="https://react.dev/errors/"+e;if(1g||(e.current=ge[g],ge[g]=null,g--)}function Y(e,t){g++,ge[g]=e.current,e.current=t}var $=H(null),fe=H(null),te=H(null),et=H(null);function xe(e,t){switch(Y(te,t),Y(fe,e),Y($,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?id(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=id(t),e=cd(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}Z($),Y($,e)}function Wt(){Z($),Z(fe),Z(te)}function ci(e){e.memoizedState!==null&&Y(et,e);var t=$.current,l=cd(t,e.type);t!==l&&(Y(fe,e),Y($,l))}function Lu(e){fe.current===e&&(Z($),Z(fe)),et.current===e&&(Z(et),Ru._currentValue=P)}var fi=Object.prototype.hasOwnProperty,ri=i.unstable_scheduleCallback,si=i.unstable_cancelCallback,hh=i.unstable_shouldYield,mh=i.unstable_requestPaint,Tt=i.unstable_now,yh=i.unstable_getCurrentPriorityLevel,Lf=i.unstable_ImmediatePriority,Yf=i.unstable_UserBlockingPriority,Yu=i.unstable_NormalPriority,vh=i.unstable_LowPriority,Gf=i.unstable_IdlePriority,gh=i.log,bh=i.unstable_setDisableYieldValue,Ca=null,tt=null;function Ft(e){if(typeof gh=="function"&&bh(e),tt&&typeof tt.setStrictMode=="function")try{tt.setStrictMode(Ca,e)}catch{}}var lt=Math.clz32?Math.clz32:xh,ph=Math.log,Sh=Math.LN2;function xh(e){return e>>>=0,e===0?32:31-(ph(e)/Sh|0)|0}var Gu=256,Xu=4194304;function Al(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Qu(e,t,l){var a=e.pendingLanes;if(a===0)return 0;var u=0,n=e.suspendedLanes,c=e.pingedLanes;e=e.warmLanes;var f=a&134217727;return f!==0?(a=f&~n,a!==0?u=Al(a):(c&=f,c!==0?u=Al(c):l||(l=f&~e,l!==0&&(u=Al(l))))):(f=a&~n,f!==0?u=Al(f):c!==0?u=Al(c):l||(l=a&~e,l!==0&&(u=Al(l)))),u===0?0:t!==0&&t!==u&&(t&n)===0&&(n=u&-u,l=t&-t,n>=l||n===32&&(l&4194048)!==0)?t:u}function Ua(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Eh(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Xf(){var e=Gu;return Gu<<=1,(Gu&4194048)===0&&(Gu=256),e}function Qf(){var e=Xu;return Xu<<=1,(Xu&62914560)===0&&(Xu=4194304),e}function oi(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function Ha(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Th(e,t,l,a,u,n){var c=e.pendingLanes;e.pendingLanes=l,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=l,e.entangledLanes&=l,e.errorRecoveryDisabledLanes&=l,e.shellSuspendCounter=0;var f=e.entanglements,m=e.expirationTimes,T=e.hiddenUpdates;for(l=c&~l;0)":-1u||m[a]!==T[u]){var D=` +`+m[a].replace(" at new "," at ");return e.displayName&&D.includes("")&&(D=D.replace("",e.displayName)),D}while(1<=a&&0<=u);break}}}finally{gi=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?$l(l):""}function zh(e){switch(e.tag){case 26:case 27:case 5:return $l(e.type);case 16:return $l("Lazy");case 13:return $l("Suspense");case 19:return $l("SuspenseList");case 0:case 15:return bi(e.type,!1);case 11:return bi(e.type.render,!1);case 1:return bi(e.type,!0);case 31:return $l("Activity");default:return""}}function If(e){try{var t="";do t+=zh(e),e=e.return;while(e);return t}catch(l){return` +Error generating stack: `+l.message+` +`+l.stack}}function ot(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function er(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Dh(e){var t=er(e)?"checked":"value",l=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),a=""+e[t];if(!e.hasOwnProperty(t)&&typeof l<"u"&&typeof l.get=="function"&&typeof l.set=="function"){var u=l.get,n=l.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return u.call(this)},set:function(c){a=""+c,n.call(this,c)}}),Object.defineProperty(e,t,{enumerable:l.enumerable}),{getValue:function(){return a},setValue:function(c){a=""+c},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Ku(e){e._valueTracker||(e._valueTracker=Dh(e))}function tr(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var l=t.getValue(),a="";return e&&(a=er(e)?e.checked?"true":"false":e.value),e=a,e!==l?(t.setValue(e),!0):!1}function Ju(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var _h=/[\n"\\]/g;function dt(e){return e.replace(_h,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function pi(e,t,l,a,u,n,c,f){e.name="",c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"?e.type=c:e.removeAttribute("type"),t!=null?c==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+ot(t)):e.value!==""+ot(t)&&(e.value=""+ot(t)):c!=="submit"&&c!=="reset"||e.removeAttribute("value"),t!=null?Si(e,c,ot(t)):l!=null?Si(e,c,ot(l)):a!=null&&e.removeAttribute("value"),u==null&&n!=null&&(e.defaultChecked=!!n),u!=null&&(e.checked=u&&typeof u!="function"&&typeof u!="symbol"),f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?e.name=""+ot(f):e.removeAttribute("name")}function lr(e,t,l,a,u,n,c,f){if(n!=null&&typeof n!="function"&&typeof n!="symbol"&&typeof n!="boolean"&&(e.type=n),t!=null||l!=null){if(!(n!=="submit"&&n!=="reset"||t!=null))return;l=l!=null?""+ot(l):"",t=t!=null?""+ot(t):l,f||t===e.value||(e.value=t),e.defaultValue=t}a=a??u,a=typeof a!="function"&&typeof a!="symbol"&&!!a,e.checked=f?e.checked:!!a,e.defaultChecked=!!a,c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"&&(e.name=c)}function Si(e,t,l){t==="number"&&Ju(e.ownerDocument)===e||e.defaultValue===""+l||(e.defaultValue=""+l)}function Wl(e,t,l,a){if(e=e.options,t){t={};for(var u=0;u"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Ri=!1;if(Ct)try{var La={};Object.defineProperty(La,"passive",{get:function(){Ri=!0}}),window.addEventListener("test",La,La),window.removeEventListener("test",La,La)}catch{Ri=!1}var It=null,Oi=null,$u=null;function rr(){if($u)return $u;var e,t=Oi,l=t.length,a,u="value"in It?It.value:It.textContent,n=u.length;for(e=0;e=Xa),yr=" ",vr=!1;function gr(e,t){switch(e){case"keyup":return nm.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function br(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ea=!1;function cm(e,t){switch(e){case"compositionend":return br(t);case"keypress":return t.which!==32?null:(vr=!0,yr);case"textInput":return e=t.data,e===yr&&vr?null:e;default:return null}}function fm(e,t){if(ea)return e==="compositionend"||!_i&&gr(e,t)?(e=rr(),$u=Oi=It=null,ea=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:l,offset:t-e};e=a}e:{for(;l;){if(l.nextSibling){l=l.nextSibling;break e}l=l.parentNode}l=void 0}l=Or(l)}}function Nr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Nr(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function zr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Ju(e.document);t instanceof e.HTMLIFrameElement;){try{var l=typeof t.contentWindow.location.href=="string"}catch{l=!1}if(l)e=t.contentWindow;else break;t=Ju(e.document)}return t}function Ui(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var vm=Ct&&"documentMode"in document&&11>=document.documentMode,ta=null,Hi=null,Ka=null,Bi=!1;function Dr(e,t,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;Bi||ta==null||ta!==Ju(a)||(a=ta,"selectionStart"in a&&Ui(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Ka&&Va(Ka,a)||(Ka=a,a=Yn(Hi,"onSelect"),0>=c,u-=c,Ht=1<<32-lt(t)+u|l<n?n:8;var c=_.T,f={};_.T=f,xc(e,!1,t,l);try{var m=u(),T=_.S;if(T!==null&&T(f,m),m!==null&&typeof m=="object"&&typeof m.then=="function"){var D=Rm(m,a);cu(e,t,D,ft(e))}else cu(e,t,a,ft(e))}catch(C){cu(e,t,{then:function(){},status:"rejected",reason:C},ft())}finally{X.p=n,_.T=c}}function Dm(){}function pc(e,t,l,a){if(e.tag!==5)throw Error(r(476));var u=_s(e).queue;Ds(e,u,t,P,l===null?Dm:function(){return js(e),l(a)})}function _s(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:P,baseState:P,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Lt,lastRenderedState:P},next:null};var l={};return t.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Lt,lastRenderedState:l},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function js(e){var t=_s(e).next.queue;cu(e,t,{},ft())}function Sc(){return Ve(Ru)}function Cs(){return je().memoizedState}function Us(){return je().memoizedState}function _m(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var l=ft();e=ll(l);var a=al(t,e,l);a!==null&&(rt(a,t,l),tu(a,t,l)),t={cache:Wi()},e.payload=t;return}t=t.return}}function jm(e,t,l){var a=ft();l={lane:a,revertLane:0,action:l,hasEagerState:!1,eagerState:null,next:null},Sn(e)?Bs(t,l):(l=Yi(e,t,l,a),l!==null&&(rt(l,e,a),qs(l,t,a)))}function Hs(e,t,l){var a=ft();cu(e,t,l,a)}function cu(e,t,l,a){var u={lane:a,revertLane:0,action:l,hasEagerState:!1,eagerState:null,next:null};if(Sn(e))Bs(t,u);else{var n=e.alternate;if(e.lanes===0&&(n===null||n.lanes===0)&&(n=t.lastRenderedReducer,n!==null))try{var c=t.lastRenderedState,f=n(c,l);if(u.hasEagerState=!0,u.eagerState=f,at(f,c))return ln(e,t,u,0),pe===null&&tn(),!1}catch{}finally{}if(l=Yi(e,t,u,a),l!==null)return rt(l,e,a),qs(l,t,a),!0}return!1}function xc(e,t,l,a){if(a={lane:2,revertLane:Ic(),action:a,hasEagerState:!1,eagerState:null,next:null},Sn(e)){if(t)throw Error(r(479))}else t=Yi(e,l,a,2),t!==null&&rt(t,e,2)}function Sn(e){var t=e.alternate;return e===ae||t!==null&&t===ae}function Bs(e,t){oa=mn=!0;var l=e.pending;l===null?t.next=t:(t.next=l.next,l.next=t),e.pending=t}function qs(e,t,l){if((l&4194048)!==0){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,Vf(e,l)}}var xn={readContext:Ve,use:vn,useCallback:ze,useContext:ze,useEffect:ze,useImperativeHandle:ze,useLayoutEffect:ze,useInsertionEffect:ze,useMemo:ze,useReducer:ze,useRef:ze,useState:ze,useDebugValue:ze,useDeferredValue:ze,useTransition:ze,useSyncExternalStore:ze,useId:ze,useHostTransitionStatus:ze,useFormState:ze,useActionState:ze,useOptimistic:ze,useMemoCache:ze,useCacheRefresh:ze},ws={readContext:Ve,use:vn,useCallback:function(e,t){return Fe().memoizedState=[e,t===void 0?null:t],e},useContext:Ve,useEffect:xs,useImperativeHandle:function(e,t,l){l=l!=null?l.concat([e]):null,pn(4194308,4,Rs.bind(null,t,e),l)},useLayoutEffect:function(e,t){return pn(4194308,4,e,t)},useInsertionEffect:function(e,t){pn(4,2,e,t)},useMemo:function(e,t){var l=Fe();t=t===void 0?null:t;var a=e();if(ql){Ft(!0);try{e()}finally{Ft(!1)}}return l.memoizedState=[a,t],a},useReducer:function(e,t,l){var a=Fe();if(l!==void 0){var u=l(t);if(ql){Ft(!0);try{l(t)}finally{Ft(!1)}}}else u=t;return a.memoizedState=a.baseState=u,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:u},a.queue=e,e=e.dispatch=jm.bind(null,ae,e),[a.memoizedState,e]},useRef:function(e){var t=Fe();return e={current:e},t.memoizedState=e},useState:function(e){e=yc(e);var t=e.queue,l=Hs.bind(null,ae,t);return t.dispatch=l,[e.memoizedState,l]},useDebugValue:gc,useDeferredValue:function(e,t){var l=Fe();return bc(l,e,t)},useTransition:function(){var e=yc(!1);return e=Ds.bind(null,ae,e.queue,!0,!1),Fe().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,l){var a=ae,u=Fe();if(se){if(l===void 0)throw Error(r(407));l=l()}else{if(l=t(),pe===null)throw Error(r(349));(ce&124)!==0||ns(a,t,l)}u.memoizedState=l;var n={value:l,getSnapshot:t};return u.queue=n,xs(cs.bind(null,a,n,e),[e]),a.flags|=2048,ha(9,bn(),is.bind(null,a,n,l,t),null),l},useId:function(){var e=Fe(),t=pe.identifierPrefix;if(se){var l=Bt,a=Ht;l=(a&~(1<<32-lt(a)-1)).toString(32)+l,t="«"+t+"R"+l,l=yn++,0F?(we=k,k=null):we=k.sibling;var re=A(S,k,E[F],j);if(re===null){k===null&&(k=we);break}e&&k&&re.alternate===null&&t(S,k),b=n(re,b,F),ue===null?K=re:ue.sibling=re,ue=re,k=we}if(F===E.length)return l(S,k),se&&_l(S,F),K;if(k===null){for(;FF?(we=k,k=null):we=k.sibling;var Sl=A(S,k,re.value,j);if(Sl===null){k===null&&(k=we);break}e&&k&&Sl.alternate===null&&t(S,k),b=n(Sl,b,F),ue===null?K=Sl:ue.sibling=Sl,ue=Sl,k=we}if(re.done)return l(S,k),se&&_l(S,F),K;if(k===null){for(;!re.done;F++,re=E.next())re=C(S,re.value,j),re!==null&&(b=n(re,b,F),ue===null?K=re:ue.sibling=re,ue=re);return se&&_l(S,F),K}for(k=a(k);!re.done;F++,re=E.next())re=R(k,S,F,re.value,j),re!==null&&(e&&re.alternate!==null&&k.delete(re.key===null?F:re.key),b=n(re,b,F),ue===null?K=re:ue.sibling=re,ue=re);return e&&k.forEach(function(Uy){return t(S,Uy)}),se&&_l(S,F),K}function ve(S,b,E,j){if(typeof E=="object"&&E!==null&&E.type===U&&E.key===null&&(E=E.props.children),typeof E=="object"&&E!==null){switch(E.$$typeof){case M:e:{for(var K=E.key;b!==null;){if(b.key===K){if(K=E.type,K===U){if(b.tag===7){l(S,b.sibling),j=u(b,E.props.children),j.return=S,S=j;break e}}else if(b.elementType===K||typeof K=="object"&&K!==null&&K.$$typeof===Ne&&Ys(K)===b.type){l(S,b.sibling),j=u(b,E.props),ru(j,E),j.return=S,S=j;break e}l(S,b);break}else t(S,b);b=b.sibling}E.type===U?(j=zl(E.props.children,S.mode,j,E.key),j.return=S,S=j):(j=un(E.type,E.key,E.props,null,S.mode,j),ru(j,E),j.return=S,S=j)}return c(S);case L:e:{for(K=E.key;b!==null;){if(b.key===K)if(b.tag===4&&b.stateNode.containerInfo===E.containerInfo&&b.stateNode.implementation===E.implementation){l(S,b.sibling),j=u(b,E.children||[]),j.return=S,S=j;break e}else{l(S,b);break}else t(S,b);b=b.sibling}j=Qi(E,S.mode,j),j.return=S,S=j}return c(S);case Ne:return K=E._init,E=K(E._payload),ve(S,b,E,j)}if(Qe(E))return I(S,b,E,j);if(Xe(E)){if(K=Xe(E),typeof K!="function")throw Error(r(150));return E=K.call(E),W(S,b,E,j)}if(typeof E.then=="function")return ve(S,b,En(E),j);if(E.$$typeof===G)return ve(S,b,rn(S,E),j);Tn(S,E)}return typeof E=="string"&&E!==""||typeof E=="number"||typeof E=="bigint"?(E=""+E,b!==null&&b.tag===6?(l(S,b.sibling),j=u(b,E),j.return=S,S=j):(l(S,b),j=Xi(E,S.mode,j),j.return=S,S=j),c(S)):l(S,b)}return function(S,b,E,j){try{fu=0;var K=ve(S,b,E,j);return ma=null,K}catch(k){if(k===Ia||k===on)throw k;var ue=ut(29,k,null,S.mode);return ue.lanes=j,ue.return=S,ue}finally{}}}var ya=Gs(!0),Xs=Gs(!1),gt=H(null),Rt=null;function nl(e){var t=e.alternate;Y(Ue,Ue.current&1),Y(gt,e),Rt===null&&(t===null||sa.current!==null||t.memoizedState!==null)&&(Rt=e)}function Qs(e){if(e.tag===22){if(Y(Ue,Ue.current),Y(gt,e),Rt===null){var t=e.alternate;t!==null&&t.memoizedState!==null&&(Rt=e)}}else il()}function il(){Y(Ue,Ue.current),Y(gt,gt.current)}function Yt(e){Z(gt),Rt===e&&(Rt=null),Z(Ue)}var Ue=H(0);function An(e){for(var t=e;t!==null;){if(t.tag===13){var l=t.memoizedState;if(l!==null&&(l=l.dehydrated,l===null||l.data==="$?"||df(l)))return t}else if(t.tag===19&&t.memoizedProps.revealOrder!==void 0){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function Ec(e,t,l,a){t=e.memoizedState,l=l(a,t),l=l==null?t:N({},t,l),e.memoizedState=l,e.lanes===0&&(e.updateQueue.baseState=l)}var Tc={enqueueSetState:function(e,t,l){e=e._reactInternals;var a=ft(),u=ll(a);u.payload=t,l!=null&&(u.callback=l),t=al(e,u,a),t!==null&&(rt(t,e,a),tu(t,e,a))},enqueueReplaceState:function(e,t,l){e=e._reactInternals;var a=ft(),u=ll(a);u.tag=1,u.payload=t,l!=null&&(u.callback=l),t=al(e,u,a),t!==null&&(rt(t,e,a),tu(t,e,a))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var l=ft(),a=ll(l);a.tag=2,t!=null&&(a.callback=t),t=al(e,a,l),t!==null&&(rt(t,e,l),tu(t,e,l))}};function Zs(e,t,l,a,u,n,c){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(a,n,c):t.prototype&&t.prototype.isPureReactComponent?!Va(l,a)||!Va(u,n):!0}function Vs(e,t,l,a){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(l,a),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(l,a),t.state!==e&&Tc.enqueueReplaceState(t,t.state,null)}function wl(e,t){var l=t;if("ref"in t){l={};for(var a in t)a!=="ref"&&(l[a]=t[a])}if(e=e.defaultProps){l===t&&(l=N({},l));for(var u in e)l[u]===void 0&&(l[u]=e[u])}return l}var Rn=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)};function Ks(e){Rn(e)}function Js(e){console.error(e)}function ks(e){Rn(e)}function On(e,t){try{var l=e.onUncaughtError;l(t.value,{componentStack:t.stack})}catch(a){setTimeout(function(){throw a})}}function $s(e,t,l){try{var a=e.onCaughtError;a(l.value,{componentStack:l.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(u){setTimeout(function(){throw u})}}function Ac(e,t,l){return l=ll(l),l.tag=3,l.payload={element:null},l.callback=function(){On(e,t)},l}function Ws(e){return e=ll(e),e.tag=3,e}function Fs(e,t,l,a){var u=l.type.getDerivedStateFromError;if(typeof u=="function"){var n=a.value;e.payload=function(){return u(n)},e.callback=function(){$s(t,l,a)}}var c=l.stateNode;c!==null&&typeof c.componentDidCatch=="function"&&(e.callback=function(){$s(t,l,a),typeof u!="function"&&(dl===null?dl=new Set([this]):dl.add(this));var f=a.stack;this.componentDidCatch(a.value,{componentStack:f!==null?f:""})})}function Um(e,t,l,a,u){if(l.flags|=32768,a!==null&&typeof a=="object"&&typeof a.then=="function"){if(t=l.alternate,t!==null&&Wa(t,l,u,!0),l=gt.current,l!==null){switch(l.tag){case 13:return Rt===null?kc():l.alternate===null&&Me===0&&(Me=3),l.flags&=-257,l.flags|=65536,l.lanes=u,a===Ii?l.flags|=16384:(t=l.updateQueue,t===null?l.updateQueue=new Set([a]):t.add(a),Wc(e,a,u)),!1;case 22:return l.flags|=65536,a===Ii?l.flags|=16384:(t=l.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([a])},l.updateQueue=t):(l=t.retryQueue,l===null?t.retryQueue=new Set([a]):l.add(a)),Wc(e,a,u)),!1}throw Error(r(435,l.tag))}return Wc(e,a,u),kc(),!1}if(se)return t=gt.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=u,a!==Ki&&(e=Error(r(422),{cause:a}),$a(ht(e,l)))):(a!==Ki&&(t=Error(r(423),{cause:a}),$a(ht(t,l))),e=e.current.alternate,e.flags|=65536,u&=-u,e.lanes|=u,a=ht(a,l),u=Ac(e.stateNode,a,u),lc(e,u),Me!==4&&(Me=2)),!1;var n=Error(r(520),{cause:a});if(n=ht(n,l),vu===null?vu=[n]:vu.push(n),Me!==4&&(Me=2),t===null)return!0;a=ht(a,l),l=t;do{switch(l.tag){case 3:return l.flags|=65536,e=u&-u,l.lanes|=e,e=Ac(l.stateNode,a,e),lc(l,e),!1;case 1:if(t=l.type,n=l.stateNode,(l.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||n!==null&&typeof n.componentDidCatch=="function"&&(dl===null||!dl.has(n))))return l.flags|=65536,u&=-u,l.lanes|=u,u=Ws(u),Fs(u,e,l,a),lc(l,u),!1}l=l.return}while(l!==null);return!1}var Ps=Error(r(461)),Be=!1;function Le(e,t,l,a){t.child=e===null?Xs(t,null,l,a):ya(t,e.child,l,a)}function Is(e,t,l,a,u){l=l.render;var n=t.ref;if("ref"in a){var c={};for(var f in a)f!=="ref"&&(c[f]=a[f])}else c=a;return Hl(t),a=cc(e,t,l,c,n,u),f=fc(),e!==null&&!Be?(rc(e,t,u),Gt(e,t,u)):(se&&f&&Zi(t),t.flags|=1,Le(e,t,a,u),t.child)}function eo(e,t,l,a,u){if(e===null){var n=l.type;return typeof n=="function"&&!Gi(n)&&n.defaultProps===void 0&&l.compare===null?(t.tag=15,t.type=n,to(e,t,n,a,u)):(e=un(l.type,null,a,t,t.mode,u),e.ref=t.ref,e.return=t,t.child=e)}if(n=e.child,!jc(e,u)){var c=n.memoizedProps;if(l=l.compare,l=l!==null?l:Va,l(c,a)&&e.ref===t.ref)return Gt(e,t,u)}return t.flags|=1,e=Ut(n,a),e.ref=t.ref,e.return=t,t.child=e}function to(e,t,l,a,u){if(e!==null){var n=e.memoizedProps;if(Va(n,a)&&e.ref===t.ref)if(Be=!1,t.pendingProps=a=n,jc(e,u))(e.flags&131072)!==0&&(Be=!0);else return t.lanes=e.lanes,Gt(e,t,u)}return Rc(e,t,l,a,u)}function lo(e,t,l){var a=t.pendingProps,u=a.children,n=e!==null?e.memoizedState:null;if(a.mode==="hidden"){if((t.flags&128)!==0){if(a=n!==null?n.baseLanes|l:l,e!==null){for(u=t.child=e.child,n=0;u!==null;)n=n|u.lanes|u.childLanes,u=u.sibling;t.childLanes=n&~a}else t.childLanes=0,t.child=null;return ao(e,t,a,l)}if((l&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&sn(t,n!==null?n.cachePool:null),n!==null?ts(t,n):uc(),Qs(t);else return t.lanes=t.childLanes=536870912,ao(e,t,n!==null?n.baseLanes|l:l,l)}else n!==null?(sn(t,n.cachePool),ts(t,n),il(),t.memoizedState=null):(e!==null&&sn(t,null),uc(),il());return Le(e,t,u,l),t.child}function ao(e,t,l,a){var u=Pi();return u=u===null?null:{parent:Ce._currentValue,pool:u},t.memoizedState={baseLanes:l,cachePool:u},e!==null&&sn(t,null),uc(),Qs(t),e!==null&&Wa(e,t,a,!0),null}function Mn(e,t){var l=t.ref;if(l===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof l!="function"&&typeof l!="object")throw Error(r(284));(e===null||e.ref!==l)&&(t.flags|=4194816)}}function Rc(e,t,l,a,u){return Hl(t),l=cc(e,t,l,a,void 0,u),a=fc(),e!==null&&!Be?(rc(e,t,u),Gt(e,t,u)):(se&&a&&Zi(t),t.flags|=1,Le(e,t,l,u),t.child)}function uo(e,t,l,a,u,n){return Hl(t),t.updateQueue=null,l=as(t,a,l,u),ls(e),a=fc(),e!==null&&!Be?(rc(e,t,n),Gt(e,t,n)):(se&&a&&Zi(t),t.flags|=1,Le(e,t,l,n),t.child)}function no(e,t,l,a,u){if(Hl(t),t.stateNode===null){var n=na,c=l.contextType;typeof c=="object"&&c!==null&&(n=Ve(c)),n=new l(a,n),t.memoizedState=n.state!==null&&n.state!==void 0?n.state:null,n.updater=Tc,t.stateNode=n,n._reactInternals=t,n=t.stateNode,n.props=a,n.state=t.memoizedState,n.refs={},ec(t),c=l.contextType,n.context=typeof c=="object"&&c!==null?Ve(c):na,n.state=t.memoizedState,c=l.getDerivedStateFromProps,typeof c=="function"&&(Ec(t,l,c,a),n.state=t.memoizedState),typeof l.getDerivedStateFromProps=="function"||typeof n.getSnapshotBeforeUpdate=="function"||typeof n.UNSAFE_componentWillMount!="function"&&typeof n.componentWillMount!="function"||(c=n.state,typeof n.componentWillMount=="function"&&n.componentWillMount(),typeof n.UNSAFE_componentWillMount=="function"&&n.UNSAFE_componentWillMount(),c!==n.state&&Tc.enqueueReplaceState(n,n.state,null),au(t,a,n,u),lu(),n.state=t.memoizedState),typeof n.componentDidMount=="function"&&(t.flags|=4194308),a=!0}else if(e===null){n=t.stateNode;var f=t.memoizedProps,m=wl(l,f);n.props=m;var T=n.context,D=l.contextType;c=na,typeof D=="object"&&D!==null&&(c=Ve(D));var C=l.getDerivedStateFromProps;D=typeof C=="function"||typeof n.getSnapshotBeforeUpdate=="function",f=t.pendingProps!==f,D||typeof n.UNSAFE_componentWillReceiveProps!="function"&&typeof n.componentWillReceiveProps!="function"||(f||T!==c)&&Vs(t,n,a,c),tl=!1;var A=t.memoizedState;n.state=A,au(t,a,n,u),lu(),T=t.memoizedState,f||A!==T||tl?(typeof C=="function"&&(Ec(t,l,C,a),T=t.memoizedState),(m=tl||Zs(t,l,m,a,A,T,c))?(D||typeof n.UNSAFE_componentWillMount!="function"&&typeof n.componentWillMount!="function"||(typeof n.componentWillMount=="function"&&n.componentWillMount(),typeof n.UNSAFE_componentWillMount=="function"&&n.UNSAFE_componentWillMount()),typeof n.componentDidMount=="function"&&(t.flags|=4194308)):(typeof n.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=a,t.memoizedState=T),n.props=a,n.state=T,n.context=c,a=m):(typeof n.componentDidMount=="function"&&(t.flags|=4194308),a=!1)}else{n=t.stateNode,tc(e,t),c=t.memoizedProps,D=wl(l,c),n.props=D,C=t.pendingProps,A=n.context,T=l.contextType,m=na,typeof T=="object"&&T!==null&&(m=Ve(T)),f=l.getDerivedStateFromProps,(T=typeof f=="function"||typeof n.getSnapshotBeforeUpdate=="function")||typeof n.UNSAFE_componentWillReceiveProps!="function"&&typeof n.componentWillReceiveProps!="function"||(c!==C||A!==m)&&Vs(t,n,a,m),tl=!1,A=t.memoizedState,n.state=A,au(t,a,n,u),lu();var R=t.memoizedState;c!==C||A!==R||tl||e!==null&&e.dependencies!==null&&fn(e.dependencies)?(typeof f=="function"&&(Ec(t,l,f,a),R=t.memoizedState),(D=tl||Zs(t,l,D,a,A,R,m)||e!==null&&e.dependencies!==null&&fn(e.dependencies))?(T||typeof n.UNSAFE_componentWillUpdate!="function"&&typeof n.componentWillUpdate!="function"||(typeof n.componentWillUpdate=="function"&&n.componentWillUpdate(a,R,m),typeof n.UNSAFE_componentWillUpdate=="function"&&n.UNSAFE_componentWillUpdate(a,R,m)),typeof n.componentDidUpdate=="function"&&(t.flags|=4),typeof n.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof n.componentDidUpdate!="function"||c===e.memoizedProps&&A===e.memoizedState||(t.flags|=4),typeof n.getSnapshotBeforeUpdate!="function"||c===e.memoizedProps&&A===e.memoizedState||(t.flags|=1024),t.memoizedProps=a,t.memoizedState=R),n.props=a,n.state=R,n.context=m,a=D):(typeof n.componentDidUpdate!="function"||c===e.memoizedProps&&A===e.memoizedState||(t.flags|=4),typeof n.getSnapshotBeforeUpdate!="function"||c===e.memoizedProps&&A===e.memoizedState||(t.flags|=1024),a=!1)}return n=a,Mn(e,t),a=(t.flags&128)!==0,n||a?(n=t.stateNode,l=a&&typeof l.getDerivedStateFromError!="function"?null:n.render(),t.flags|=1,e!==null&&a?(t.child=ya(t,e.child,null,u),t.child=ya(t,null,l,u)):Le(e,t,l,u),t.memoizedState=n.state,e=t.child):e=Gt(e,t,u),e}function io(e,t,l,a){return ka(),t.flags|=256,Le(e,t,l,a),t.child}var Oc={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Mc(e){return{baseLanes:e,cachePool:Jr()}}function Nc(e,t,l){return e=e!==null?e.childLanes&~l:0,t&&(e|=bt),e}function co(e,t,l){var a=t.pendingProps,u=!1,n=(t.flags&128)!==0,c;if((c=n)||(c=e!==null&&e.memoizedState===null?!1:(Ue.current&2)!==0),c&&(u=!0,t.flags&=-129),c=(t.flags&32)!==0,t.flags&=-33,e===null){if(se){if(u?nl(t):il(),se){var f=Oe,m;if(m=f){e:{for(m=f,f=At;m.nodeType!==8;){if(!f){f=null;break e}if(m=Et(m.nextSibling),m===null){f=null;break e}}f=m}f!==null?(t.memoizedState={dehydrated:f,treeContext:Dl!==null?{id:Ht,overflow:Bt}:null,retryLane:536870912,hydrationErrors:null},m=ut(18,null,null,0),m.stateNode=f,m.return=t,t.child=m,ke=t,Oe=null,m=!0):m=!1}m||Cl(t)}if(f=t.memoizedState,f!==null&&(f=f.dehydrated,f!==null))return df(f)?t.lanes=32:t.lanes=536870912,null;Yt(t)}return f=a.children,a=a.fallback,u?(il(),u=t.mode,f=Nn({mode:"hidden",children:f},u),a=zl(a,u,l,null),f.return=t,a.return=t,f.sibling=a,t.child=f,u=t.child,u.memoizedState=Mc(l),u.childLanes=Nc(e,c,l),t.memoizedState=Oc,a):(nl(t),zc(t,f))}if(m=e.memoizedState,m!==null&&(f=m.dehydrated,f!==null)){if(n)t.flags&256?(nl(t),t.flags&=-257,t=Dc(e,t,l)):t.memoizedState!==null?(il(),t.child=e.child,t.flags|=128,t=null):(il(),u=a.fallback,f=t.mode,a=Nn({mode:"visible",children:a.children},f),u=zl(u,f,l,null),u.flags|=2,a.return=t,u.return=t,a.sibling=u,t.child=a,ya(t,e.child,null,l),a=t.child,a.memoizedState=Mc(l),a.childLanes=Nc(e,c,l),t.memoizedState=Oc,t=u);else if(nl(t),df(f)){if(c=f.nextSibling&&f.nextSibling.dataset,c)var T=c.dgst;c=T,a=Error(r(419)),a.stack="",a.digest=c,$a({value:a,source:null,stack:null}),t=Dc(e,t,l)}else if(Be||Wa(e,t,l,!1),c=(l&e.childLanes)!==0,Be||c){if(c=pe,c!==null&&(a=l&-l,a=(a&42)!==0?1:di(a),a=(a&(c.suspendedLanes|l))!==0?0:a,a!==0&&a!==m.retryLane))throw m.retryLane=a,ua(e,a),rt(c,e,a),Ps;f.data==="$?"||kc(),t=Dc(e,t,l)}else f.data==="$?"?(t.flags|=192,t.child=e.child,t=null):(e=m.treeContext,Oe=Et(f.nextSibling),ke=t,se=!0,jl=null,At=!1,e!==null&&(yt[vt++]=Ht,yt[vt++]=Bt,yt[vt++]=Dl,Ht=e.id,Bt=e.overflow,Dl=t),t=zc(t,a.children),t.flags|=4096);return t}return u?(il(),u=a.fallback,f=t.mode,m=e.child,T=m.sibling,a=Ut(m,{mode:"hidden",children:a.children}),a.subtreeFlags=m.subtreeFlags&65011712,T!==null?u=Ut(T,u):(u=zl(u,f,l,null),u.flags|=2),u.return=t,a.return=t,a.sibling=u,t.child=a,a=u,u=t.child,f=e.child.memoizedState,f===null?f=Mc(l):(m=f.cachePool,m!==null?(T=Ce._currentValue,m=m.parent!==T?{parent:T,pool:T}:m):m=Jr(),f={baseLanes:f.baseLanes|l,cachePool:m}),u.memoizedState=f,u.childLanes=Nc(e,c,l),t.memoizedState=Oc,a):(nl(t),l=e.child,e=l.sibling,l=Ut(l,{mode:"visible",children:a.children}),l.return=t,l.sibling=null,e!==null&&(c=t.deletions,c===null?(t.deletions=[e],t.flags|=16):c.push(e)),t.child=l,t.memoizedState=null,l)}function zc(e,t){return t=Nn({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function Nn(e,t){return e=ut(22,e,null,t),e.lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function Dc(e,t,l){return ya(t,e.child,null,l),e=zc(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function fo(e,t,l){e.lanes|=t;var a=e.alternate;a!==null&&(a.lanes|=t),ki(e.return,t,l)}function _c(e,t,l,a,u){var n=e.memoizedState;n===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:a,tail:l,tailMode:u}:(n.isBackwards=t,n.rendering=null,n.renderingStartTime=0,n.last=a,n.tail=l,n.tailMode=u)}function ro(e,t,l){var a=t.pendingProps,u=a.revealOrder,n=a.tail;if(Le(e,t,a.children,l),a=Ue.current,(a&2)!==0)a=a&1|2,t.flags|=128;else{if(e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&fo(e,l,t);else if(e.tag===19)fo(e,l,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}a&=1}switch(Y(Ue,a),u){case"forwards":for(l=t.child,u=null;l!==null;)e=l.alternate,e!==null&&An(e)===null&&(u=l),l=l.sibling;l=u,l===null?(u=t.child,t.child=null):(u=l.sibling,l.sibling=null),_c(t,!1,u,l,n);break;case"backwards":for(l=null,u=t.child,t.child=null;u!==null;){if(e=u.alternate,e!==null&&An(e)===null){t.child=u;break}e=u.sibling,u.sibling=l,l=u,u=e}_c(t,!0,l,null,n);break;case"together":_c(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function Gt(e,t,l){if(e!==null&&(t.dependencies=e.dependencies),ol|=t.lanes,(l&t.childLanes)===0)if(e!==null){if(Wa(e,t,l,!1),(l&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(r(153));if(t.child!==null){for(e=t.child,l=Ut(e,e.pendingProps),t.child=l,l.return=t;e.sibling!==null;)e=e.sibling,l=l.sibling=Ut(e,e.pendingProps),l.return=t;l.sibling=null}return t.child}function jc(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&fn(e)))}function Hm(e,t,l){switch(t.tag){case 3:xe(t,t.stateNode.containerInfo),el(t,Ce,e.memoizedState.cache),ka();break;case 27:case 5:ci(t);break;case 4:xe(t,t.stateNode.containerInfo);break;case 10:el(t,t.type,t.memoizedProps.value);break;case 13:var a=t.memoizedState;if(a!==null)return a.dehydrated!==null?(nl(t),t.flags|=128,null):(l&t.child.childLanes)!==0?co(e,t,l):(nl(t),e=Gt(e,t,l),e!==null?e.sibling:null);nl(t);break;case 19:var u=(e.flags&128)!==0;if(a=(l&t.childLanes)!==0,a||(Wa(e,t,l,!1),a=(l&t.childLanes)!==0),u){if(a)return ro(e,t,l);t.flags|=128}if(u=t.memoizedState,u!==null&&(u.rendering=null,u.tail=null,u.lastEffect=null),Y(Ue,Ue.current),a)break;return null;case 22:case 23:return t.lanes=0,lo(e,t,l);case 24:el(t,Ce,e.memoizedState.cache)}return Gt(e,t,l)}function so(e,t,l){if(e!==null)if(e.memoizedProps!==t.pendingProps)Be=!0;else{if(!jc(e,l)&&(t.flags&128)===0)return Be=!1,Hm(e,t,l);Be=(e.flags&131072)!==0}else Be=!1,se&&(t.flags&1048576)!==0&&Yr(t,cn,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var a=t.elementType,u=a._init;if(a=u(a._payload),t.type=a,typeof a=="function")Gi(a)?(e=wl(a,e),t.tag=1,t=no(null,t,a,e,l)):(t.tag=0,t=Rc(null,t,a,e,l));else{if(a!=null){if(u=a.$$typeof,u===ee){t.tag=11,t=Is(null,t,a,e,l);break e}else if(u===Te){t.tag=14,t=eo(null,t,a,e,l);break e}}throw t=Tl(a)||a,Error(r(306,t,""))}}return t;case 0:return Rc(e,t,t.type,t.pendingProps,l);case 1:return a=t.type,u=wl(a,t.pendingProps),no(e,t,a,u,l);case 3:e:{if(xe(t,t.stateNode.containerInfo),e===null)throw Error(r(387));a=t.pendingProps;var n=t.memoizedState;u=n.element,tc(e,t),au(t,a,null,l);var c=t.memoizedState;if(a=c.cache,el(t,Ce,a),a!==n.cache&&$i(t,[Ce],l,!0),lu(),a=c.element,n.isDehydrated)if(n={element:a,isDehydrated:!1,cache:c.cache},t.updateQueue.baseState=n,t.memoizedState=n,t.flags&256){t=io(e,t,a,l);break e}else if(a!==u){u=ht(Error(r(424)),t),$a(u),t=io(e,t,a,l);break e}else{switch(e=t.stateNode.containerInfo,e.nodeType){case 9:e=e.body;break;default:e=e.nodeName==="HTML"?e.ownerDocument.body:e}for(Oe=Et(e.firstChild),ke=t,se=!0,jl=null,At=!0,l=Xs(t,null,a,l),t.child=l;l;)l.flags=l.flags&-3|4096,l=l.sibling}else{if(ka(),a===u){t=Gt(e,t,l);break e}Le(e,t,a,l)}t=t.child}return t;case 26:return Mn(e,t),e===null?(l=yd(t.type,null,t.pendingProps,null))?t.memoizedState=l:se||(l=t.type,e=t.pendingProps,a=Xn(te.current).createElement(l),a[Ze]=t,a[$e]=e,Ge(a,l,e),He(a),t.stateNode=a):t.memoizedState=yd(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return ci(t),e===null&&se&&(a=t.stateNode=dd(t.type,t.pendingProps,te.current),ke=t,At=!0,u=Oe,yl(t.type)?(hf=u,Oe=Et(a.firstChild)):Oe=u),Le(e,t,t.pendingProps.children,l),Mn(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&se&&((u=a=Oe)&&(a=ry(a,t.type,t.pendingProps,At),a!==null?(t.stateNode=a,ke=t,Oe=Et(a.firstChild),At=!1,u=!0):u=!1),u||Cl(t)),ci(t),u=t.type,n=t.pendingProps,c=e!==null?e.memoizedProps:null,a=n.children,rf(u,n)?a=null:c!==null&&rf(u,c)&&(t.flags|=32),t.memoizedState!==null&&(u=cc(e,t,Mm,null,null,l),Ru._currentValue=u),Mn(e,t),Le(e,t,a,l),t.child;case 6:return e===null&&se&&((e=l=Oe)&&(l=sy(l,t.pendingProps,At),l!==null?(t.stateNode=l,ke=t,Oe=null,e=!0):e=!1),e||Cl(t)),null;case 13:return co(e,t,l);case 4:return xe(t,t.stateNode.containerInfo),a=t.pendingProps,e===null?t.child=ya(t,null,a,l):Le(e,t,a,l),t.child;case 11:return Is(e,t,t.type,t.pendingProps,l);case 7:return Le(e,t,t.pendingProps,l),t.child;case 8:return Le(e,t,t.pendingProps.children,l),t.child;case 12:return Le(e,t,t.pendingProps.children,l),t.child;case 10:return a=t.pendingProps,el(t,t.type,a.value),Le(e,t,a.children,l),t.child;case 9:return u=t.type._context,a=t.pendingProps.children,Hl(t),u=Ve(u),a=a(u),t.flags|=1,Le(e,t,a,l),t.child;case 14:return eo(e,t,t.type,t.pendingProps,l);case 15:return to(e,t,t.type,t.pendingProps,l);case 19:return ro(e,t,l);case 31:return a=t.pendingProps,l=t.mode,a={mode:a.mode,children:a.children},e===null?(l=Nn(a,l),l.ref=t.ref,t.child=l,l.return=t,t=l):(l=Ut(e.child,a),l.ref=t.ref,t.child=l,l.return=t,t=l),t;case 22:return lo(e,t,l);case 24:return Hl(t),a=Ve(Ce),e===null?(u=Pi(),u===null&&(u=pe,n=Wi(),u.pooledCache=n,n.refCount++,n!==null&&(u.pooledCacheLanes|=l),u=n),t.memoizedState={parent:a,cache:u},ec(t),el(t,Ce,u)):((e.lanes&l)!==0&&(tc(e,t),au(t,null,null,l),lu()),u=e.memoizedState,n=t.memoizedState,u.parent!==a?(u={parent:a,cache:a},t.memoizedState=u,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=u),el(t,Ce,a)):(a=n.cache,el(t,Ce,a),a!==u.cache&&$i(t,[Ce],l,!0))),Le(e,t,t.pendingProps.children,l),t.child;case 29:throw t.pendingProps}throw Error(r(156,t.tag))}function Xt(e){e.flags|=4}function oo(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!Sd(t)){if(t=gt.current,t!==null&&((ce&4194048)===ce?Rt!==null:(ce&62914560)!==ce&&(ce&536870912)===0||t!==Rt))throw eu=Ii,kr;e.flags|=8192}}function zn(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?Qf():536870912,e.lanes|=t,pa|=t)}function su(e,t){if(!se)switch(e.tailMode){case"hidden":t=e.tail;for(var l=null;t!==null;)t.alternate!==null&&(l=t),t=t.sibling;l===null?e.tail=null:l.sibling=null;break;case"collapsed":l=e.tail;for(var a=null;l!==null;)l.alternate!==null&&(a=l),l=l.sibling;a===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:a.sibling=null}}function Ae(e){var t=e.alternate!==null&&e.alternate.child===e.child,l=0,a=0;if(t)for(var u=e.child;u!==null;)l|=u.lanes|u.childLanes,a|=u.subtreeFlags&65011712,a|=u.flags&65011712,u.return=e,u=u.sibling;else for(u=e.child;u!==null;)l|=u.lanes|u.childLanes,a|=u.subtreeFlags,a|=u.flags,u.return=e,u=u.sibling;return e.subtreeFlags|=a,e.childLanes=l,t}function Bm(e,t,l){var a=t.pendingProps;switch(Vi(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Ae(t),null;case 1:return Ae(t),null;case 3:return l=t.stateNode,a=null,e!==null&&(a=e.memoizedState.cache),t.memoizedState.cache!==a&&(t.flags|=2048),wt(Ce),Wt(),l.pendingContext&&(l.context=l.pendingContext,l.pendingContext=null),(e===null||e.child===null)&&(Ja(t)?Xt(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,Qr())),Ae(t),null;case 26:return l=t.memoizedState,e===null?(Xt(t),l!==null?(Ae(t),oo(t,l)):(Ae(t),t.flags&=-16777217)):l?l!==e.memoizedState?(Xt(t),Ae(t),oo(t,l)):(Ae(t),t.flags&=-16777217):(e.memoizedProps!==a&&Xt(t),Ae(t),t.flags&=-16777217),null;case 27:Lu(t),l=te.current;var u=t.type;if(e!==null&&t.stateNode!=null)e.memoizedProps!==a&&Xt(t);else{if(!a){if(t.stateNode===null)throw Error(r(166));return Ae(t),null}e=$.current,Ja(t)?Gr(t):(e=dd(u,a,l),t.stateNode=e,Xt(t))}return Ae(t),null;case 5:if(Lu(t),l=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==a&&Xt(t);else{if(!a){if(t.stateNode===null)throw Error(r(166));return Ae(t),null}if(e=$.current,Ja(t))Gr(t);else{switch(u=Xn(te.current),e){case 1:e=u.createElementNS("http://www.w3.org/2000/svg",l);break;case 2:e=u.createElementNS("http://www.w3.org/1998/Math/MathML",l);break;default:switch(l){case"svg":e=u.createElementNS("http://www.w3.org/2000/svg",l);break;case"math":e=u.createElementNS("http://www.w3.org/1998/Math/MathML",l);break;case"script":e=u.createElement("div"),e.innerHTML=" +