From 73ceeb493c7eeeb4333990ad1bb22ae0d9d9ab1f Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sat, 6 Jan 2024 00:15:45 +0000 Subject: [PATCH 1/5] RFID: Clean up and generalize Add a constant for the ID block used and replace it. Clean up some comments and clarify expectations. readBlock() did not properly halt tags after errors and the else clause was superfluous. Properly handle error status. Move currentActiveTags to be static and initialize it. This allows for keeping track of max/current tags accross multiple instances. --- include/RFID.h | 7 +++++-- src/RFID.cpp | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/include/RFID.h b/include/RFID.h index ef5b02b..c134fa4 100644 --- a/include/RFID.h +++ b/include/RFID.h @@ -30,6 +30,8 @@ struct uidNode { class RFID { public: + static constexpr int maxTags = 2; + /*** * Use the main SPI port and NFC chip select by default, but allow choosing * an alternate chip select and/or SPI bus if needed to allow multiple @@ -49,10 +51,10 @@ public: */ int8_t checkTags(); private: + static const byte tagIdBlock = 0x11; /* Used for "encrypted" communication with the tags. */ static MFRC522Constants::MIFARE_Key defaultKey; - static constexpr int maxTags = 2; - int currentActiveTags = 0; + static int currentActiveTags; /* * The linked list tracking active tags. @@ -90,6 +92,7 @@ private: /** * Read block at blockADdr from tagUid and return if the read succeeded or not. * The read block will be in comData. + * The tag needs to be already woken up. * @param tagUid Uid of the tag to be read. * @param blockAddr Address of the block to be read. * @return True if read succeeded, false otherwise. diff --git a/src/RFID.cpp b/src/RFID.cpp index 22c69fc..fd40870 100644 --- a/src/RFID.cpp +++ b/src/RFID.cpp @@ -5,6 +5,7 @@ #include "RFID.h" MFRC522Constants::MIFARE_Key RFID::defaultKey = {0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +int RFID::currentActiveTags = 0; void RFID::init() { // Create and set up a static linked list to manage active tags. @@ -58,11 +59,11 @@ int8_t RFID::checkTags() { return 0; } // If the read fails, we cannot use this tag anyway so remove it from the list. - if (!readBlock(newTag->uid, 0x11) ) { + if (!readBlock(newTag->uid, tagIdBlock) ) { removeActiveTag(newTag, nullptr); return 0; } - // TODO: ID check ? + // Don' t check the ID, otherwise we cannot program the tag. newTag->tag_ID = comData[0]; return newTag->tag_ID; } @@ -101,16 +102,17 @@ bool RFID::readBlock(MFRC522Constants::Uid &uidToRead, byte blockAddr) { if (mfrc.PCD_Authenticate( MFRC522Constants::PICC_Command::PICC_CMD_MF_AUTH_KEY_A, blockAddr, (&defaultKey),&uidToRead)!= MFRC522Constants::STATUS_OK) { - Serial.println("Failed to authenticate"); + Serial.println("readBlock: Failed to authenticate"); + mfrc.PICC_HaltA(); return false; - } else { - byte size = sizeof(comData); - mfrc.MIFARE_Read(blockAddr, comData, &size); -// Serial.printf("Read block : 0x%lX\n", *(uint32_t *)comData); } + + byte size = sizeof(comData); + MFRC522::StatusCode status = mfrc.MIFARE_Read(blockAddr, comData, &size); +// Serial.printf("Read block : 0x%lX\n", *(uint32_t *)comData); // Needed otherwise no new communications can happen. mfrc.PCD_StopCrypto1(); // No need to keep the tag active. mfrc.PICC_HaltA(); - return true; + return status == MFRC522Constants::STATUS_OK; } From 324cf50653e2d37715f277d0a5dcec9630ed27a1 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sat, 6 Jan 2024 00:25:46 +0000 Subject: [PATCH 2/5] RFID: Implement writing to blocks In order to set the ID to the tag, we need to be able to write blocks. Implement writeBlock() similarly to readBlock() to allow that. Make sure to clean the communication buffer and handle failure states, this will become more important for dual RFID readers as will allow 'chaining' calls and cover both readers without keeping track of what reader sees what tag. Only allow to write a byte for now, we don't need more. --- include/RFID.h | 10 ++++++++++ src/RFID.cpp | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/include/RFID.h b/include/RFID.h index c134fa4..168eca4 100644 --- a/include/RFID.h +++ b/include/RFID.h @@ -98,6 +98,16 @@ private: * @return True if read succeeded, false otherwise. */ bool readBlock(MFRC522Constants::Uid &tagUid, byte blockAddr); + + /** + * Write data to tagUid at blockAddr and return if the write succeeded or not. + * The tag needs to be already woken up. + * @param tagUid Uid of the tag to write to. + * @param blockAddr Address of the block to write to. + * @param data Byte to write. + * @return True if write succeeded, false otherwise. + */ + bool writeBlock(MFRC522Constants::Uid &tagUid, byte blockAddr, uint8_t data); }; #endif //JIN_BARBAPAPA_RFID_H diff --git a/src/RFID.cpp b/src/RFID.cpp index fd40870..7df626d 100644 --- a/src/RFID.cpp +++ b/src/RFID.cpp @@ -116,3 +116,26 @@ bool RFID::readBlock(MFRC522Constants::Uid &uidToRead, byte blockAddr) { mfrc.PICC_HaltA(); return status == MFRC522Constants::STATUS_OK; } + +bool RFID::writeBlock(MFRC522Constants::Uid &uidToRead, byte blockAddr, uint8_t data) { + if (mfrc.PCD_Authenticate( + MFRC522Constants::PICC_Command::PICC_CMD_MF_AUTH_KEY_A, + blockAddr, (&defaultKey),&uidToRead)!= MFRC522Constants::STATUS_OK) { + Serial.println("writeBlock: Failed to authenticate"); + mfrc.PICC_HaltA(); + return false; + } + + byte size = sizeof(comData); + for (byte& bufferByte: comData) + bufferByte = 0; + + *comData = data; + + MFRC522::StatusCode status = mfrc.MIFARE_Write(blockAddr, comData, size); + // Needed otherwise no new communications can happen. + mfrc.PCD_StopCrypto1(); + // No need to keep the tag active. + mfrc.PICC_HaltA(); + return status == MFRC522Constants::STATUS_OK; +} From b5b5268b79d5fb74f8e15a477caa81d303f4ee6c Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sat, 6 Jan 2024 00:32:19 +0000 Subject: [PATCH 3/5] Com: Implement tag programming Define the protocol allowing the software to track tags and program them, in the README. The data structure is shared between a programming command and an information message, so create a new struct for it and an array to keep track of it and expose to the rest of the program. The goal is to keep the protocol core and message construction entirely inside Com, so this intermediary buffer allows exposing it outside while hiding the protocol details. This means that we are starting to receive data from the software, so implement generic functions to check and receive data as well as receiveMessage() which parses the message and handles them. --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++ include/Com.h | 35 ++++++++++++++++++++++++++++++ src/Com.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c9c577c..f1bc184 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,61 @@ Octet 1 `D`: bit 7, 1 si enlevé, 0 sinon `C`: bit 6, 1 si personnage, 0 si année +### Requête d'information pour la programmation de tag + +**En-tête** : `?` +**Longueur** : 0 octets +**Description** : Message **vide** envoyé du logiciel vers l'objet pour +demander les informations complètes qui permettront de programmer les tags. +Il attend une réponse de l'objet, décrit dans la catégorie suivante. + +### Réponse d'information pour la programmation de tag + +**En-tête** : `!` +**Longueur** : 1 + 5*n octets, n le nombre de figurines présentes (>=0) +**Description** : Message de réponse à la requête d'information pour la programmation +des tags. +Le premier octet est fixé et indique le nombre de figurines présentes, et donc +le nombre d'entrées dans le message. +Ensuite, chaque entrée est constituée de l'ID actuel, sur un octet, puis de l'UID +du tag, sur quatre octets. +C'est l'UID qui est utilisé dans la demande de programmation pour sélectionner le tag. + +#### Schéma + +``` + │Optional, N records + ┌─┼────┬──┬────┬──┐ + │N│UID1│I1│UID2│I2│... + └─┴────┴──┴────┴──┘ + 0 1 5 6 10 11 - Octets +``` +`N` : Nombre de tags présents, 1 octet +`UIDn` : UID du tag n, 4 octets +`In` : ID du tag n, 1 octet + +### Message de programmation de tag + +**En-tête** : `=` +**Longueur** : 5 octets +**Description** : Message envoyé par le logiciel à l'objet, permet de changer +(programmer) l'ID stocké dans un tag. +Le contenu est formaté de façon similaire à la réponse d'information : +l'UID du tag à programmer, sur quatre octets, puis le nouvel identifiant du tag, +sur un octet. +On ne programme qu'un tag à la fois. + +#### Schéma + +``` + ┌─────┬────┐ + │ UID │ ID │ + └─────┴────┘ + 0 4 5 - Octets +``` +`UID` : UID du tag à programmer, 4 octets +`ID` : Nouvel ID à programmer, 1 octet + ### Message informatif **En-tête** : `#` diff --git a/include/Com.h b/include/Com.h index 8165ebe..ac3a688 100644 --- a/include/Com.h +++ b/include/Com.h @@ -13,8 +13,43 @@ * functions for the fixed protocols. */ namespace Com { + enum ReceivedMessage { + NO_MESSAGE, + TAG_INFO_REQUEST, + TAG_PROGRAMMING, + INVALID_MESSAGE, + }; + + /** + * Struct used for tag info responses and tag programming. + * Would be 8 bytes if not packed due to padding. + */ + struct __attribute__((packed)) TagInfoRecord { + uint32_t uid; + uint8_t figId; + }; + void sendFigUpdate(int8_t event); void sendComment(const char message[62], ...); + /** + * Send the response to the tag info request. + * The TagInfoRecord buffer needs to be properly filled through getTagRecords(). + * @param tagCount The amount of tags that are present in the response. + */ + void sendTagInfo(uint8_t tagCount); + + /** + * @return Pointer to a buffer of TagInfoRecords + */ + TagInfoRecord* getTagRecords(); + + /** + * Check for any incoming message and handle it if needed, returning the + * kind of message received if known, no message if there was nothing to read + * and invalid message otherwise. + * @return Kind of message received. + */ + ReceivedMessage receiveMessage(); } #endif //JIN_BARBAPAPA_COM_H diff --git a/src/Com.cpp b/src/Com.cpp index adafc82..8c45eaa 100644 --- a/src/Com.cpp +++ b/src/Com.cpp @@ -3,9 +3,12 @@ // #include "Com.h" +#include "RFID.h" namespace Com { byte buffer[64]; + TagInfoRecord records[RFID::maxTags] = {}; + /** * Will flush buffer through either HID or Serial depending on build * options. @@ -19,7 +22,7 @@ namespace Com { Serial.write((const char*)buffer); #endif /* Clear the buffer. */ - memset(buffer, 0, 64); + memset(buffer, 0, sizeof(buffer)); } void sendFigUpdate(int8_t event) { @@ -36,4 +39,58 @@ namespace Com { va_end(args); flushBuffer(); } + + void sendTagInfo(uint8_t tagCount) { + buffer[0] = '!'; + buffer[1] = tagCount; + memcpy(buffer + 2, records, sizeof(TagInfoRecord)*tagCount); + flushBuffer(); + } + + TagInfoRecord* getTagRecords() { + return records; + } + + inline bool dataAvailable() { +#ifdef USB_RAWHID + return usb_rawhid_available(); +#else + /* Serial is used for debug, do not care about message length. */ + return Serial.available(); +#endif + } + + inline void receiveData() { + memset(buffer, 0, sizeof(buffer)); +#ifdef USB_RAWHID + /* We know we have data, don't need to set a timeout. */ + usb_rawhid_recv(buffer, 0); +#else + /* Could lead to incomplete messages, but should be OK on Teensy. */ + Serial.readBytes(buffer, Serial.available()); +#endif + } + + ReceivedMessage receiveMessage() { + if (!dataAvailable()) + return NO_MESSAGE; + receiveData(); + + ReceivedMessage messageType; + switch (buffer[0]) { + case '?': + messageType = TAG_INFO_REQUEST; + break; + case '=': + messageType = TAG_PROGRAMMING; + /* Make the record available to the rest of the code. */ + memset(records, 0, sizeof(records)); + memcpy(records, buffer + 1, sizeof(TagInfoRecord)); + break; + default: + messageType = INVALID_MESSAGE; + Serial.printf("Invalid header received : %c\n", buffer[0]); + } + return messageType; + } } From 38e4982aace4acf0fcfb171ff265ae8625e439db Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sat, 6 Jan 2024 00:36:19 +0000 Subject: [PATCH 4/5] RFID: Implement tag programming The software needs tag data to be able to send programming requests, so go through the active tags and extract the data in the new Com structs. programTag() handles tag wakeup and selection, so might fail if the requested tag is on another reader. This allows chaining them together and not keeping track of which tag which reader sees. Add an assert in case the communication buffer would get too small to send all active tags as the protocol does not define a multi-message response. --- include/RFID.h | 16 ++++++++++++++++ src/RFID.cpp | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/include/RFID.h b/include/RFID.h index 168eca4..66a42f8 100644 --- a/include/RFID.h +++ b/include/RFID.h @@ -15,6 +15,7 @@ #include #include +#include "Com.h" #include "IDs.h" #include "Pinout.h" @@ -50,6 +51,21 @@ public: * @return 0 if no change, ID if new tag detected, ID & sign bit if the tag left. */ int8_t checkTags(); + + /*** + * Populate an already allocated array of sufficient size to fit + * maxTags number of TagInfoRecords. + * @param tagData Pointer to an array of size maxTags*sizeof(Com::TagInfoRecord) + * @return Number of tags actually put in the array. + */ + uint8_t gatherTagInfo(Com::TagInfoRecord* tagData); + + /*** + * Change the ID stored in a tag so that it matches tagToProgram. + * @param tagToProgram Pointer to an existing struct with the data to program. + * @return True if the programming succeeded. + */ + bool programTag(Com::TagInfoRecord* tagToProgram); private: static const byte tagIdBlock = 0x11; /* Used for "encrypted" communication with the tags. */ diff --git a/src/RFID.cpp b/src/RFID.cpp index 7df626d..03b40d5 100644 --- a/src/RFID.cpp +++ b/src/RFID.cpp @@ -4,6 +4,9 @@ #include "RFID.h" +static_assert(RFID::maxTags * sizeof(Com::TagInfoRecord) <= 62, + "RFID::maxTags too high, protocol cannot support it.\n"); + MFRC522Constants::MIFARE_Key RFID::defaultKey = {0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; int RFID::currentActiveTags = 0; @@ -68,6 +71,38 @@ int8_t RFID::checkTags() { return newTag->tag_ID; } +uint8_t RFID::gatherTagInfo(Com::TagInfoRecord *tagData) { + int tagCount = 0; + for (uidNode* node = activeTags; node; node = node->next) { + // The protocol does not support UIDs other than 4 bytes, so assume it + // is the case here directly. + tagData[tagCount].uid = *(uint32_t*)(node->uid.uidByte); + tagData[tagCount].figId = node->tag_ID; + tagCount++; + } + return tagCount; +} + +bool RFID::programTag(Com::TagInfoRecord *tagToProgram) { + + byte comSize = sizeof(comData); + + mfrc.uid.size = 4; + *(uint32_t*)(mfrc.uid.uidByte) = tagToProgram->uid; + mfrc.PICC_WakeupA(comData, &comSize); + if (mfrc.PICC_Select(&mfrc.uid, mfrc.uid.size) != MFRC522Constants::STATUS_OK) { + Serial.println("Failed to wakeup for programming. Is tag still there ?\n"); + mfrc.PICC_HaltA(); + return false; + } + + if (!writeBlock(mfrc.uid, tagIdBlock, tagToProgram->figId)) { + Serial.println("Failed to program tag."); + return false; + } + return true; +} + uidNode* RFID::addActiveTag(const MFRC522Constants::Uid &newTag) { if (!nextFreeTagSlot) { return nullptr; From 5618f52a0e444866ac6617b06333fe0a8b72fae3 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sat, 6 Jan 2024 00:40:27 +0000 Subject: [PATCH 5/5] main: Add message handling We want to check for new messages at the end of the loop, when all other events should have happenned. Handle the two supported messages there : tag info request and tag programming command. The timing might need some testing, hopefully it is short enough. --- src/main.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 3b5902f..b281973 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -77,6 +77,16 @@ __attribute__((noreturn)) int main() { } } + Com::ReceivedMessage message = Com::receiveMessage(); + if (message == Com::TAG_INFO_REQUEST) { + uint8_t tagCount = rfid.gatherTagInfo(Com::getTagRecords()); + Com::sendTagInfo(tagCount); + } else if (message == Com::TAG_PROGRAMMING) { + if (!rfid.programTag(Com::getTagRecords())) { + Com::sendComment("Tag programming failed."); + } + } + /* TODO: Drop delay, WFE+timer interrupt(s) ? */ delay(25); }