Add Gitea OTA firmware update, dual-slot partition layout, and Gitea Actions release workflow
This commit is contained in:
858
components/rc522/src/rc522_picc.c
Normal file
858
components/rc522/src/rc522_picc.c
Normal file
@@ -0,0 +1,858 @@
|
||||
#include <esp_system.h>
|
||||
#include <esp_check.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "rc522_internal.h"
|
||||
#include "rc522_types_internal.h"
|
||||
#include "rc522_helpers_internal.h"
|
||||
#include "rc522_pcd_internal.h"
|
||||
#include "rc522_picc_internal.h"
|
||||
|
||||
RC522_LOG_DEFINE_BASE();
|
||||
|
||||
struct rc522_picc_transaction_context
|
||||
{
|
||||
const rc522_picc_transaction_t *transaction;
|
||||
uint8_t interrupts;
|
||||
bool completed;
|
||||
uint8_t error_reg;
|
||||
};
|
||||
|
||||
esp_err_t rc522_picc_send(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction,
|
||||
rc522_picc_transaction_context_t *out_context)
|
||||
{
|
||||
RC522_CHECK(rc522 == NULL);
|
||||
RC522_CHECK(transaction == NULL);
|
||||
RC522_CHECK_BYTES(&transaction->bytes);
|
||||
RC522_CHECK(
|
||||
transaction->pcd_command != RC522_PCD_TRANSCEIVE_CMD && transaction->pcd_command != RC522_PCD_MF_AUTH_CMD);
|
||||
RC522_CHECK(transaction->expected_interrupts == 0);
|
||||
|
||||
rc522_picc_transaction_context_t context = {
|
||||
.transaction = transaction,
|
||||
};
|
||||
|
||||
// Prepare values for bit framing
|
||||
uint8_t bit_framing = (transaction->rx_align << 4) + transaction->valid_bits;
|
||||
|
||||
if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) {
|
||||
RC522_LOGD("rx_align=%d,tx_last_bits=%d, bit_framing=%d",
|
||||
transaction->rx_align,
|
||||
transaction->valid_bits,
|
||||
bit_framing);
|
||||
|
||||
char debug_buffer[64];
|
||||
rc522_buffer_to_hex_str(transaction->bytes.ptr, transaction->bytes.length, debug_buffer, sizeof(debug_buffer));
|
||||
RC522_LOGD("picc << %s", debug_buffer);
|
||||
}
|
||||
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_stop_active_command(rc522));
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_clear_all_com_interrupts(rc522));
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_flush(rc522));
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_write(rc522, &transaction->bytes));
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_BIT_FRAMING_REG, bit_framing));
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, transaction->pcd_command));
|
||||
|
||||
if (transaction->pcd_command == RC522_PCD_TRANSCEIVE_CMD) {
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_start_data_transmission(rc522));
|
||||
}
|
||||
|
||||
// TAuto flag in TModeReg is set.
|
||||
// This means the timer automatically starts when the PCD stops transmitting.
|
||||
|
||||
const uint32_t deadline = rc522_millis() + 36;
|
||||
|
||||
do {
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_COM_INT_REQ_REG, &context.interrupts));
|
||||
|
||||
if (context.interrupts & transaction->expected_interrupts) {
|
||||
context.completed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Timer interrupt - nothing received
|
||||
if (context.interrupts & RC522_PCD_TIMER_IRQ_BIT) {
|
||||
RC522_LOGD("timer interrupt (irq=0x%02" RC522_X ")", context.interrupts);
|
||||
rc522_pcd_stop_active_command(rc522);
|
||||
return RC522_ERR_RX_TIMER_TIMEOUT;
|
||||
}
|
||||
|
||||
rc522_delay_ms(2);
|
||||
}
|
||||
while (rc522_millis() < deadline);
|
||||
|
||||
// Deadline reached and nothing happened (normal when no card is present).
|
||||
if (!context.completed) {
|
||||
rc522_pcd_stop_active_command(rc522);
|
||||
return RC522_ERR_RX_TIMEOUT;
|
||||
}
|
||||
|
||||
// Stop now if any errors except collisions were detected.
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_ERROR_REG, &context.error_reg));
|
||||
|
||||
if (context.error_reg & RC522_PCD_BUFFER_OVFL_BIT) {
|
||||
return RC522_ERR_PCD_FIFO_BUFFER_OVERFLOW;
|
||||
}
|
||||
else if (context.error_reg & RC522_PCD_PARITY_ERR_BIT) {
|
||||
RC522_LOGD("parity error detected");
|
||||
|
||||
return RC522_ERR_PCD_PARITY_CHECK_FAILED;
|
||||
}
|
||||
else if (context.error_reg & RC522_PCD_PROTOCOL_ERR_BIT) {
|
||||
RC522_LOGD("protocol error detected");
|
||||
|
||||
return RC522_ERR_PCD_PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
if (out_context) {
|
||||
memcpy(out_context, &context, sizeof(context));
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t rc522_picc_receive(const rc522_handle_t rc522, const rc522_picc_transaction_context_t *context,
|
||||
rc522_picc_transaction_result_t *out_result)
|
||||
{
|
||||
RC522_CHECK(rc522 == NULL);
|
||||
RC522_CHECK(context == NULL);
|
||||
RC522_CHECK(context->transaction == NULL);
|
||||
RC522_CHECK(out_result == NULL);
|
||||
RC522_CHECK_BYTES(&context->transaction->bytes);
|
||||
|
||||
uint8_t fifo_level = 0;
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_FIFO_LEVEL_REG, &fifo_level));
|
||||
|
||||
if (fifo_level < 1) {
|
||||
RC522_LOGD("fifo empty (irq=0x%02" RC522_X ")", context->interrupts);
|
||||
|
||||
return RC522_ERR_PCD_FIFO_EMPTY;
|
||||
}
|
||||
|
||||
RC522_CHECK(fifo_level > out_result->bytes.length);
|
||||
|
||||
rc522_picc_transaction_result_t result = {
|
||||
.bytes = {
|
||||
.ptr = out_result->bytes.ptr, // Use buffer provided by caller
|
||||
.length = fifo_level,
|
||||
},
|
||||
};
|
||||
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_read(rc522, &result.bytes));
|
||||
|
||||
if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) {
|
||||
char debug_buffer[64];
|
||||
rc522_buffer_to_hex_str(result.bytes.ptr, result.bytes.length, debug_buffer, sizeof(debug_buffer));
|
||||
RC522_LOGD("picc >> %s", debug_buffer);
|
||||
}
|
||||
|
||||
if (context->transaction->rx_align) {
|
||||
RC522_LOGD("applying mask (rx_align=%d)", context->transaction->rx_align);
|
||||
|
||||
// Apply mask for rx_align..7 of the first byte
|
||||
result.bytes.ptr[0] &= (0xFF << context->transaction->rx_align);
|
||||
}
|
||||
|
||||
// RxLastBits[2:0] indicates the number of valid bits in the last received byte.
|
||||
// If this value is 0, the whole byte is valid.
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_CONTROL_REG, &result.valid_bits));
|
||||
result.valid_bits &= 0x07;
|
||||
|
||||
if (result.valid_bits) {
|
||||
RC522_LOGD("not full byte received, valid_bits=%d", result.valid_bits);
|
||||
}
|
||||
|
||||
if (context->error_reg & RC522_PCD_COLL_ERR_BIT) {
|
||||
return RC522_ERR_COLLISION;
|
||||
}
|
||||
|
||||
// Perform CRC_A validation
|
||||
if (context->transaction->check_crc) {
|
||||
// We need at least the CRC_A value
|
||||
// and all 8 bits of the last byte
|
||||
RC522_CHECK_AND_RETURN(result.bytes.length < 3, ESP_ERR_INVALID_STATE);
|
||||
RC522_CHECK_AND_RETURN(result.valid_bits != 0, ESP_ERR_INVALID_STATE);
|
||||
|
||||
// Verify CRC_A
|
||||
rc522_pcd_crc_t crc = { 0 };
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
|
||||
&(rc522_bytes_t) { .ptr = result.bytes.ptr, .length = result.bytes.length - 2 },
|
||||
&crc));
|
||||
|
||||
if (memcmp(result.bytes.ptr + result.bytes.length - 2, &crc, sizeof(crc)) != 0) {
|
||||
return RC522_ERR_CRC_WRONG;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(out_result, &result, sizeof(result));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t rc522_picc_transceive(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction,
|
||||
rc522_picc_transaction_result_t *out_result)
|
||||
{
|
||||
RC522_CHECK(rc522 == NULL);
|
||||
RC522_CHECK(transaction == NULL);
|
||||
|
||||
rc522_picc_transaction_t transaction_clone = { 0 };
|
||||
memcpy(&transaction_clone, transaction, sizeof(transaction_clone));
|
||||
|
||||
transaction_clone.pcd_command = RC522_PCD_TRANSCEIVE_CMD;
|
||||
transaction_clone.expected_interrupts = RC522_PCD_RX_IRQ_BIT | RC522_PCD_IDLE_IRQ_BIT;
|
||||
|
||||
rc522_picc_transaction_context_t context = { 0 };
|
||||
RC522_RETURN_ON_ERROR_SILENTLY(rc522_picc_send(rc522, &transaction_clone, &context));
|
||||
|
||||
if (out_result) {
|
||||
if (!(context.interrupts & RC522_PCD_RX_IRQ_BIT)) {
|
||||
return RC522_ERR_RX_TIMEOUT;
|
||||
}
|
||||
|
||||
RC522_RETURN_ON_ERROR_SILENTLY(rc522_picc_receive(rc522, &context, out_result));
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
inline static esp_err_t rc522_picc_parse_atqa(uint16_t atqa, rc522_picc_atqa_desc_t *out_atqa)
|
||||
{
|
||||
RC522_CHECK(out_atqa == NULL);
|
||||
|
||||
out_atqa->source = atqa;
|
||||
out_atqa->rfu4 = (atqa >> 12) & 0x0F;
|
||||
out_atqa->prop_coding = (atqa >> 8) & 0x0F;
|
||||
out_atqa->uid_size = (atqa >> 6) & 0x03;
|
||||
out_atqa->rfu1 = (atqa >> 5) & 0x01;
|
||||
out_atqa->anticollision = atqa & 0x1F;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t rc522_picc_reqa_or_wupa(const rc522_handle_t rc522, uint8_t picc_cmd, rc522_picc_atqa_desc_t *out_atqa)
|
||||
{
|
||||
RC522_CHECK(rc522 == NULL);
|
||||
RC522_CHECK(picc_cmd != RC522_PICC_CMD_REQA && picc_cmd != RC522_PICC_CMD_WUPA);
|
||||
RC522_CHECK(out_atqa == NULL);
|
||||
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_COLL_REG, RC522_PCD_VALUES_AFTER_COLL_BIT));
|
||||
|
||||
uint8_t buffer[2] = { 0 };
|
||||
|
||||
rc522_picc_transaction_t transaction = {
|
||||
.bytes = { .ptr = &picc_cmd, .length = 1 },
|
||||
.valid_bits = 7, // REQA and WUPA use short frame format
|
||||
};
|
||||
|
||||
rc522_picc_transaction_result_t transaction_result = {
|
||||
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
|
||||
};
|
||||
|
||||
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &transaction_result);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
// Timeouts are expected if no PICC are in the field, log other errors
|
||||
if (ret != RC522_ERR_RX_TIMER_TIMEOUT && ret != RC522_ERR_RX_TIMEOUT) {
|
||||
RC522_LOGD("non-timeout error: %04" RC522_X, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (transaction_result.bytes.length != 2 || transaction_result.valid_bits != 0) {
|
||||
return RC522_ERR_INVALID_ATQA;
|
||||
}
|
||||
|
||||
uint16_t atqa = (buffer[0] << 8) | buffer[1];
|
||||
RC522_RETURN_ON_ERROR(rc522_picc_parse_atqa(atqa, out_atqa));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
inline esp_err_t rc522_picc_reqa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa)
|
||||
{
|
||||
RC522_CHECK(rc522 == NULL);
|
||||
RC522_CHECK(out_atqa == NULL);
|
||||
|
||||
RC522_LOGD("REQA");
|
||||
return rc522_picc_reqa_or_wupa(rc522, RC522_PICC_CMD_REQA, out_atqa);
|
||||
}
|
||||
|
||||
inline esp_err_t rc522_picc_wupa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa)
|
||||
{
|
||||
RC522_CHECK(rc522 == NULL);
|
||||
RC522_CHECK(out_atqa == NULL);
|
||||
|
||||
RC522_LOGD("WUPA");
|
||||
return rc522_picc_reqa_or_wupa(rc522, RC522_PICC_CMD_WUPA, out_atqa);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve collision and SELECT a PICC
|
||||
*/
|
||||
esp_err_t rc522_picc_select(const rc522_handle_t rc522, rc522_picc_uid_t *out_uid, uint8_t *out_sak, bool skip_anticoll)
|
||||
{
|
||||
RC522_CHECK(rc522 == NULL);
|
||||
RC522_CHECK(skip_anticoll && (out_uid == NULL || out_uid->length < RC522_PICC_UID_SIZE_MIN));
|
||||
|
||||
bool uid_complete;
|
||||
bool select_done;
|
||||
bool use_cascade_tag;
|
||||
uint8_t cascade_level = 1;
|
||||
esp_err_t ret;
|
||||
uint8_t count;
|
||||
uint8_t check_bit;
|
||||
uint8_t index;
|
||||
uint8_t uid_index; // The first index in uid->uidByte[] that is used in the current Cascade Level.
|
||||
int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level.
|
||||
uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A
|
||||
uint8_t buffer_used; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO.
|
||||
uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received.
|
||||
uint8_t tx_last_bits = 0; // Used in BitFramingReg. The number of valid bits in the last transmitted byte.
|
||||
uint8_t *response_buffer;
|
||||
uint8_t response_length;
|
||||
|
||||
rc522_picc_uid_t uid;
|
||||
|
||||
if (skip_anticoll) {
|
||||
memcpy(&uid, out_uid, sizeof(rc522_picc_uid_t));
|
||||
}
|
||||
else {
|
||||
memset(&uid, 0, sizeof(uid));
|
||||
}
|
||||
|
||||
uint8_t sak;
|
||||
|
||||
// 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
|
||||
|
||||
// Prepare MFRC522
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_COLL_REG, RC522_PCD_VALUES_AFTER_COLL_BIT));
|
||||
|
||||
// Repeat Cascade Level loop until we have a complete UID.
|
||||
uid_complete = false;
|
||||
while (!uid_complete) {
|
||||
RC522_LOGD("cascade_level=%d, uid.length=%d", cascade_level, uid.length);
|
||||
|
||||
// Set the Cascade Level in the SEL byte, find out if we need to use the Cascade Tag in byte 2.
|
||||
switch (cascade_level) {
|
||||
case 1:
|
||||
buffer[0] = RC522_PICC_CMD_SEL_CL1;
|
||||
uid_index = 0;
|
||||
|
||||
// When we know that the UID has more than 4 bytes
|
||||
use_cascade_tag = uid.length > 4;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
buffer[0] = RC522_PICC_CMD_SEL_CL2;
|
||||
uid_index = 3;
|
||||
|
||||
// When we know that the UID has more than 7 bytes
|
||||
use_cascade_tag = uid.length > 7;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
buffer[0] = RC522_PICC_CMD_SEL_CL3;
|
||||
uid_index = 6;
|
||||
use_cascade_tag = false; // Never used in CL3.
|
||||
break;
|
||||
|
||||
default:
|
||||
return ESP_FAIL; // TODO: use custom err
|
||||
break;
|
||||
}
|
||||
|
||||
RC522_LOGD("cl=%d, uid_index=%d, use_cascade_tag=%d", cascade_level, uid_index, use_cascade_tag);
|
||||
|
||||
// How many UID bits are known in this Cascade Level?
|
||||
current_level_known_bits = skip_anticoll ? (4 * 8) : 0;
|
||||
|
||||
// Copy the known bits from uid.uidByte[] to buffer[]
|
||||
index = 2; // destination index in buffer[]
|
||||
if (use_cascade_tag) {
|
||||
buffer[index++] = RC522_PICC_CMD_CT;
|
||||
}
|
||||
|
||||
// The number of bytes needed to represent the known bits for this level.
|
||||
uint8_t bytes_to_copy = current_level_known_bits / 8 + (current_level_known_bits % 8 ? 1 : 0);
|
||||
|
||||
if (bytes_to_copy) {
|
||||
// Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag
|
||||
uint8_t max_bytes = use_cascade_tag ? 3 : 4;
|
||||
if (bytes_to_copy > max_bytes) {
|
||||
bytes_to_copy = max_bytes;
|
||||
}
|
||||
for (count = 0; count < bytes_to_copy; count++) {
|
||||
buffer[index++] = uid.value[uid_index + count];
|
||||
}
|
||||
}
|
||||
// Now that the data has been copied we need to include the 8 bits in CT in current_level_known_bits
|
||||
if (use_cascade_tag) {
|
||||
current_level_known_bits += 8;
|
||||
}
|
||||
|
||||
// Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations.
|
||||
select_done = false;
|
||||
while (!select_done) {
|
||||
// Find out how many bits and bytes to send and receive.
|
||||
if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT.
|
||||
RC522_LOGD("SELECT (cl=%d)", cascade_level);
|
||||
|
||||
// NVB - Number of Valid Bits: Seven whole bytes
|
||||
buffer[1] = 0x70;
|
||||
// Calculate BCC - Block Check Character
|
||||
buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5];
|
||||
// Calculate CRC_A
|
||||
|
||||
rc522_pcd_crc_t crc = { 0 };
|
||||
RC522_RETURN_ON_ERROR(
|
||||
rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 7 }, &crc));
|
||||
|
||||
buffer[7] = crc.lsb;
|
||||
buffer[8] = crc.msb;
|
||||
|
||||
tx_last_bits = 0; // 0 => All 8 bits are valid.
|
||||
buffer_used = 9;
|
||||
// Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx)
|
||||
response_buffer = &buffer[6];
|
||||
response_length = 3;
|
||||
}
|
||||
else { // This is an ANTICOLLISION.
|
||||
RC522_LOGD("ANTICOLLISION (cl=%d)", cascade_level);
|
||||
|
||||
tx_last_bits = current_level_known_bits % 8;
|
||||
count = current_level_known_bits / 8; // Number of whole bytes in the UID part.
|
||||
index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs
|
||||
buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits
|
||||
buffer_used = index + (tx_last_bits ? 1 : 0);
|
||||
// Store response in the unused part of buffer
|
||||
response_buffer = &buffer[index];
|
||||
response_length = sizeof(buffer) - index;
|
||||
}
|
||||
|
||||
// Set bit adjustments
|
||||
// Having a separate variable is overkill. But it makes the next line easier to read.
|
||||
rx_align = tx_last_bits;
|
||||
|
||||
// RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_BIT_FRAMING_REG, (rx_align << 4) + tx_last_bits));
|
||||
|
||||
// Transmit the buffer and receive the response.
|
||||
rc522_picc_transaction_t transaction = {
|
||||
.bytes = { .ptr = buffer, .length = buffer_used },
|
||||
.rx_align = rx_align,
|
||||
.valid_bits = tx_last_bits,
|
||||
};
|
||||
|
||||
rc522_picc_transaction_result_t transaction_result = {
|
||||
.bytes = { .ptr = response_buffer, .length = response_length },
|
||||
};
|
||||
|
||||
ret = rc522_picc_transceive(rc522, &transaction, &transaction_result);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
response_length = transaction_result.bytes.length;
|
||||
tx_last_bits = transaction_result.valid_bits;
|
||||
}
|
||||
|
||||
if (ret == RC522_ERR_COLLISION) { // More than one PICC in the field => collision.
|
||||
RC522_LOGD("collision detected (cl=%d, skip_anticoll=%d)", cascade_level, skip_anticoll);
|
||||
|
||||
if (skip_anticoll) {
|
||||
// If we are skipping anticoll, we should not have collisions
|
||||
RC522_LOGD("unexpected collision detected");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0]
|
||||
uint8_t value_of_coll_reg;
|
||||
rc522_pcd_read(rc522, RC522_PCD_COLL_REG, &value_of_coll_reg);
|
||||
|
||||
if (value_of_coll_reg & RC522_PCD_COLL_POS_NOT_VALID_BIT) {
|
||||
// Without a valid collision position we cannot continue
|
||||
RC522_LOGD("collision position not valid, coll_poss[4:0] out of range");
|
||||
|
||||
return RC522_ERR_COLLISION_UNSOLVABLE;
|
||||
}
|
||||
|
||||
uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32.
|
||||
if (collision_pos == 0) {
|
||||
collision_pos = 32;
|
||||
}
|
||||
if (collision_pos <= current_level_known_bits) { // No progress - should not happen
|
||||
RC522_LOGD("collision_pos (%d) <= current_level_known_bits (%d)",
|
||||
collision_pos,
|
||||
current_level_known_bits);
|
||||
|
||||
return RC522_ERR_COLLISION_UNSOLVABLE;
|
||||
}
|
||||
// Choose the PICC with the bit set.
|
||||
current_level_known_bits = collision_pos;
|
||||
count = current_level_known_bits % 8; // The bit to modify
|
||||
check_bit = (current_level_known_bits - 1) % 8;
|
||||
index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First byte is index 0.
|
||||
buffer[index] |= (1 << check_bit);
|
||||
}
|
||||
else if (ret != ESP_OK) {
|
||||
RC522_LOGD("transceive failed");
|
||||
|
||||
return ret;
|
||||
}
|
||||
else { // ESP_OK
|
||||
if (current_level_known_bits >= 32) { // This was a SELECT.
|
||||
// No more anticollision
|
||||
// We continue below outside the while.
|
||||
select_done = true;
|
||||
}
|
||||
else { // This was an ANTICOLLISION.
|
||||
// We now have all 32 bits of the UID in this Cascade Level
|
||||
current_level_known_bits = 32;
|
||||
// Run loop again to do the SELECT.
|
||||
}
|
||||
}
|
||||
} // End of while (!selectDone)
|
||||
|
||||
RC522_LOGD("SELECT (cl=%d) done", cascade_level);
|
||||
|
||||
// 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] == RC522_PICC_CMD_CT) ? 3 : 2; // source index in buffer[]
|
||||
bytes_to_copy = (buffer[2] == RC522_PICC_CMD_CT) ? 3 : 4;
|
||||
for (count = 0; count < bytes_to_copy; count++) {
|
||||
uid.value[uid_index + count] = buffer[index++];
|
||||
}
|
||||
|
||||
// Check response SAK (Select Acknowledge)
|
||||
if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A).
|
||||
RC522_LOGD("invalid sak");
|
||||
return RC522_ERR_INVALID_SAK;
|
||||
}
|
||||
// Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed
|
||||
// anymore.
|
||||
|
||||
// compiler complains about uninitialized response_buffer even is
|
||||
// no chance that response_buffer is NULL here, so ignore warning here
|
||||
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
||||
rc522_pcd_crc_t crc = { 0 };
|
||||
RC522_RETURN_ON_ERROR(
|
||||
rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = response_buffer, .length = 1 }, &crc));
|
||||
|
||||
if (memcmp(response_buffer + 1, &crc, sizeof(crc)) != 0) {
|
||||
RC522_LOGD("crc wrong");
|
||||
return RC522_ERR_CRC_WRONG;
|
||||
}
|
||||
|
||||
buffer[2] = crc.lsb;
|
||||
buffer[3] = crc.msb;
|
||||
|
||||
if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes
|
||||
cascade_level++;
|
||||
}
|
||||
else {
|
||||
uid_complete = true;
|
||||
sak = response_buffer[0];
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
} // End of while (!uidComplete)
|
||||
|
||||
// Set correct uid.size
|
||||
uid.length = 3 * cascade_level + 1;
|
||||
|
||||
RC522_LOGD("sak=0x%02" RC522_X, sak);
|
||||
|
||||
if (out_uid) {
|
||||
memcpy(out_uid, &uid, sizeof(uid));
|
||||
}
|
||||
|
||||
if (out_sak) {
|
||||
*out_sak = sak;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if PICC is still in the PCD field
|
||||
*/
|
||||
esp_err_t rc522_picc_heartbeat(
|
||||
const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_uid_t *out_uid, uint8_t *out_sak)
|
||||
{
|
||||
RC522_CHECK(rc522 == NULL);
|
||||
RC522_CHECK(picc == NULL);
|
||||
RC522_CHECK(picc->state != RC522_PICC_STATE_ACTIVE && picc->state != RC522_PICC_STATE_ACTIVE_H);
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
const uint8_t retries = 5;
|
||||
uint8_t retry = 1;
|
||||
|
||||
do {
|
||||
rc522_picc_atqa_desc_t atqa;
|
||||
|
||||
if (retry <= 2) {
|
||||
if (picc->state == RC522_PICC_STATE_ACTIVE) {
|
||||
ret = rc522_picc_reqa(rc522, &atqa);
|
||||
}
|
||||
else if (picc->state == RC522_PICC_STATE_ACTIVE_H) {
|
||||
ret = rc522_picc_wupa(rc522, &atqa);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((ret = rc522_picc_reqa(rc522, &atqa)) != ESP_OK) {
|
||||
ret = rc522_picc_wupa(rc522, &atqa);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
rc522_delay_ms(5);
|
||||
}
|
||||
while (retry++ < retries);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
rc522_picc_uid_t uid;
|
||||
uint8_t sak;
|
||||
|
||||
memcpy(&uid, &picc->uid, sizeof(rc522_picc_uid_t));
|
||||
|
||||
ret = rc522_picc_select(rc522, &uid, &sak, true);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (picc->sak != sak) {
|
||||
return RC522_ERR_PICC_POST_HEARTBEAT_MISSMATCH;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < uid.length; i++) {
|
||||
if (picc->uid.value[i] != uid.value[i]) {
|
||||
return RC522_ERR_PICC_POST_HEARTBEAT_MISSMATCH;
|
||||
}
|
||||
}
|
||||
|
||||
if (out_uid) {
|
||||
memcpy(out_uid, &uid, sizeof(uid));
|
||||
}
|
||||
|
||||
if (out_sak) {
|
||||
*out_sak = sak;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t rc522_picc_uid_to_str(const rc522_picc_uid_t *uid, char *buffer, uint8_t buffer_size)
|
||||
{
|
||||
RC522_CHECK(uid == NULL);
|
||||
RC522_CHECK(buffer == NULL);
|
||||
RC522_CHECK(buffer_size < RC522_PICC_UID_STR_BUFFER_SIZE_MAX);
|
||||
|
||||
return rc522_buffer_to_hex_str(uid->value, uid->length, buffer, buffer_size);
|
||||
}
|
||||
|
||||
esp_err_t rc522_picc_halta(const rc522_handle_t rc522, rc522_picc_t *picc)
|
||||
{
|
||||
RC522_CHECK(rc522 == NULL);
|
||||
RC522_CHECK(picc == NULL);
|
||||
|
||||
RC522_LOGD("HALTA");
|
||||
|
||||
uint8_t buffer[4] = { 0 };
|
||||
|
||||
buffer[0] = RC522_PICC_CMD_HLTA;
|
||||
buffer[1] = 0;
|
||||
|
||||
rc522_pcd_crc_t crc = { 0 };
|
||||
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 2 }, &crc));
|
||||
|
||||
buffer[2] = crc.lsb;
|
||||
buffer[3] = crc.msb;
|
||||
|
||||
rc522_picc_transaction_t transaction = {
|
||||
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
|
||||
};
|
||||
|
||||
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, NULL);
|
||||
|
||||
// If the PICC responds with any modulation during a period of 1 ms after the HLTA,
|
||||
// response shall be interpreted as 'not acknowledge', so timeout is not an error.
|
||||
if (ret == ESP_OK) {
|
||||
return RC522_ERR_HLTA_NOT_ACKED;
|
||||
}
|
||||
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT || ret == RC522_ERR_RX_TIMEOUT) {
|
||||
RC522_RETURN_ON_ERROR(rc522_picc_set_state(rc522, picc, RC522_PICC_STATE_HALT, true));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t rc522_picc_set_state(
|
||||
const rc522_handle_t rc522, rc522_picc_t *picc, rc522_picc_state_t new_state, bool fire_event)
|
||||
{
|
||||
RC522_CHECK(rc522 == NULL);
|
||||
RC522_CHECK(picc == NULL);
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
if (picc->state == new_state) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
RC522_LOGD("changing state from %d to %d (fire=%d)", picc->state, new_state, fire_event);
|
||||
|
||||
rc522_picc_state_t old_state = picc->state;
|
||||
|
||||
picc->state = new_state;
|
||||
|
||||
if (fire_event) {
|
||||
rc522_picc_state_changed_event_t event_data = {
|
||||
.old_state = old_state,
|
||||
.picc = picc,
|
||||
};
|
||||
|
||||
if ((ret = rc522_dispatch_event(rc522, RC522_EVENT_PICC_STATE_CHANGED, &event_data, sizeof(event_data)))
|
||||
!= ESP_OK) {
|
||||
RC522_LOGW("picc_state_changed event dispatch failed (err=%04" RC522_X ")", ret);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
rc522_picc_type_t rc522_picc_get_type(const rc522_picc_t *picc)
|
||||
{
|
||||
RC522_CHECK(picc == NULL);
|
||||
|
||||
uint8_t sak = picc->sak;
|
||||
|
||||
// http://www.nxp.com/documents/application_note/AN10833.pdf
|
||||
// Section: Coding of Select Acknowledge (SAK)
|
||||
|
||||
// ignore 8th (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 0x09:
|
||||
return RC522_PICC_TYPE_MIFARE_MINI;
|
||||
case 0x08:
|
||||
return RC522_PICC_TYPE_MIFARE_1K;
|
||||
case 0x18:
|
||||
return RC522_PICC_TYPE_MIFARE_4K;
|
||||
case 0x00:
|
||||
return RC522_PICC_TYPE_MIFARE_UL;
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
return RC522_PICC_TYPE_MIFARE_PLUS;
|
||||
case 0x01:
|
||||
return RC522_PICC_TYPE_TNP3XXX;
|
||||
case 0x20:
|
||||
return picc->atqa.source == 0x4400 ? RC522_PICC_TYPE_MIFARE_DESFIRE : RC522_PICC_TYPE_ISO_14443_4;
|
||||
case 0x40:
|
||||
return RC522_PICC_TYPE_ISO_18092;
|
||||
default:
|
||||
return RC522_PICC_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
char *rc522_picc_type_name(rc522_picc_type_t type)
|
||||
{
|
||||
switch (type) {
|
||||
/* SAK-determined */
|
||||
case RC522_PICC_TYPE_ISO_14443_4:
|
||||
return "PICC compliant with ISO/IEC 14443-4";
|
||||
case RC522_PICC_TYPE_ISO_18092:
|
||||
return "PICC compliant with ISO/IEC 18092 (NFC)";
|
||||
case RC522_PICC_TYPE_MIFARE_MINI:
|
||||
return "MIFARE Mini, 320 bytes";
|
||||
case RC522_PICC_TYPE_MIFARE_1K:
|
||||
return "MIFARE 1K";
|
||||
case RC522_PICC_TYPE_MIFARE_4K:
|
||||
return "MIFARE 4K";
|
||||
case RC522_PICC_TYPE_MIFARE_UL:
|
||||
// SAK=0x00 for both of these families
|
||||
return "MIFARE Ultralight or NXP NTAG";
|
||||
case RC522_PICC_TYPE_MIFARE_PLUS:
|
||||
return "MIFARE Plus";
|
||||
case RC522_PICC_TYPE_MIFARE_DESFIRE:
|
||||
return "MIFARE DESFire";
|
||||
case RC522_PICC_TYPE_TNP3XXX:
|
||||
return "MIFARE TNP3XXX";
|
||||
|
||||
/* Further ID processing */
|
||||
case RC522_PICC_TYPE_MIFARE_UL_:
|
||||
return "MIFARE Ultralight";
|
||||
case RC522_PICC_TYPE_MIFARE_UL_C:
|
||||
return "MIFARE Ultralight C";
|
||||
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
|
||||
return "MIFARE Ultralight EV1 (80 bytes)";
|
||||
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
|
||||
return "MIFARE Ultralight EV1 (164 bytes)";
|
||||
case RC522_PICC_TYPE_MIFARE_UL_NANO:
|
||||
return "MIFARE Ultralight NANO";
|
||||
case RC522_PICC_TYPE_MIFARE_UL_AES:
|
||||
return "MIFARE Ultralight AES";
|
||||
case RC522_PICC_TYPE_NTAG2xx:
|
||||
return "NTAG2xx";
|
||||
case RC522_PICC_TYPE_NTAG213:
|
||||
return "NTAG213";
|
||||
case RC522_PICC_TYPE_NTAG215:
|
||||
return "NTAG215";
|
||||
case RC522_PICC_TYPE_NTAG216:
|
||||
return "NTAG216";
|
||||
case RC522_PICC_TYPE_UNDEFINED:
|
||||
case RC522_PICC_TYPE_UNKNOWN:
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t rc522_picc_print(const rc522_picc_t *picc)
|
||||
{
|
||||
RC522_CHECK(picc == NULL);
|
||||
|
||||
char uid_str[RC522_PICC_UID_STR_BUFFER_SIZE_MAX];
|
||||
RC522_RETURN_ON_ERROR(rc522_picc_uid_to_str(&picc->uid, uid_str, sizeof(uid_str)));
|
||||
|
||||
ESP_LOGI(TAG, "");
|
||||
ESP_LOGI(TAG, "╔══════════════╗");
|
||||
ESP_LOGI(TAG, "║ ║ Type: %s", rc522_picc_type_name(picc->type));
|
||||
ESP_LOGI(TAG, "║ RFID ║ UID: %s", uid_str);
|
||||
ESP_LOGI(TAG, "║ CARD ║ ATQA: 0x%04" RC522_X, picc->atqa.source);
|
||||
ESP_LOGI(TAG, "║ ║ SAK: 0x%02" RC522_X, picc->sak);
|
||||
ESP_LOGI(TAG, "╚══════════════╝");
|
||||
ESP_LOGI(TAG, "");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user