Add Gitea OTA firmware update, dual-slot partition layout, and Gitea Actions release workflow
This commit is contained in:
57
.gitea/workflows/releases.yml
Normal file
57
.gitea/workflows/releases.yml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: Build & Release Firmware
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build ESP32 Firmware
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: espressif/idf:v5.5
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Compile ESP32 firmware
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
. $IDF_PATH/export.sh
|
||||||
|
idf.py build
|
||||||
|
|
||||||
|
- name: Bundle release artifacts
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p release-dist
|
||||||
|
# Primary OTA binary required by on-device update manager:
|
||||||
|
cp build/mtg-rfid-companion.bin release-dist/
|
||||||
|
# USB flashing bundle:
|
||||||
|
cp build/bootloader/bootloader.bin release-dist/
|
||||||
|
cp build/partition_table/partition-table.bin release-dist/
|
||||||
|
cp build/storage.bin release-dist/
|
||||||
|
cd release-dist
|
||||||
|
tar -czvf full-flash-bundle-${{ gitea.ref_name || 'latest' }}.tar.gz *.bin
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
- name: Publish to Gitea Release
|
||||||
|
uses: https://gitea.com/actions/release-action@v1
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
release-dist/mtg-rfid-companion.bin
|
||||||
|
release-dist/full-flash-bundle-${{ gitea.ref_name || 'latest' }}.tar.gz
|
||||||
|
name: "Release ${{ gitea.ref_name }}"
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
api_key: ${{ secrets.RELEASE_TOKEN || gitea.token }}
|
||||||
|
|
||||||
|
- name: Upload Artifacts to Workflow
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: mtg-firmware-${{ gitea.ref_name || 'manual' }}
|
||||||
|
path: release-dist/*
|
||||||
28
.gitignore
vendored
28
.gitignore
vendored
@@ -1,5 +1,29 @@
|
|||||||
|
# ESP-IDF build output and temporary files
|
||||||
build/
|
build/
|
||||||
managed_components/
|
|
||||||
sdkconfig.old
|
sdkconfig.old
|
||||||
sdkconfig.defaults.bak
|
sdkconfig.defaults.bak
|
||||||
dependencies.lock
|
dependencies.lock
|
||||||
|
|
||||||
|
# ESP Component Manager downloads
|
||||||
|
managed_components/
|
||||||
|
|
||||||
|
# Editor & IDE configurations
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.code-workspace
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Python and virtual environments
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Operating System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
[Dd]esktop.ini
|
||||||
@@ -32,7 +32,6 @@ The **MTG RFID Companion** is a hand-held hardware utility for Magic: The Gather
|
|||||||
| **MFRC522 RFID**| CS (SDA) / RST | GPIO 5 / GPIO 22 | Dedicated CS & Reset |
|
| **MFRC522 RFID**| CS (SDA) / RST | GPIO 5 / GPIO 22 | Dedicated CS & Reset |
|
||||||
| **ST7789 Display**| CS / DC / RST | GPIO 15 / GPIO 2 / GPIO 4 | Dedicated CS, Data/Cmd & Reset |
|
| **ST7789 Display**| CS / DC / RST | GPIO 15 / GPIO 2 / GPIO 4 | Dedicated CS, Data/Cmd & Reset |
|
||||||
| **Rotary Encoder**| CLK / DT / SW | GPIO 32 / GPIO 33 / GPIO 25 | Active low / Interrupt driven |
|
| **Rotary Encoder**| CLK / DT / SW | GPIO 32 / GPIO 33 / GPIO 25 | Active low / Interrupt driven |
|
||||||
| **Indicators** | Neopixel (WS2812B) | GPIO 27 | RMT driver |
|
|
||||||
| **Audio** | Piezo Buzzer | GPIO 14 | LEDC PWM channel |
|
| **Audio** | Piezo Buzzer | GPIO 14 | LEDC PWM channel |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -124,7 +123,7 @@ Build Commands
|
|||||||
|
|
||||||
Monitor Logs: idf.py -p <PORT> monitor
|
Monitor Logs: idf.py -p <PORT> monitor
|
||||||
|
|
||||||
Partition Table: Uses custom partitions.csv (Flash allocation: 2MB App, 1.5MB LittleFS data).
|
Partition Table: Uses custom partitions.csv (Flash allocation: 2x 1.56MB dual-OTA app slots, 832KB LittleFS data, 8KB otadata, 16KB NVS).
|
||||||
|
|
||||||
8. Guidance for AI Agents Generating Code
|
8. Guidance for AI Agents Generating Code
|
||||||
|
|
||||||
@@ -148,8 +147,8 @@ Build Commands
|
|||||||
| `wifi_manager.c` | STA auto-connect + reconnect | event loop |
|
| `wifi_manager.c` | STA auto-connect + reconnect | event loop |
|
||||||
| `scryfall_client.c` | Scryfall HTTPS, cJSON cache, prefetch | net_task / 0 |
|
| `scryfall_client.c` | Scryfall HTTPS, cJSON cache, prefetch | net_task / 0 |
|
||||||
| `web_server.c` | SoftAP MTG-Companion 192.168.4.1 + pairing SPA | httpd task |
|
| `web_server.c` | SoftAP MTG-Companion 192.168.4.1 + pairing SPA | httpd task |
|
||||||
|
| `ota_manager.c` | Gitea release API, HTTPS OTA flash, rollback cancel | Core 0 |
|
||||||
| `encoder.c` | EC11 ISR decode + button | ISR |
|
| `encoder.c` | EC11 ISR decode + button | ISR |
|
||||||
| `led_manager.c` | WS2812B RMT | ISR + ui_task |
|
|
||||||
| `buzzer.c` | LEDC PWM tones | ui_task |
|
| `buzzer.c` | LEDC PWM tones | ui_task |
|
||||||
| `app_state.c` | STATE_* + life counter values | all |
|
| `app_state.c` | STATE_* + life counter values | all |
|
||||||
|
|
||||||
|
|||||||
342
README.md
342
README.md
@@ -1,14 +1,28 @@
|
|||||||
# MTG RFID Companion
|
# MTG RFID Companion 🧙♂️✨
|
||||||
|
|
||||||
A feature-packed, handheld **Magic: The Gathering** companion built on the **ESP32-WROOM-32**. Slip an RFID/NFC tag into your card sleeves and tap them to the device for instant card lookups, official art display, life tracking, dice rolling, and complete deck management.
|
A dedicated tabletop companion for **Magic: The Gathering** players. Slip micro RFID/NFC stickers into your card sleeves and tap them to the device for instant card lookups, official full-color art, life tracking, dice rolling, and complete deck management.
|
||||||
|
|
||||||
All firmware is written in **pure ESP-IDF v5.x (C) / FreeRTOS** — no Arduino overhead, dual-core task segregation, and strictly optimized for devices without external PSRAM (< 520 KB SRAM).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🌟 Feature Tour & Modes
|
## 🃏 Part 1: Player's Guide (Non-Technical Overview)
|
||||||
|
|
||||||
The companion boots directly into an interactive, rotary-controlled interface designed specifically for tabletop MTG gameplay:
|
### What is the MTG Companion?
|
||||||
|
The MTG Companion is a handheld gadget designed to sit on your playmat during Commander (EDH), Modern, or casual kitchen-table games.
|
||||||
|
|
||||||
|
Instead of fumbling for your phone to check complex Oracle card text, searching for spindown dice, or asking *"Wait, how much commander tax am I at?"*, the Companion handles everything with a turn of a knob and a tap of a card:
|
||||||
|
|
||||||
|
* **⚡ Tap-to-Identify:** Tap any sleeved card with an NFC sticker to instantly display its official illustration, mana cost, updated Oracle text, type line, power/toughness, and current market price.
|
||||||
|
* **👑 Legendary Fanfares:** An 8-bit stinger plays whenever you cast a Mythic Rare or Legendary spell!
|
||||||
|
* **❤️ Life & Multi-Counter:** Tracks starting life (40 for Commander, 20 for Standard/Modern), **Poison Counters** (tracks to 10 lethal), and **Commander Combat Damage** (tracks to 21 lethal). Sounds an 8-bit defeat tone if a player dies.
|
||||||
|
* **🎲 Spindown D20, D6 & Fair Coin:** Roll animated dice on screen with ticking sound effects and special fanfares for Natural 20s and Natural 1s.
|
||||||
|
* **🪙 Token & Counter Spawner:** Digital counters for **Treasures, Foods, Clues, 1/1 Soldiers, 1/1 Goblins, 2/2 Zombies, 3/3 Beasts**, and **+1/+1 Counters**.
|
||||||
|
* **👑 Commander Tax & Turn Tracker:** Automatically calculates the extra {2} mana tax every time your commander returns to the command zone, alongside a table turn counter.
|
||||||
|
* **📱 Wireless Phone Pairing:** Connect your phone or laptop over your home Wi-Fi (or the device's hotspot) to easily assign card names to tags with live Scryfall search autocomplete.
|
||||||
|
* **🔄 Over-The-Air (OTA) Updates:** Update to new software versions wirelessly over Wi-Fi right from the on-device menu.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### How to Use at the Table
|
||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────────────────────┐
|
┌──────────────────────────────┐
|
||||||
@@ -20,160 +34,115 @@ The companion boots directly into an interactive, rotary-controlled interface de
|
|||||||
│ [4] Commander Tax │
|
│ [4] Commander Tax │
|
||||||
│ [5] Token & Counters │
|
│ [5] Token & Counters │
|
||||||
│ [6] Deck Storage │
|
│ [6] Deck Storage │
|
||||||
│ [7] Device Info │
|
│ [7] Device & WiFi Info │
|
||||||
|
│ [8] Check Firmware OTA │
|
||||||
└──────────────────────────────┘
|
└──────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1. Interactive Boot Menu (`APP_STATE_MENU`)
|
#### Controls
|
||||||
On power-up, the device greets you with the Main Menu.
|
* **Turn the knob:** Scroll through menus, adjust life totals, change token quantities, or pick dice modes.
|
||||||
* **Rotate the knob** to highlight any of the 7 modes with an active amber cursor.
|
* **Click the knob:** Select an option, roll dice, or toggle between Life, Poison, and Commander Damage modes.
|
||||||
* **Click the knob** to enter the highlighted mode.
|
* **Long Press (Hold knob for 0.75s):** Sounds a chirp and instantly takes you back to the **Main Menu** from any screen.
|
||||||
* **Universal Back (Hold knob $\ge$ 0.75s):** Press and hold the rotary switch from **any** screen to sound a double-chirp, flash purple on the WS2812B LED, and instantly return to the Main Menu.
|
* **Tap a Card Anytime:** Tapping a card sleeve immediately pulls up that card's details, regardless of which screen you are on.
|
||||||
* **Universal Card Interrupt:** Tapping a card tag from **any** screen or menu immediately transitions into that card's view.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2. Universal RFID Scanner & Art Crop Display (`APP_STATE_CARD_VIEW`)
|
### Tabletop Modes
|
||||||
* **Instant Tag Identification:** Supports MFRC522, ISO/IEC 14443A, NTAG213/215 stickers, and standard MIFARE 1K cards (4-byte and 7-byte UIDs).
|
|
||||||
* **True RGB565 Scryfall Art:** Cards display official card illustrations pre-scaled to a crisp 200×110 format, decoded via hardware-assisted Baseline JPEG in ~10 ms with true color accuracy.
|
#### 1. Card Scanner & Art Viewer
|
||||||
* **Card Metadata:** Shows card name, mana cost, type line, oracle text, power/toughness, loyalty, and market pricing.
|
Tap your card against the scanner area. The device checks its internal memory and displays:
|
||||||
* **8-Bit Legendary Stinger & Neopixel Feedback:**
|
* Full-color official art crop streamed from Scryfall.
|
||||||
* **Legendary / Mythic Stinger:** Scanning any Legendary creature or Mythic Rare plays an upward 8-bit stinger and pulses the LED in gold!
|
* Mana cost, card type, power / toughness, and loyalty.
|
||||||
* **Mana Color LEDs:** WS2812B Neopixel illuminates in the card's native mana identity (White, Blue, Black, Red, Green, Gold, Colorless).
|
* Updated Oracle card text with rules text formatting.
|
||||||
* **Unmapped Card Support:** Tapping an unmapped tag switches to the Registration view, displaying the UID and pairing instructions.
|
* Market price in USD.
|
||||||
* **Smart Cache Validation:** When a card tag is re-paired or changed in the web UI, the firmware automatically detects the change, purges old cached assets, and downloads the fresh card details and art.
|
|
||||||
|
#### 2. Life & Metric Tracker
|
||||||
|
* Starts at **40 life** (Commander) or **20 life** (1v1).
|
||||||
|
* Rotate clockwise to gain life; counter-clockwise to take damage.
|
||||||
|
* **Click the knob** to cycle between:
|
||||||
|
1. **Life Total** (Standard life score).
|
||||||
|
2. **Poison Counters** (Counts 0 to 10; triggers a defeat tone at 10).
|
||||||
|
3. **Commander Combat Damage** (Counts 0 to 21; triggers lethal banner at 21).
|
||||||
|
|
||||||
|
#### 3. Spindown D20 / D6 / Fair Coin Flip
|
||||||
|
* Select **D20 Spindown**, **D6**, or **Coin Flip** and click to roll.
|
||||||
|
* An animated roll plays with sound effects.
|
||||||
|
* **Natural 20:** Plays a triumphant 8-bit Victory Fanfare!
|
||||||
|
* **Natural 1:** Sounds a critical failure buzz.
|
||||||
|
* **Coin Flip:** Clean 50/50 toss landing on **HEADS** or **TAILS**.
|
||||||
|
|
||||||
|
#### 4. Built-in Token & Counter Spawner
|
||||||
|
Never dig through your token box again for missing tokens:
|
||||||
|
* Pre-loaded with MTG staples: **Treasure, Food, Clue, Soldier (1/1), Zombie (2/2), Goblin (1/1), Beast (3/3)**, and **+1/+1 Counters**.
|
||||||
|
* Rotate to select a token, click to edit, and rotate to change quantity (persists throughout your game).
|
||||||
|
|
||||||
|
#### 5. Commander Tax & Turn Tracker
|
||||||
|
* Rotate the knob each time your commander is recast from the command zone; the screen calculates the extra mana required (e.g. `Cast 3 = +4 Mana`).
|
||||||
|
* Click to switch to the table turn counter.
|
||||||
|
|
||||||
|
#### 6. Pairing Cards from your Phone (No App Needed!)
|
||||||
|
When you scan a new card tag that hasn't been programmed yet:
|
||||||
|
1. The screen displays:
|
||||||
|
```
|
||||||
|
PAIR VIA WI-FI:
|
||||||
|
http://192.168.0.57
|
||||||
|
(or Hotspot: MTG-Companion @ 192.168.4.1)
|
||||||
|
```
|
||||||
|
2. Open that address in your smartphone or laptop browser.
|
||||||
|
3. Tap the card sleeve to the device—its unique ID pops up in the web form automatically.
|
||||||
|
4. Start typing the card name (e.g. `"Sol Ring"`). Real-time Scryfall suggestions appear.
|
||||||
|
5. Click **Pair Card**. The device downloads the high-res card art and text and saves it to its permanent memory.
|
||||||
|
6. **Deck Overview:** The web page also lets you view your entire scanned deck with card art thumbnails and unbind old cards with one click.
|
||||||
|
|
||||||
|
#### 7. Over-The-Air (OTA) Updates
|
||||||
|
* Select **8. CHECK FIRMWARE OTA** from the menu (or let it auto-check on boot when connected to Wi-Fi).
|
||||||
|
* If a new version is detected, the screen shows the new version number and asks you to click to install or turn the knob to dismiss.
|
||||||
|
* While updating, a live progress bar shows the download. When finished, the device verifies the update and restarts automatically.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3. Life & Multi-Counter (`APP_STATE_LIFE_COUNTER`)
|
## 🛠️ Part 2: Technical Specifications & Build Guide
|
||||||
A dedicated, high-contrast tournament-grade counter supporting the three most important Magic metrics:
|
|
||||||
* **Life Total (Starts at 40 Commander / 20 Standard):**
|
### Hardware Architecture
|
||||||
* Turn clockwise to gain life (green LED flash); turn counter-clockwise to lose life (red LED flash).
|
|
||||||
* **Click to Cycle Counter Types:**
|
| Component | Part / Model | Purpose |
|
||||||
1. **Life Total:** Standard life tracking.
|
| :--- | :--- | :--- |
|
||||||
2. **Poison Counters (0–10):** In MTG, taking 10 poison counters loses the game.
|
| **MCU** | ESP32-WROOM-32 (4MB Flash, no external PSRAM) | Dual-core 240 MHz processor |
|
||||||
3. **Commander Combat Damage (0–21):** Dealing 21 combat damage with a single commander eliminates a player.
|
| **Display** | 2.0" ST7789 IPS TFT LCD (240x320 resolution) | 16-bit RGB565 high-contrast screen |
|
||||||
* **8-Bit Dramatic Loss Tone:** Reaching 0 life or 10 poison triggers an 8-bit descending defeat stinger and a red alert banner.
|
| **RFID Reader** | MFRC522 (13.56 MHz, SPI Interface) | Reads ISO/IEC 14443A tags & stickers |
|
||||||
|
| **User Input** | EC11 Rotary Encoder with push button | Tabletop navigation & life adjustment |
|
||||||
|
| **Audio** | Passive Piezo Buzzer (GPIO 14) | LEDC PWM multi-frequency tones |
|
||||||
|
| **Tags** | NTAG213 / NTAG215 micro FPC stickers | Thin adhesive tags embedded inside sleeves |
|
||||||
|
| **Capacitor** | **2200 µF Electrolytic Capacitor** | **CRITICAL**: Placed across MFRC522 VCC/GND to absorb RF transmit current spikes |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4. Spindown D20 / D6 / Fair Coin Flip (`APP_STATE_DICE_ROLLER`)
|
### GPIO Wiring & Pinout
|
||||||
* **Rotate knob** to select between **D20 (Spindown)**, **D6**, or **Coin Flip**, then **click to roll**.
|
|
||||||
* Plays an interactive rolling shuffle animation with rapid tick sounds.
|
|
||||||
* **Natural 20 (Critical Hit):** Plays the 8-bit **Victory Fanfare** and flashes brilliant green.
|
|
||||||
* **Natural 1 (Critical Fail):** Sounds a buzzer tone and flashes red.
|
|
||||||
* **D6 Mode:** Quick six-sided die roll for ability triggers and random player selection.
|
|
||||||
* **Coin Flip Mode:** Random 50/50 flip landing on **HEADS (Green)** or **TAILS (Amber)**.
|
|
||||||
|
|
||||||
---
|
The display and RFID reader run on **two separate SPI host controllers** to prevent SPI bus contention and clock frequency mismatches:
|
||||||
|
|
||||||
### 5. Built-in Token & Counter Spawner (`APP_STATE_TOKEN_SPAWNER`)
|
|
||||||
An interactive tabletop tool for spawning and managing tokens on the battlefield:
|
|
||||||
* **Pre-configured Tokens:**
|
|
||||||
* **Treasure** (Artifact Token) — `{T}, Sacrifice: Add one mana of any color.`
|
|
||||||
* **Food** (Artifact Token) — `{2}, {T}, Sacrifice: You gain 3 life.`
|
|
||||||
* **Clue** (Artifact Token) — `{2}, Sacrifice: Draw a card.`
|
|
||||||
* **1/1 Soldier** (White Creature Token) — With full P/T box.
|
|
||||||
* **2/2 Zombie** (Black Creature Token) — With full P/T box.
|
|
||||||
* **1/1 Goblin** (Red Creature Token) — With full P/T box.
|
|
||||||
* **3/3 Beast** (Green Creature Token) — With full P/T box.
|
|
||||||
* **+1/+1 Counter** (Permanent Modifier) — Universal stat counter.
|
|
||||||
* **Controls:**
|
|
||||||
* **Pick Mode:** Rotate the knob to cycle through tokens (`< 1 OF 8 >`).
|
|
||||||
* **Edit Mode:** Click the knob to select; rotate to increment/decrement count (`0` to `99`). Click again to save. Token quantities persist throughout the play session.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. Commander Tax & Turn Tracker (`APP_STATE_COMMANDER_TRACKER`)
|
|
||||||
In Commander (EDH), casting your commander costs an additional {2} generic mana for each time it has previously been cast from the command zone.
|
|
||||||
* **Commander Casts:** Rotate the knob to adjust casts; the screen automatically calculates and displays the total tax (e.g., `Cast 3 = +4 Mana`).
|
|
||||||
* **Turn Counter:** Click the button to switch to the table turn tracker.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 7. Deck & Storage Manager (`APP_STATE_STORAGE_MANAGER`)
|
|
||||||
View flash disk usage and manage cards stored in LittleFS:
|
|
||||||
* **Storage Metrics:** Displays total storage space (~1.9 MB), used bytes, and counts of cached JSON cards and art crop images.
|
|
||||||
* **Wipe All Cards (New Deck Mode):** Switching to a new deck? Rotate to highlight "Wipe All Cards", click, and confirm. This purges `/spiffs/cards/*` and resets `mappings.json`, freeing space for a completely new 100-card deck with full artwork.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 8. Web Registration, SoftAP Pairing & Scanned Cards Overview (`web_server`)
|
|
||||||
Pair tags and manage your entire scanned deck directly from your smartphone browser—no host software required:
|
|
||||||
1. Connect your phone or laptop to the Wi-Fi network: **`MTG-Companion`** (open network, default IP `192.168.4.1`).
|
|
||||||
2. Open `http://192.168.4.1` in your browser.
|
|
||||||
3. **Pair New Tag:**
|
|
||||||
* Tap an RFID tag to the reader — the UID populates automatically.
|
|
||||||
* Type the card name (Scryfall autocomplete suggestions appear in real time).
|
|
||||||
* Click **Pair Card**. The binding is saved to `/spiffs/mappings.json` and the firmware fetches the card details and cover art over Wi-Fi.
|
|
||||||
4. **Scanned / Paired Cards Overview:**
|
|
||||||
* View an alphabetized list of all registered cards in your deck with live card counter (`Paired Cards (X)`).
|
|
||||||
* Displays the card title, tag UID, and cover art thumbnails streamed directly from LittleFS flash storage (`/api/art?uid=...`).
|
|
||||||
* **Unbind Card:** Click "Unbind" next to any card to immediately delete its mapping and clean up its cached JSON and JPEG files from flash memory.
|
|
||||||
* **Refresh:** Click "Refresh" to re-sync the list after scans or edits.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 9. Device Info Screen (`APP_STATE_DEVICE_INFO`)
|
|
||||||
Real-time diagnostic screen showing:
|
|
||||||
* Wi-Fi STA connection status, SSID, and assigned IP address.
|
|
||||||
* SoftAP network details (`MTG-Companion`, `192.168.4.1`).
|
|
||||||
* Free internal heap SRAM (typically ~180 KB free during active rendering).
|
|
||||||
* System uptime and LittleFS partition status.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎮 Quick Controls Cheat Sheet
|
|
||||||
|
|
||||||
| Action | Control |
|
|
||||||
| :--- | :--- |
|
|
||||||
| **Navigate / Adjust** | Rotate EC11 rotary knob clockwise / counter-clockwise |
|
|
||||||
| **Select / Roll / Toggle** | Short press encoder button |
|
|
||||||
| **Universal Back (To Menu)** | **Long press encoder button ($\ge$ 0.75s)** (double chirp + purple flash) |
|
|
||||||
| **Instant Card View** | Tap any RFID card sleeve to reader from **any** screen |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ Hardware Requirements
|
|
||||||
|
|
||||||
- **ESP32-WROOM-32** development board (4MB flash, dual-core LX6, no external PSRAM needed).
|
|
||||||
- **ST7789** SPI TFT LCD, 240x320 resolution (2.0" or 2.4" breakout).
|
|
||||||
- **MFRC522** RFID reader module (13.56 MHz, SPI).
|
|
||||||
- **EC11** rotary encoder with integrated push switch.
|
|
||||||
- **WS2812B / Neopixel** (single addressable RGB LED).
|
|
||||||
- **Passive piezo buzzer** (driven via LEDC PWM).
|
|
||||||
- **Antenna-friendly NTAG213/NTAG215** micro FPC stickers (for card sleeves) or standard MIFARE cards.
|
|
||||||
- **2200 µF Electrolytic Capacitor** (placed directly across MFRC522 VCC and GND) — **CRITICAL**: absorbs RF transmit spikes and prevents 3.3V rail voltage dips that trigger ESP32 brownouts or MFRC522 register resets.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔌 Peripheral Wiring & Pinout
|
|
||||||
|
|
||||||
The display and RFID reader use **two separate SPI host controllers** to prevent SPI bus contention and clock mismatch:
|
|
||||||
- **ST7789 Display:** `SPI2_HOST` (VSPI) at 20 MHz.
|
- **ST7789 Display:** `SPI2_HOST` (VSPI) at 20 MHz.
|
||||||
- **MFRC522 Reader:** `SPI3_HOST` (HSPI) at 1 MHz.
|
- **MFRC522 Reader:** `SPI3_HOST` (HSPI) at 1 MHz.
|
||||||
|
|
||||||
| Peripheral | Function | ESP32 GPIO | Notes |
|
| Peripheral | Functional Pin | ESP32 GPIO | Notes |
|
||||||
| :--- | :--- | :---: | :--- |
|
| :--- | :--- | :---: | :--- |
|
||||||
| **ST7789 Display** (SPI2) | SCL (Clock) | **18** | SPI2_HOST (VSPI) clock |
|
| **ST7789 Display** (SPI2) | SCL (Clock) | **GPIO 18** | SPI2_HOST (VSPI) Clock |
|
||||||
| | SDA (MOSI) | **23** | SPI2_HOST MOSI |
|
| | SDA (MOSI) | **GPIO 23** | SPI2_HOST MOSI |
|
||||||
| | CS | **21** | Dedicated Chip Select (non-strapping pin) |
|
| | CS | **GPIO 21** | Dedicated Chip Select (non-strapping) |
|
||||||
| | DC | **19** | Data / Command select (non-strapping pin) |
|
| | DC | **GPIO 19** | Data / Command Select (non-strapping) |
|
||||||
| | RST | **4** | Display Reset |
|
| | RST | **GPIO 4** | Display Reset |
|
||||||
| | VCC / GND | 3.3V / GND | Display logic & backlight power |
|
| | VCC / GND | 3.3V / GND | Display logic & backlight power |
|
||||||
| **MFRC522 RFID** (SPI3) | SCK | **16** | SPI3_HOST (HSPI) clock |
|
| **MFRC522 RFID** (SPI3) | SCK | **GPIO 16** | SPI3_HOST (HSPI) Clock |
|
||||||
| | MOSI | **17** | SPI3_HOST MOSI |
|
| | MOSI | **GPIO 17** | SPI3_HOST MOSI |
|
||||||
| | MISO | **13** | SPI3_HOST MISO |
|
| | MISO | **GPIO 13** | SPI3_HOST MISO |
|
||||||
| | SDA (CS) | **26** | Dedicated Chip Select |
|
| | SDA (CS) | **GPIO 26** | Dedicated Chip Select |
|
||||||
| | RST | **22** | Hard reset line |
|
| | RST | **GPIO 22** | Reader Reset |
|
||||||
| | 3.3V / GND | 3.3V / GND | **Attach 2200 µF capacitor across these pins!** |
|
| | 3.3V / GND | 3.3V / GND | **Attach 2200 µF capacitor across these pins!** |
|
||||||
| **EC11 Rotary Encoder** | CLK (A) | **32** | Quadrature A (interrupt-driven, internal pull-up) |
|
| **EC11 Rotary Encoder** | CLK (A) | **GPIO 32** | Quadrature A (interrupt-driven) |
|
||||||
| | DT (B) | **33** | Quadrature B (internal pull-up) |
|
| | DT (B) | **GPIO 33** | Quadrature B |
|
||||||
| | SW (Button) | **25** | Push button (active LOW, internal pull-up) |
|
| | SW (Button) | **GPIO 25** | Push button (active LOW) |
|
||||||
| **Indicators** | WS2812B DIN | **27** | RMT peripheral Tx (supply 5V to VDD, common GND) |
|
| **Audio** | Buzzer SIG | **GPIO 14** | LEDC PWM audio output |
|
||||||
| **Audio** | Buzzer SIG | **14** | LEDC PWM channel |
|
|
||||||
|
|
||||||
### Wiring Diagram
|
#### Wiring Diagram
|
||||||
```
|
```
|
||||||
ESP32-WROOM-32
|
ESP32-WROOM-32
|
||||||
┌────────────────────────────┐
|
┌────────────────────────────┐
|
||||||
@@ -182,13 +151,12 @@ The display and RFID reader use **two separate SPI host controllers** to prevent
|
|||||||
│ 13 ── MISO ────┼──┘ │
|
│ 13 ── MISO ────┼──┘ │
|
||||||
│ 26 ── SDA(CS) ─┘ │
|
│ 26 ── SDA(CS) ─┘ │
|
||||||
│ 22 ── RST ─┐ │
|
│ 22 ── RST ─┐ │
|
||||||
│ 21 ── CS ──┼───────┤ ST7789 (7-pin, SPI2_HOST)
|
│ 21 ── CS ──┼───────┤ ST7789 Display (SPI2_HOST)
|
||||||
│ 19 ── DC ──┤ │ SCL=18, SDA=23, CS=21, DC=19, RST=4
|
│ 19 ── DC ──┤ │ SCL=18, SDA=23, CS=21, DC=19, RST=4
|
||||||
│ 4 ── RST ─┘ │ VCC=3.3V, GND=GND
|
│ 4 ── RST ─┘ │ VCC=3.3V, GND=GND
|
||||||
│ 18 ── SCL ─────────┘
|
│ 18 ── SCL ─────────┘
|
||||||
│ 23 ── SDA ─────────
|
│ 23 ── SDA ─────────
|
||||||
│ 32 ── CLK ────────┤ EC11 Rotary Encoder (CLK=32, DT=33, SW=25)
|
│ 32 ── CLK ────────┤ EC11 Rotary Encoder (CLK=32, DT=33, SW=25)
|
||||||
│ 27 ── Din ────────┤ WS2812B LED (GPIO 27, 5V rail)
|
|
||||||
│ 14 ── SIG ────────┤ Piezo Buzzer (GPIO 14)
|
│ 14 ── SIG ────────┤ Piezo Buzzer (GPIO 14)
|
||||||
└────────────────────────────┘
|
└────────────────────────────┘
|
||||||
│ │
|
│ │
|
||||||
@@ -200,57 +168,93 @@ The display and RFID reader use **two separate SPI host controllers** to prevent
|
|||||||
└────────────────────────────┘
|
└────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note on Flashing:** GPIO21 (CS) and GPIO19 (DC) are deliberate non-strapping pins. The ST7789 display can remain fully connected during `idf.py flash`.
|
---
|
||||||
|
|
||||||
|
### Flash Partition Layout (4MB Dual-OTA)
|
||||||
|
|
||||||
|
Configured in [`partitions.csv`](file:///c:/Users/rsben/Desktop/Rasmus/Programmering/espMTG/partitions.csv):
|
||||||
|
|
||||||
|
| Partition | Type | SubType | Offset | Size | Purpose |
|
||||||
|
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||||
|
| `nvs` | data | nvs | `0x9000` | 16 KB | Wi-Fi credentials & system settings |
|
||||||
|
| `otadata` | data | ota | `0xd000` | 8 KB | Active OTA slot pointer & rollback state |
|
||||||
|
| `phy_init` | data | phy | `0xf000` | 4 KB | RF calibration data |
|
||||||
|
| `ota_0` | app | ota_0 | `0x10000` | 1,600 KB | Primary firmware slot |
|
||||||
|
| `ota_1` | app | ota_1 | `0x1a0000` | 1,600 KB | Secondary firmware slot |
|
||||||
|
| `storage` | data | littlefs | `0x330000` | 832 KB | LittleFS flash database (`/spiffs`) |
|
||||||
|
|
||||||
|
* **Rollback Safety:** Uses `CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y`. On first boot of a new firmware version, the app must call `esp_ota_mark_app_valid_cancel_rollback()`. If an update is corrupt or crashes on boot, the bootloader automatically reverts to the previous working slot.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💻 Software Setup & Build
|
### FreeRTOS Dual-Core Architecture
|
||||||
|
|
||||||
### 1. Requirements
|
* **Core 0 (Networking, Storage & Hardware I/O):**
|
||||||
- **ESP-IDF v5.x** (v5.0 through v5.5 supported):
|
* `rc522_polling_task`: Continuously polls the MFRC522 reader.
|
||||||
```powershell
|
* `net_task`: Manages Wi-Fi auto-reconnection and Scryfall HTTPS API downloads (`esp_http_client`).
|
||||||
. C:\Espressif\frameworks\esp-idf-v5.5\export.ps1
|
* `httpd`: Runs the embedded web server (serves the pairing SPA and REST APIs).
|
||||||
idf.py --version
|
* `ota_check` / `ota_update`: Queries Gitea release API and streams firmware updates into the inactive OTA bank.
|
||||||
```
|
* **Core 1 (User Interface & Rendering):**
|
||||||
|
* `ui_task`: Drives the LVGL display subsystem, rotary encoder state machine, and screen animations at 40 FPS without I/O stutter.
|
||||||
|
|
||||||
### 2. Configure Wi-Fi
|
---
|
||||||
Configure home Wi-Fi credentials for automatic Scryfall fetching:
|
|
||||||
|
### Software Setup & Build Commands
|
||||||
|
|
||||||
|
#### 1. Prerequisites
|
||||||
|
Install [ESP-IDF v5.x](https://docs.espressif.com/projects/esp-idf/en/v5.5/esp32/get-started/):
|
||||||
|
```powershell
|
||||||
|
. C:\Espressif\frameworks\esp-idf-v5.5\export.ps1
|
||||||
|
idf.py --version
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Configuration
|
||||||
|
Configure Wi-Fi credentials and Gitea instance details:
|
||||||
```powershell
|
```powershell
|
||||||
idf.py menuconfig
|
idf.py menuconfig
|
||||||
# Navigate to: MTG RFID Companion -> Wi-Fi SSID & Password
|
# Navigate to: "MTG RFID Companion"
|
||||||
|
# - Wi-Fi SSID & Password
|
||||||
|
# - Gitea instance base URL (e.g. https://git.example.com)
|
||||||
|
# - Gitea repository owner & repo name
|
||||||
```
|
```
|
||||||
*(Or edit `sdkconfig.defaults.esp32` directly).*
|
|
||||||
|
|
||||||
### 3. Build & Flash
|
#### 3. Build & Flash (Initial USB Provisioning)
|
||||||
```powershell
|
```powershell
|
||||||
idf.py set-target esp32
|
idf.py set-target esp32
|
||||||
idf.py build
|
idf.py build
|
||||||
idf.py -p COM4 flash monitor
|
idf.py -p COM4 erase-flash flash monitor
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📂 LittleFS Storage Structure
|
### Automated CI / Gitea Actions Workflow
|
||||||
|
|
||||||
|
The repository includes a Gitea Actions workflow in [`.gitea/workflows/releases.yml`](file:///c:/Users/rsben/Desktop/Rasmus/Programmering/espMTG/.gitea/workflows/releases.yml):
|
||||||
|
* **Trigger:** Pushing any version tag (e.g. `git tag v1.0.1 && git push origin v1.0.1`).
|
||||||
|
* **Environment:** Official `docker://espressif/idf:v5.5` container.
|
||||||
|
* **Artifacts:** Compiles the firmware and automatically attaches `mtg-rfid-companion.bin` (for wireless OTA updates) and `full-flash-bundle-*.tar.gz` (for USB cable flashing) directly to the Gitea release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### LittleFS Storage Structure
|
||||||
|
|
||||||
|
All local card details and artwork are stored in LittleFS mounted at `/spiffs`:
|
||||||
```
|
```
|
||||||
/spiffs/
|
/spiffs/
|
||||||
├── mappings.json UID -> Card Name JSON map
|
├── mappings.json # Scanned UID -> Card Name mapping table
|
||||||
└── cards/
|
└── cards/
|
||||||
├── <UID>.json Scryfall attributes (Mana, Type, Oracle, P/T)
|
├── <UID>.json # Cached Scryfall card data (Mana, Oracle, Type, P/T)
|
||||||
└── <UID>.jpg Cached 200x110 Baseline JPEG art crop
|
└── <UID>.jpg # Cached 200x110 Baseline JPEG art crop
|
||||||
```
|
```
|
||||||
|
|
||||||
The `storage/` repository directory is compiled directly into the LittleFS partition image at build time (`littlefs_create_partition_image`).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ❓ Troubleshooting
|
### ❓ Troubleshooting
|
||||||
|
|
||||||
| Symptom | Cause & Solution |
|
| Symptom | Cause & Solution |
|
||||||
| :--- | :--- |
|
| :--- | :--- |
|
||||||
| **ESP32 reboots when scanning card** | 3.3V rail voltage sag caused by the MFRC522 RF transmitter. **Add a 2200 µF capacitor** directly across the MFRC522 VCC and GND pins. |
|
| **Device reboots when scanning a card** | Power rail sag caused by the RF transmitter. **Solder a 2200 µF capacitor** directly across the MFRC522 VCC and GND pins. |
|
||||||
| **Display colors inverted (blues look red)** | Check `flags.swap_color_bytes = 1` in JPEG config and `esp_lcd_panel_invert_color()` in `display_manager.c`. Panel clones differ between RGB and BGR. |
|
| **Display colors inverted (blues look red)** | Check `flags.swap_color_bytes = 1` in JPEG config and `esp_lcd_panel_invert_color()` in `display_manager.c`. |
|
||||||
| **Card shows "PARSE FAILED"** | Corrupt card JSON in cache. The firmware auto-purges corrupt entries on reboot, or use **Deck Storage -> Wipe** to reset. |
|
| **Card displays "Could not fetch card"** | Device is unable to reach Scryfall. Verify your home Wi-Fi credentials in `menuconfig` or verify card spelling on the web pairing page. |
|
||||||
| **Scanned card shows previously paired card** | Re-pairing a card now automatically purges the old cache. Ensure firmware is up to date, or clear via **Deck Storage -> Wipe**. |
|
| **Router displays device name as "espressif"** | Hostname is set to `mtg-companion`. Clear DHCP lease on your router or reboot the ESP32 after connecting. |
|
||||||
| **Art crop doesn't show** | Ensure device has Wi-Fi connectivity to download from Scryfall. Images are cached locally on flash once downloaded. |
|
| **Flash fails: `Failed to connect to ESP32`** | Ensure no serial monitor is holding COM4 open (`Ctrl + ]` to exit monitor). Hold the device BOOT button while plugging into USB. |
|
||||||
| **Flash fails: `Failed to connect to ESP32`** | Make sure no other terminal is monitoring COM4 (`Ctrl + ]` to exit monitor). If using older GPIO15/2 wiring, hold the BOOT button during connect. |
|
|
||||||
75
components/rc522/.clang-format
Normal file
75
components/rc522/.clang-format
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: WebKit
|
||||||
|
AlignConsecutiveBitFields: true
|
||||||
|
AlignConsecutiveMacros: true
|
||||||
|
AlignEscapedNewlines: Right
|
||||||
|
AlignOperands: true
|
||||||
|
AlignTrailingComments: true
|
||||||
|
AllowAllArgumentsOnNextLine: false
|
||||||
|
AllowAllConstructorInitializersOnNextLine: true
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
|
AllowShortBlocksOnASingleLine: true
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
|
AllowShortLambdasOnASingleLine: All
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes
|
||||||
|
BinPackArguments: false
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BitFieldColonSpacing: Before
|
||||||
|
PenaltyBreakBeforeFirstCallParameter: 0
|
||||||
|
BraceWrapping:
|
||||||
|
AfterCaseLabel: false
|
||||||
|
AfterClass: true
|
||||||
|
AfterControlStatement: false
|
||||||
|
AfterEnum: true
|
||||||
|
AfterFunction: true
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterObjCDeclaration: true
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: true
|
||||||
|
AfterExternBlock: false
|
||||||
|
BeforeCatch: true
|
||||||
|
BeforeElse: true
|
||||||
|
BeforeLambdaBody: false
|
||||||
|
BeforeWhile: true
|
||||||
|
IndentBraces: false
|
||||||
|
SplitEmptyFunction: true
|
||||||
|
SplitEmptyRecord: true
|
||||||
|
SplitEmptyNamespace: false
|
||||||
|
BreakBeforeBinaryOperators: NonAssignment
|
||||||
|
BreakBeforeTernaryOperators: true
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
ColumnLimit: 120
|
||||||
|
CompactNamespaces: true
|
||||||
|
Cpp11BracedListStyle: false
|
||||||
|
FixNamespaceComments: true
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 4
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
|
NamespaceIndentation: Inner
|
||||||
|
PointerAlignment: Right
|
||||||
|
ReflowComments: true
|
||||||
|
SortIncludes: false
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceAfterLogicalNot: false
|
||||||
|
SpaceAfterTemplateKeyword: false
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeInheritanceColon: false
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: false
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Auto
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
InsertNewlineAtEOF: true
|
||||||
|
InsertTrailingCommas: Wrapped
|
||||||
|
LineEnding: LF
|
||||||
1
components/rc522/.component_hash
Normal file
1
components/rc522/.component_hash
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3302c1e7e19ee75c37e3e7284e86d0f624b56b7c5c55929c4ed1a1bdd28b62c5
|
||||||
6
components/rc522/.gitignore
vendored
Normal file
6
components/rc522/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/dist
|
||||||
|
**/build/
|
||||||
|
sdkconfig.old
|
||||||
|
sdkconfig
|
||||||
|
.vscode
|
||||||
|
dependencies.lock
|
||||||
1
components/rc522/CHECKSUMS.json
Normal file
1
components/rc522/CHECKSUMS.json
Normal file
File diff suppressed because one or more lines are too long
47
components/rc522/CMakeLists.txt
Normal file
47
components/rc522/CMakeLists.txt
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
SET (RC522_REQUIRES
|
||||||
|
esp_event
|
||||||
|
esp_driver_gpio
|
||||||
|
esp_driver_i2c
|
||||||
|
esp_timer
|
||||||
|
)
|
||||||
|
|
||||||
|
if(${IDF_TARGET} STREQUAL "linux") # tests
|
||||||
|
list(APPEND RC522_REQUIRES driver)
|
||||||
|
else()
|
||||||
|
list(APPEND RC522_REQUIRES esp_driver_spi)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
idf_component_register(
|
||||||
|
INCLUDE_DIRS
|
||||||
|
include
|
||||||
|
internal
|
||||||
|
SRCS
|
||||||
|
src/rc522.c
|
||||||
|
src/rc522_helpers.c
|
||||||
|
src/rc522_pcd.c
|
||||||
|
src/rc522_picc.c
|
||||||
|
src/picc/rc522_mifare.c
|
||||||
|
src/picc/rc522_nxp.c
|
||||||
|
src/rc522_driver.c
|
||||||
|
src/driver/rc522_spi.c
|
||||||
|
src/driver/rc522_i2c.c
|
||||||
|
REQUIRES
|
||||||
|
${RC522_REQUIRES}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_options(${COMPONENT_LIB} PRIVATE
|
||||||
|
-Werror # Warning as error
|
||||||
|
#-pedantic
|
||||||
|
-Wextra
|
||||||
|
-Wall
|
||||||
|
-Wshadow
|
||||||
|
-Wcast-qual
|
||||||
|
-Wswitch-default
|
||||||
|
#-Wswitch-enum
|
||||||
|
#-Wconversion
|
||||||
|
-Wunreachable-code
|
||||||
|
-Wuninitialized
|
||||||
|
-Winit-self
|
||||||
|
-Wpointer-arith
|
||||||
|
-Werror-implicit-function-declaration
|
||||||
|
)
|
||||||
14
components/rc522/Kconfig
Normal file
14
components/rc522/Kconfig
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
menu "RC522"
|
||||||
|
|
||||||
|
config RC522_PREVENT_SECTOR_TRAILER_WRITE
|
||||||
|
bool "Prevent writing to the Sector Trailer block"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
The Sector Trailer block stores authentication keys
|
||||||
|
and access bits. Enabling this option prevents writing
|
||||||
|
to the Trailer block. This is enabled by default to protect
|
||||||
|
inexperienced users from accidentally overwriting keys or
|
||||||
|
writing incorrect access bits, which could render the sector
|
||||||
|
unusable.
|
||||||
|
|
||||||
|
endmenu
|
||||||
202
components/rc522/LICENSE
Normal file
202
components/rc522/LICENSE
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [2024] [Alija Bobija]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
109
components/rc522/README.md
Normal file
109
components/rc522/README.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# esp-idf-rc522
|
||||||
|
|
||||||
|
 [](https://components.espressif.com/components/abobija/rc522)
|
||||||
|
|
||||||
|
This repository contains [ESP-IDF](https://github.com/espressif/esp-idf) library (component) for communication with RFID cards using [MFRC522](https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf) reader.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Library takes care of polling the cards and managing the card lifecycle. It also fires events when a card is detected, removed, or when the card changes to any state described in ISO-14443. Additionally, it provides an API for reading and writing to card memory blocks.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To install latest version of this component to your project, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
idf.py add-dependency "abobija/rc522"
|
||||||
|
```
|
||||||
|
|
||||||
|
Read more about esp-idf component manager in [official documentation](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/tools/idf-component-manager.html).
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- Full support: `MIFARE 1K`, `MIFARE 4K`, `MIFARE Mini`
|
||||||
|
- Partial support: Ultralight & NTAG families
|
||||||
|
- Supported commands: `GET_VERSION`, `READ`, `FAST_READ`, `WRITE`, `READ_CNT`,
|
||||||
|
`READ_SIG`, `PWD_AUTH`
|
||||||
|
- Supported features: PICC identification, read/write, page counts, password
|
||||||
|
authentication
|
||||||
|
- Unsupported commands: `INCR_CNT`, `WRITE_SIG`, `LOCK_SIG`, `AUTHENTICATE`,
|
||||||
|
`COMPAT_WRITE`, `VCSL`
|
||||||
|
- Unsupported features: signature validation, key authentication, high-level
|
||||||
|
configuration access (raw read + bit-manipulation only)
|
||||||
|
- No support: ISO-14443-4 compatible PICCs, other proprietary PICC protocols
|
||||||
|
- Communication protocols: `SPI` and `I2C`
|
||||||
|
|
||||||
|
See [examples](examples/) for example projects.
|
||||||
|
|
||||||
|
## Create project from example
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> To find more interesting examples (like [`memory_dump`](examples/memory_dump)), go to [examples](examples) folder.
|
||||||
|
|
||||||
|
To run [`basic`](examples/basic) example, create it as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
idf.py create-project-from-example "abobija/rc522:basic"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then build and flash it as usual:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd basic
|
||||||
|
idf.py build flash monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> [`basic`](examples/basic) example uses SPI communication. Find defined GPIO configuration in [basic.c](examples/basic/main/basic.c) file.
|
||||||
|
|
||||||
|
## Pin Layout
|
||||||
|
|
||||||
|
Pin layout is configurable by the user. To configure the GPIOs, check the `#define` statements in the [basic example](examples/basic/main/basic.c). If you are not using the RST pin, you can connect it to the 3.3V.
|
||||||
|
|
||||||
|
## Unit testing
|
||||||
|
|
||||||
|
To run unit tests, go to [`test`](test) directory and set target to `linux`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd test
|
||||||
|
idf.py --preview set-target linux
|
||||||
|
```
|
||||||
|
|
||||||
|
Then build the project and run tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
idf.py build && ./build/test.elf
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Mifare Classic cards use the Crypto-1 cipher for authentication and encryption, which has been [broken](https://eprint.iacr.org/2008/166) for a long time. As a result, it is not advisable to use Mifare Classic cards for security-sensitive applications. Instead, consider using Mifare Plus or Desfire cards, which utilize AES encryption.
|
||||||
|
- Even though block zero, which contains the UID, is typically considered as read-only, there are certain cards known as "magic" or "Chinese magic" cards that can be used to modify the UID. As a result, relying on the UID of a card as a secure identifier is not recommended.
|
||||||
|
|
||||||
|
## Terms
|
||||||
|
|
||||||
|
| Term | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
| PCD | Proximity Coupling Device (the card reader). In our case this is MFRC522 module |
|
||||||
|
| PICC | Proximity Integrated Circuit Card (e.g: rfid card, tag, ...) |
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [ISO/IEC 14443](https://en.wikipedia.org/wiki/ISO/IEC_14443) Identification cards - Contactless integrated circuit cards
|
||||||
|
- [ISO/IEC 14443-2](http://www.emutag.com/iso/14443-2.pdf) Radio frequency power and signal interface
|
||||||
|
- [ISO/IEC 14443-3](http://www.emutag.com/iso/14443-3.pdf) Initialization and anticollision
|
||||||
|
- [ISO/IEC 14443-4](http://www.emutag.com/iso/14443-4.pdf) Transmission protocol
|
||||||
|
- [MFRC522](https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf) MFRC522 - Standard performance MIFARE and NTAG frontend
|
||||||
|
- [AN10833](https://www.nxp.com/docs/en/application-note/AN10833.pdf) MIFARE type identification procedure
|
||||||
|
- [AN10834](https://www.nxp.com/docs/en/application-note/AN10834.pdf) MIFARE ISO/IEC 14443 PICC selection
|
||||||
|
- [MF1S50YYX_V1](https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf) MIFARE Classic EV1 1K
|
||||||
|
- [MF1S70YYX_V1](https://www.nxp.com/docs/en/data-sheet/MF1S70YYX_V1.pdf) MIFARE Classic EV1 4K
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
GitHub: [abobija](https://github.com/abobija)<br />
|
||||||
|
Homepage: [abobija.com](https://abobija.com)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This component is provided under Apache 2.0 license, see [LICENSE](LICENSE) file for details.
|
||||||
8
components/rc522/examples/basic/CMakeLists.txt
Normal file
8
components/rc522/examples/basic/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# For more information about build system see
|
||||||
|
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||||
|
# The following five lines of boilerplate have to be in your project's
|
||||||
|
# CMakeLists in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(basic)
|
||||||
2
components/rc522/examples/basic/main/CMakeLists.txt
Normal file
2
components/rc522/examples/basic/main/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
idf_component_register(SRCS "basic.c"
|
||||||
|
INCLUDE_DIRS ".")
|
||||||
55
components/rc522/examples/basic/main/basic.c
Normal file
55
components/rc522/examples/basic/main/basic.c
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#include <esp_log.h>
|
||||||
|
#include "rc522.h"
|
||||||
|
#include "driver/rc522_spi.h"
|
||||||
|
#include "rc522_picc.h"
|
||||||
|
|
||||||
|
static const char *TAG = "rc522-basic-example";
|
||||||
|
|
||||||
|
#define RC522_SPI_BUS_GPIO_MISO (25)
|
||||||
|
#define RC522_SPI_BUS_GPIO_MOSI (23)
|
||||||
|
#define RC522_SPI_BUS_GPIO_SCLK (19)
|
||||||
|
#define RC522_SPI_SCANNER_GPIO_SDA (22)
|
||||||
|
#define RC522_SCANNER_GPIO_RST (-1) // soft-reset
|
||||||
|
|
||||||
|
static rc522_spi_config_t driver_config = {
|
||||||
|
.host_id = SPI3_HOST,
|
||||||
|
.bus_config = &(spi_bus_config_t){
|
||||||
|
.miso_io_num = RC522_SPI_BUS_GPIO_MISO,
|
||||||
|
.mosi_io_num = RC522_SPI_BUS_GPIO_MOSI,
|
||||||
|
.sclk_io_num = RC522_SPI_BUS_GPIO_SCLK,
|
||||||
|
},
|
||||||
|
.dev_config = {
|
||||||
|
.spics_io_num = RC522_SPI_SCANNER_GPIO_SDA,
|
||||||
|
},
|
||||||
|
.rst_io_num = RC522_SCANNER_GPIO_RST,
|
||||||
|
};
|
||||||
|
|
||||||
|
static rc522_driver_handle_t driver;
|
||||||
|
static rc522_handle_t scanner;
|
||||||
|
|
||||||
|
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
|
||||||
|
{
|
||||||
|
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
|
||||||
|
rc522_picc_t *picc = event->picc;
|
||||||
|
|
||||||
|
if (picc->state == RC522_PICC_STATE_ACTIVE) {
|
||||||
|
rc522_picc_print(picc);
|
||||||
|
}
|
||||||
|
else if (picc->state == RC522_PICC_STATE_IDLE && event->old_state >= RC522_PICC_STATE_ACTIVE) {
|
||||||
|
ESP_LOGI(TAG, "Card has been removed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main()
|
||||||
|
{
|
||||||
|
rc522_spi_create(&driver_config, &driver);
|
||||||
|
rc522_driver_install(driver);
|
||||||
|
|
||||||
|
rc522_config_t scanner_config = {
|
||||||
|
.driver = driver,
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_create(&scanner_config, &scanner);
|
||||||
|
rc522_register_events(scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL);
|
||||||
|
rc522_start(scanner);
|
||||||
|
}
|
||||||
3
components/rc522/examples/basic/main/idf_component.yml
Normal file
3
components/rc522/examples/basic/main/idf_component.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
dependencies:
|
||||||
|
abobija/rc522:
|
||||||
|
version: '*'
|
||||||
1
components/rc522/examples/basic/sdkconfig.defaults
Normal file
1
components/rc522/examples/basic/sdkconfig.defaults
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CONFIG_LOG_COLORS=y
|
||||||
8
components/rc522/examples/basic_i2c/CMakeLists.txt
Normal file
8
components/rc522/examples/basic_i2c/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# For more information about build system see
|
||||||
|
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||||
|
# The following five lines of boilerplate have to be in your project's
|
||||||
|
# CMakeLists in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(basic_i2c)
|
||||||
2
components/rc522/examples/basic_i2c/main/CMakeLists.txt
Normal file
2
components/rc522/examples/basic_i2c/main/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
idf_component_register(SRCS "basic_i2c.c"
|
||||||
|
INCLUDE_DIRS ".")
|
||||||
56
components/rc522/examples/basic_i2c/main/basic_i2c.c
Normal file
56
components/rc522/examples/basic_i2c/main/basic_i2c.c
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#include <esp_log.h>
|
||||||
|
#include "rc522.h"
|
||||||
|
#include "driver/rc522_i2c.h"
|
||||||
|
#include "rc522_picc.h"
|
||||||
|
|
||||||
|
static const char *TAG = "rc522-basic-i2c-example";
|
||||||
|
|
||||||
|
#define RC522_I2C_ADDRESS (0x28)
|
||||||
|
#define RC522_I2C_GPIO_SDA (18)
|
||||||
|
#define RC522_I2C_GPIO_SCL (21)
|
||||||
|
#define RC522_SCANNER_GPIO_RST (-1) // soft-reset
|
||||||
|
|
||||||
|
static rc522_i2c_config_t driver_config = {
|
||||||
|
.bus_config = &(i2c_master_bus_config_t){
|
||||||
|
.i2c_port = I2C_NUM_0,
|
||||||
|
.sda_io_num = RC522_I2C_GPIO_SDA,
|
||||||
|
.scl_io_num = RC522_I2C_GPIO_SCL,
|
||||||
|
.glitch_ignore_cnt = 7,
|
||||||
|
.flags.enable_internal_pullup = true,
|
||||||
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||||
|
},
|
||||||
|
.dev_config = {
|
||||||
|
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||||
|
.device_address = RC522_I2C_ADDRESS,
|
||||||
|
.scl_speed_hz = 100000,
|
||||||
|
},
|
||||||
|
.rst_io_num = RC522_SCANNER_GPIO_RST,
|
||||||
|
};
|
||||||
|
|
||||||
|
static rc522_driver_handle_t driver;
|
||||||
|
static rc522_handle_t scanner;
|
||||||
|
|
||||||
|
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
|
||||||
|
{
|
||||||
|
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
|
||||||
|
|
||||||
|
if (event->picc->state != RC522_PICC_STATE_ACTIVE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_picc_print(event->picc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main()
|
||||||
|
{
|
||||||
|
rc522_i2c_create(&driver_config, &driver);
|
||||||
|
rc522_driver_install(driver);
|
||||||
|
|
||||||
|
rc522_config_t scanner_config = {
|
||||||
|
.driver = driver,
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_create(&scanner_config, &scanner);
|
||||||
|
rc522_register_events(scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL);
|
||||||
|
rc522_start(scanner);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dependencies:
|
||||||
|
abobija/rc522:
|
||||||
|
version: '*'
|
||||||
1
components/rc522/examples/basic_i2c/sdkconfig.defaults
Normal file
1
components/rc522/examples/basic_i2c/sdkconfig.defaults
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CONFIG_LOG_COLORS=y
|
||||||
8
components/rc522/examples/memory_dump/CMakeLists.txt
Normal file
8
components/rc522/examples/memory_dump/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# For more information about build system see
|
||||||
|
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||||
|
# The following five lines of boilerplate have to be in your project's
|
||||||
|
# CMakeLists in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(memory_dump)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
idf_component_register(SRCS "memory_dump.c"
|
||||||
|
INCLUDE_DIRS ".")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dependencies:
|
||||||
|
abobija/rc522:
|
||||||
|
version: '*'
|
||||||
187
components/rc522/examples/memory_dump/main/memory_dump.c
Normal file
187
components/rc522/examples/memory_dump/main/memory_dump.c
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_check.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "rc522.h"
|
||||||
|
#include "driver/rc522_spi.h"
|
||||||
|
#include "picc/rc522_mifare.h"
|
||||||
|
|
||||||
|
static const char *TAG = "rc522-memory-dump-example";
|
||||||
|
|
||||||
|
#define RC522_SPI_BUS_GPIO_MISO (25)
|
||||||
|
#define RC522_SPI_BUS_GPIO_MOSI (23)
|
||||||
|
#define RC522_SPI_BUS_GPIO_SCLK (19)
|
||||||
|
#define RC522_SPI_SCANNER_GPIO_SDA (22)
|
||||||
|
#define RC522_SCANNER_GPIO_RST (-1) // soft-reset
|
||||||
|
|
||||||
|
static rc522_spi_config_t driver_config = {
|
||||||
|
.host_id = SPI3_HOST,
|
||||||
|
.bus_config = &(spi_bus_config_t){
|
||||||
|
.miso_io_num = RC522_SPI_BUS_GPIO_MISO,
|
||||||
|
.mosi_io_num = RC522_SPI_BUS_GPIO_MOSI,
|
||||||
|
.sclk_io_num = RC522_SPI_BUS_GPIO_SCLK,
|
||||||
|
},
|
||||||
|
.dev_config = {
|
||||||
|
.spics_io_num = RC522_SPI_SCANNER_GPIO_SDA,
|
||||||
|
},
|
||||||
|
.rst_io_num = RC522_SCANNER_GPIO_RST,
|
||||||
|
};
|
||||||
|
|
||||||
|
static rc522_driver_handle_t driver;
|
||||||
|
static rc522_handle_t scanner;
|
||||||
|
|
||||||
|
#define DUMP(format, ...) esp_log_write(ESP_LOG_INFO, TAG, format, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
static void dump_header()
|
||||||
|
{
|
||||||
|
DUMP("Sector Block Bytes AccessBits\n");
|
||||||
|
DUMP(" 0 1 2 3 4 5 6 7 8 9 11 12 15 c1 c2 c3\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dump_block(rc522_mifare_sector_block_t *block, uint8_t sector_index)
|
||||||
|
{
|
||||||
|
if (block->type == RC522_MIFARE_BLOCK_TRAILER) {
|
||||||
|
DUMP("%*d", 6, sector_index);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DUMP("%*s", 6, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block address
|
||||||
|
DUMP(" %*d", 5, block->address);
|
||||||
|
|
||||||
|
// Data
|
||||||
|
DUMP(" ");
|
||||||
|
for (uint8_t i = 0; i < 16; i++) {
|
||||||
|
DUMP("%02" RC522_X "", block->bytes[i]);
|
||||||
|
|
||||||
|
if ((i % 4) == 3) {
|
||||||
|
DUMP(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access bits
|
||||||
|
DUMP(" %d %d %d", block->access_bits.c1, block->access_bits.c2, block->access_bits.c3);
|
||||||
|
|
||||||
|
// String representation (if it's data block)
|
||||||
|
if (block->type == RC522_MIFARE_BLOCK_DATA) {
|
||||||
|
DUMP(" |");
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 16; i++) {
|
||||||
|
if (block->bytes[i] >= 32 && block->bytes[i] <= 126) { // standard ascii codes
|
||||||
|
DUMP(LOG_COLOR(LOG_COLOR_GREEN) "%c" LOG_RESET_COLOR, block->bytes[i]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DUMP("%c", '.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DUMP("|");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value (if it's value block)
|
||||||
|
else if (block->type == RC522_MIFARE_BLOCK_VALUE) {
|
||||||
|
DUMP(" (val=%ld (0x%04l" RC522_X "), adr=0x%02" RC522_X ")",
|
||||||
|
block->value_info.value,
|
||||||
|
block->value_info.value,
|
||||||
|
block->value_info.addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors and warnings
|
||||||
|
if (block->type == RC522_MIFARE_BLOCK_TRAILER && block->error == RC522_ERR_MIFARE_ACCESS_BITS_INTEGRITY_VIOLATION) {
|
||||||
|
DUMP(" ABITS_ERR");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block->type == RC522_MIFARE_BLOCK_VALUE && block->error == RC522_ERR_MIFARE_VALUE_BLOCK_INTEGRITY_VIOLATION) {
|
||||||
|
DUMP(" VAL_ERR");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Termination
|
||||||
|
DUMP("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t dump_memory(rc522_handle_t scanner, rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
rc522_mifare_key_t key = {
|
||||||
|
.value = { RC522_MIFARE_KEY_VALUE_DEFAULT },
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_mifare_desc_t mifare;
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_mifare_get_desc(picc, &mifare), TAG, "");
|
||||||
|
|
||||||
|
DUMP("\n");
|
||||||
|
dump_header();
|
||||||
|
|
||||||
|
// Start from the highest sector
|
||||||
|
uint8_t sector_index = mifare.number_of_sectors - 1;
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc522_mifare_sector_desc_t sector;
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_mifare_get_sector_desc(sector_index, §or), TAG, "");
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_mifare_auth_sector(scanner, picc, §or, &key), TAG, "");
|
||||||
|
|
||||||
|
rc522_mifare_sector_block_t trailer;
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_mifare_read_sector_trailer_block(scanner, picc, §or, &trailer), TAG, "");
|
||||||
|
|
||||||
|
dump_block(&trailer, sector_index);
|
||||||
|
|
||||||
|
// Start from the highest (non-trailer) block
|
||||||
|
uint8_t block_offset = sector.number_of_blocks - 2;
|
||||||
|
|
||||||
|
do {
|
||||||
|
rc522_mifare_sector_block_t block;
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_mifare_read_sector_block(scanner, picc, §or, &trailer, block_offset, &block),
|
||||||
|
TAG,
|
||||||
|
"");
|
||||||
|
|
||||||
|
dump_block(&block, sector_index);
|
||||||
|
}
|
||||||
|
while (block_offset--);
|
||||||
|
}
|
||||||
|
while (sector_index--);
|
||||||
|
|
||||||
|
DUMP("\n");
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
|
||||||
|
{
|
||||||
|
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
|
||||||
|
rc522_picc_t *picc = event->picc;
|
||||||
|
|
||||||
|
if (picc->state != RC522_PICC_STATE_ACTIVE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_picc_print(picc);
|
||||||
|
|
||||||
|
if (!rc522_mifare_type_is_classic_compatible(picc->type)) {
|
||||||
|
ESP_LOGW(TAG, "Card is not supported by this example");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dump_memory(scanner, picc) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Memory dump success");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ESP_LOGE(TAG, "Memory dump failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc522_mifare_deauth(scanner, picc) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Deauth failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main()
|
||||||
|
{
|
||||||
|
rc522_spi_create(&driver_config, &driver);
|
||||||
|
rc522_driver_install(driver);
|
||||||
|
|
||||||
|
rc522_config_t scanner_config = {
|
||||||
|
.driver = driver,
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_create(&scanner_config, &scanner);
|
||||||
|
rc522_register_events(scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL);
|
||||||
|
rc522_start(scanner);
|
||||||
|
}
|
||||||
1
components/rc522/examples/memory_dump/sdkconfig.defaults
Normal file
1
components/rc522/examples/memory_dump/sdkconfig.defaults
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CONFIG_LOG_COLORS=y
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# For more information about build system see
|
||||||
|
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||||
|
# The following five lines of boilerplate have to be in your project's
|
||||||
|
# CMakeLists in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(multiple_scanners)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
idf_component_register(SRCS "multiple_scanners.c"
|
||||||
|
INCLUDE_DIRS ".")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dependencies:
|
||||||
|
abobija/rc522:
|
||||||
|
version: '*'
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
#include <esp_log.h>
|
||||||
|
#include "rc522.h"
|
||||||
|
#include "driver/rc522_spi.h"
|
||||||
|
#include "rc522_picc.h"
|
||||||
|
|
||||||
|
static const char *TAG = "rc522-multiple-scanners-example";
|
||||||
|
|
||||||
|
// SPI bus GPIOs
|
||||||
|
|
||||||
|
#define RC522_SPI_BUS_GPIO_MISO (25)
|
||||||
|
#define RC522_SPI_BUS_GPIO_MOSI (23)
|
||||||
|
#define RC522_SPI_BUS_GPIO_SCLK (19)
|
||||||
|
|
||||||
|
// CS GPIOs for each scanner
|
||||||
|
|
||||||
|
#define RC522_SPI_SCANNER_1_GPIO_SDA (22)
|
||||||
|
#define RC522_SPI_SCANNER_2_GPIO_SDA (26)
|
||||||
|
|
||||||
|
// {{ Scanner configurations
|
||||||
|
|
||||||
|
static rc522_spi_config_t scanner_1_config = {
|
||||||
|
.host_id = SPI3_HOST,
|
||||||
|
.bus_config = &(spi_bus_config_t){
|
||||||
|
.miso_io_num = RC522_SPI_BUS_GPIO_MISO,
|
||||||
|
.mosi_io_num = RC522_SPI_BUS_GPIO_MOSI,
|
||||||
|
.sclk_io_num = RC522_SPI_BUS_GPIO_SCLK,
|
||||||
|
},
|
||||||
|
.dev_config = {
|
||||||
|
.spics_io_num = RC522_SPI_SCANNER_1_GPIO_SDA,
|
||||||
|
},
|
||||||
|
.rst_io_num = -1, // soft-reset
|
||||||
|
};
|
||||||
|
|
||||||
|
// Second scanner does not need bus configuration,
|
||||||
|
// since first scanner will initialize the bus.
|
||||||
|
|
||||||
|
static rc522_spi_config_t scanner_2_config = {
|
||||||
|
.host_id = SPI3_HOST,
|
||||||
|
.dev_config = {
|
||||||
|
.spics_io_num = RC522_SPI_SCANNER_2_GPIO_SDA,
|
||||||
|
},
|
||||||
|
.rst_io_num = -1, // soft-reset
|
||||||
|
};
|
||||||
|
|
||||||
|
// }}
|
||||||
|
|
||||||
|
// Drivers
|
||||||
|
|
||||||
|
static rc522_driver_handle_t driver_1;
|
||||||
|
static rc522_driver_handle_t driver_2;
|
||||||
|
|
||||||
|
// Scanners
|
||||||
|
|
||||||
|
static rc522_handle_t scanner_1;
|
||||||
|
static rc522_handle_t scanner_2;
|
||||||
|
|
||||||
|
// Event handler
|
||||||
|
|
||||||
|
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
|
||||||
|
{
|
||||||
|
rc522_handle_t scanner = (rc522_handle_t)arg;
|
||||||
|
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
|
||||||
|
rc522_picc_t *picc = event->picc;
|
||||||
|
|
||||||
|
uint8_t scanner_no = 1;
|
||||||
|
|
||||||
|
if (scanner == scanner_2) {
|
||||||
|
scanner_no = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (picc->state == RC522_PICC_STATE_ACTIVE) {
|
||||||
|
ESP_LOGI(TAG, "Card detected on the scanner #%d", scanner_no);
|
||||||
|
rc522_picc_print(picc);
|
||||||
|
}
|
||||||
|
else if (picc->state == RC522_PICC_STATE_IDLE && event->old_state >= RC522_PICC_STATE_ACTIVE) {
|
||||||
|
ESP_LOGI(TAG, "Card has been removed from the scanner #%d", scanner_no);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// App entry point
|
||||||
|
|
||||||
|
void app_main()
|
||||||
|
{
|
||||||
|
// Create drivers
|
||||||
|
|
||||||
|
rc522_spi_create(&scanner_1_config, &driver_1);
|
||||||
|
rc522_spi_create(&scanner_2_config, &driver_2);
|
||||||
|
|
||||||
|
// Install drivers
|
||||||
|
|
||||||
|
rc522_driver_install(driver_1);
|
||||||
|
rc522_driver_install(driver_2);
|
||||||
|
|
||||||
|
// Create scanners
|
||||||
|
|
||||||
|
rc522_create(
|
||||||
|
&(rc522_config_t) {
|
||||||
|
.driver = driver_1,
|
||||||
|
},
|
||||||
|
&scanner_1);
|
||||||
|
|
||||||
|
rc522_create(
|
||||||
|
&(rc522_config_t) {
|
||||||
|
.driver = driver_2,
|
||||||
|
},
|
||||||
|
&scanner_2);
|
||||||
|
|
||||||
|
// Register events for each scanner
|
||||||
|
|
||||||
|
rc522_register_events(scanner_1, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, scanner_1);
|
||||||
|
rc522_register_events(scanner_2, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, scanner_2);
|
||||||
|
|
||||||
|
// Start scanners
|
||||||
|
|
||||||
|
rc522_start(scanner_1);
|
||||||
|
rc522_start(scanner_2);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
CONFIG_LOG_COLORS=y
|
||||||
4
components/rc522/examples/picc_nxp/CMakeLists.txt
Normal file
4
components/rc522/examples/picc_nxp/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(picc_nxp)
|
||||||
65
components/rc522/examples/picc_nxp/example.log
Normal file
65
components/rc522/examples/picc_nxp/example.log
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
I (98) main_task: Started on CPU0
|
||||||
|
I (108) main_task: Calling app_main()
|
||||||
|
I (128) rc522: PCD (fw=unknown) initialized
|
||||||
|
I (128) main_task: Returned from app_main()
|
||||||
|
I (5088) rc522:
|
||||||
|
I (5088) rc522: ╔══════════════╗
|
||||||
|
I (5088) rc522: ║ ║ Type: NTAG215
|
||||||
|
I (5098) rc522: ║ RFID ║ UID: 04 26 F8 F5 9F 61 80
|
||||||
|
I (5098) rc522: ║ CARD ║ ATQA: 0x4400
|
||||||
|
I (5098) rc522: ║ ║ SAK: 0x00
|
||||||
|
I (5098) rc522: ╚══════════════╝
|
||||||
|
I (5098) rc522:
|
||||||
|
I (5098) rc522-picc-nxp-example: User pages: 126/135
|
||||||
|
I (5098) rc522-picc-nxp-example: Writing to address 05: R=00
|
||||||
|
I (5108) rc522-picc-nxp-example: Reading from address 05: R=00
|
||||||
|
I (5108) rc522-picc-nxp-example: Data read/write match
|
||||||
|
I (5108) rc522-picc-nxp-example: Pages 5 to 8: 10 20 30 40 F3 E7 42 BD 00 00 00 00 00 00 00 00
|
||||||
|
I (5108) rc522-picc-nxp-example: Dumping 3 pages: R=00
|
||||||
|
I (5108) rc522-picc-nxp-example: Page 004: 67 B6 A5 B4
|
||||||
|
I (5108) rc522-picc-nxp-example: Page 005: 10 20 30 40
|
||||||
|
I (5108) rc522-picc-nxp-example: Page 006: F3 E7 42 BD
|
||||||
|
I (5108) rc522-picc-nxp-example: Password auth: R=00
|
||||||
|
I (5118) rc522-picc-nxp-example: Read config bytes: R=00
|
||||||
|
I (5118) rc522-picc-nxp-example: NFC_CNT_EN bit: 0
|
||||||
|
I (5118) rc522-picc-nxp-example: Counter disabled; skipping
|
||||||
|
I (5118) rc522-picc-nxp-example: Read signature: R=00
|
||||||
|
I (5118) rc522-picc-nxp-example: Signature: B6 54 F4 00 A4 A5 6E 28 96 90 76 84 39 CA B4 CE 8E FC 3D 08 65 A3 29 60 1C 36 EA E4 B5 DA 74 7C
|
||||||
|
|
||||||
|
Page | Offset: +0 +1 +2 +3
|
||||||
|
00 | 04 26 F8 52 F5 9F 61 80 8B 48 00 00 E1 10 3E 00
|
||||||
|
04 | 67 B6 A5 B4* 10 20 30 40 F3 E7 42 BD 00 00 00 00 * Start of user memory (0x04)
|
||||||
|
08 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
0C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
10 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
14 | 1A 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
18 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
1C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
20 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
24 | 00 00 00 00 00 00 00 00 1A 2B 00 3C 00 00 00 00
|
||||||
|
28 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
2C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
30 | 2A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
34 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
38 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
3C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
40 | 2A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
44 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
48 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
4C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
50 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
54 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
58 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
5C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
60 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
64 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
68 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
6C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
70 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
74 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
78 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
7C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||||
|
80 | 00 00 00 00 00 00 00 00* 00 00 00 BD 04 00 00 FF * End of user memory (0x81)
|
||||||
|
84 | 00 05 00 00 00 00 00 00 00 00 00 00 04 26 F8 52
|
||||||
|
|
||||||
|
I (5258) rc522-picc-nxp-example: Memory dump success
|
||||||
2
components/rc522/examples/picc_nxp/main/CMakeLists.txt
Normal file
2
components/rc522/examples/picc_nxp/main/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
idf_component_register(SRCS "picc_nxp.c"
|
||||||
|
INCLUDE_DIRS ".")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dependencies:
|
||||||
|
abobija/rc522:
|
||||||
|
version: '*'
|
||||||
283
components/rc522/examples/picc_nxp/main/picc_nxp.c
Normal file
283
components/rc522/examples/picc_nxp/main/picc_nxp.c
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_check.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include "rc522.h"
|
||||||
|
#include "rc522_pcd.h"
|
||||||
|
#include "driver/rc522_spi.h"
|
||||||
|
#include "picc/rc522_mifare.h"
|
||||||
|
#include "picc/rc522_nxp.h"
|
||||||
|
|
||||||
|
#include "rc522_picc.h"
|
||||||
|
|
||||||
|
static const char *TAG = "rc522-picc-nxp-example";
|
||||||
|
|
||||||
|
#define RC522_SPI_BUS_GPIO_MISO (16)
|
||||||
|
#define RC522_SPI_BUS_GPIO_MOSI (15)
|
||||||
|
#define RC522_SPI_BUS_GPIO_SCLK (17)
|
||||||
|
#define RC522_SPI_SCANNER_GPIO_SDA (1)
|
||||||
|
#define RC522_SCANNER_GPIO_RST (-1) // soft-reset
|
||||||
|
|
||||||
|
static rc522_spi_config_t driver_config = {
|
||||||
|
.host_id = SPI3_HOST,
|
||||||
|
.bus_config = &(spi_bus_config_t){
|
||||||
|
.miso_io_num = RC522_SPI_BUS_GPIO_MISO,
|
||||||
|
.mosi_io_num = RC522_SPI_BUS_GPIO_MOSI,
|
||||||
|
.sclk_io_num = RC522_SPI_BUS_GPIO_SCLK,
|
||||||
|
},
|
||||||
|
.dev_config = {
|
||||||
|
.spics_io_num = RC522_SPI_SCANNER_GPIO_SDA,
|
||||||
|
},
|
||||||
|
.rst_io_num = RC522_SCANNER_GPIO_RST,
|
||||||
|
};
|
||||||
|
|
||||||
|
static rc522_driver_handle_t driver;
|
||||||
|
static rc522_handle_t scanner;
|
||||||
|
static char str_buf[128];
|
||||||
|
|
||||||
|
#define DUMP(format, ...) esp_log_write(ESP_LOG_INFO, TAG, format, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
static void dump_header()
|
||||||
|
{
|
||||||
|
DUMP("Page | Offset: +0 +1 +2 +3\n");
|
||||||
|
// 00 | 10 20 30 40 50 60 70 80 90 A0 B0 C0 D0 E0 F0 11
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump four pages (size of a single READ) to output, annotating the start
|
||||||
|
// and end pages of user memory if present
|
||||||
|
static void dump_pages(uint8_t address, uint8_t page[RC522_NXP_READ_SIZE], rc522_picc_type_t picc_type)
|
||||||
|
{
|
||||||
|
// Get the start and end pages of user memory so we can mark them in the dump
|
||||||
|
uint8_t start_page = rc522_nxp_get_user_mem_start(picc_type);
|
||||||
|
uint8_t end_page = rc522_nxp_get_user_mem_end(picc_type);
|
||||||
|
// 0: nothing of interest, 1: start page, 2: end page
|
||||||
|
uint8_t found = 0;
|
||||||
|
DUMP(" %02X |", address);
|
||||||
|
for (uint8_t i = 0; i < RC522_NXP_READ_SIZE; i++) {
|
||||||
|
DUMP(" %02X", page[i]);
|
||||||
|
if (i / 4 + address == start_page && i % 4 == 3) {
|
||||||
|
found = 1;
|
||||||
|
DUMP("*");
|
||||||
|
}
|
||||||
|
else if (i / 4 + address == end_page && i % 4 == 3) {
|
||||||
|
found = 2;
|
||||||
|
DUMP("*");
|
||||||
|
}
|
||||||
|
else if (i % 4 == 3) {
|
||||||
|
DUMP(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found == 1) {
|
||||||
|
DUMP(" * Start of user memory (0x%02X)", start_page);
|
||||||
|
}
|
||||||
|
else if (found == 2) {
|
||||||
|
DUMP(" * End of user memory (0x%02X)", end_page);
|
||||||
|
}
|
||||||
|
DUMP("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump all memory to output
|
||||||
|
static esp_err_t dump_memory(rc522_handle_t scanner, rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
DUMP("\n");
|
||||||
|
dump_header();
|
||||||
|
|
||||||
|
uint8_t page_count = rc522_nxp_get_page_count(picc->type);
|
||||||
|
// Standard read is 4 pages; allocate an appropriate buffer and increment by
|
||||||
|
// 4 pages on each loop
|
||||||
|
uint8_t recv[RC522_NXP_PAGE_SIZE * 4];
|
||||||
|
for (uint8_t i = 0; i < page_count; i += 4) {
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_nxp_read(scanner, picc, i, recv), TAG, "");
|
||||||
|
dump_pages(i, recv, picc->type);
|
||||||
|
}
|
||||||
|
DUMP("\n");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void buf_to_hex(const uint8_t *buffer, uint8_t buflen, char *strbuf, uint8_t strbuflen)
|
||||||
|
{
|
||||||
|
const char *format = "%02X ";
|
||||||
|
|
||||||
|
uint16_t len = 0;
|
||||||
|
for (uint16_t i = 0; i < buflen; i++) {
|
||||||
|
len += sprintf(strbuf + len, format, buffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
strbuf[len - 1] = 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: Get the total and user memory page count of an NXP PICC
|
||||||
|
static void example_page_counts(rc522_handle_t rc522, rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
uint8_t page_count = rc522_nxp_get_page_count(picc->type);
|
||||||
|
uint8_t user_page_count = rc522_nxp_get_user_page_count(picc->type);
|
||||||
|
ESP_LOGI(TAG, "User pages: %d/%d", user_page_count, page_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: Write to and read from user memory, verifying write works
|
||||||
|
static esp_err_t example_read_write(rc522_handle_t rc522, rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
uint8_t page = 5;
|
||||||
|
uint8_t write_buf[4] = { 0x10, 0x20, 0x30, 0x40 };
|
||||||
|
esp_err_t ret = rc522_nxp_write(scanner, picc, page, write_buf);
|
||||||
|
ESP_LOGI(TAG, "Writing to address %02X: R=%02X", page, ret);
|
||||||
|
ESP_RETURN_ON_ERROR(ret, TAG, "Error in WRITE");
|
||||||
|
|
||||||
|
uint8_t read_buf[16] = { 0 };
|
||||||
|
ret = rc522_nxp_read(scanner, picc, page, read_buf);
|
||||||
|
ESP_LOGI(TAG, "Reading from address %02X: R=%02X", page, ret);
|
||||||
|
ESP_RETURN_ON_ERROR(ret, TAG, "Error in READ");
|
||||||
|
|
||||||
|
if (memcmp(write_buf, read_buf, 4) == 0) {
|
||||||
|
ESP_LOGI(TAG, "Data read/write match");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ESP_LOGW(TAG, "Data read/write mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
buf_to_hex(read_buf, 16, str_buf, sizeof(str_buf));
|
||||||
|
ESP_LOGI(TAG, "Pages %d to %d: %s", page, page + 3, str_buf);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: Use of FAST_READ command for variable-size read
|
||||||
|
static esp_err_t example_fast_read(rc522_handle_t rc522, rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
uint8_t page_count = 3;
|
||||||
|
uint8_t start_page = 4;
|
||||||
|
// Need to allocate 4 bytes per page
|
||||||
|
uint8_t *mem_buf = calloc(page_count, 4);
|
||||||
|
ESP_RETURN_ON_FALSE(mem_buf != NULL, ESP_FAIL, TAG, "Out of memory");
|
||||||
|
|
||||||
|
rc522_nxp_fast_read_data_t mem_data = {
|
||||||
|
.bytes = mem_buf,
|
||||||
|
.buffer_size = page_count * 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t ret = rc522_nxp_fast_read(scanner,
|
||||||
|
picc,
|
||||||
|
start_page,
|
||||||
|
start_page + page_count - 1, // end is inclusive, so subtract 1
|
||||||
|
&mem_data);
|
||||||
|
ESP_LOGI(TAG, "Dumping %d pages: R=%02X", page_count, ret);
|
||||||
|
ESP_RETURN_ON_ERROR(ret, TAG, "Error in FAST_READ");
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < page_count; i++) {
|
||||||
|
buf_to_hex(&mem_buf[i * 4], 4, str_buf, 128);
|
||||||
|
ESP_LOGI(TAG, "Page %03d: %s", start_page + i, str_buf);
|
||||||
|
}
|
||||||
|
free(mem_buf);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: Password authentication with PWD/PACK
|
||||||
|
static esp_err_t example_pwd_auth(rc522_handle_t rc522, rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
// Note both PWD and PACK can and should be programmed during initial
|
||||||
|
// PICC setup
|
||||||
|
esp_err_t ret = rc522_nxp_pwd_auth(scanner,
|
||||||
|
picc,
|
||||||
|
RC522_NXP_DEFAULT_PWD, // 4-byte password, default is 0xFFFFFFFF
|
||||||
|
RC522_NXP_DEFAULT_PACK, // 2-byte acknowledge, default is 0x0000
|
||||||
|
&picc->state // Ensure the card state is updated if successful
|
||||||
|
);
|
||||||
|
ESP_LOGI(TAG, "Password auth: R=%02X", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: Counter reading
|
||||||
|
static esp_err_t example_read_cnt(rc522_handle_t rc522, rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
// Counters can only be read if they're enabled, otherwise the PICC will
|
||||||
|
// just respond with a NAK. On the NTAG21x, the single counter can be
|
||||||
|
// enabled in the config section, so read it and check if it's enabled.
|
||||||
|
// 0x83 for NTAG215; address will vary on other PICCs. Check your datasheet
|
||||||
|
uint8_t read_buf[16];
|
||||||
|
esp_err_t ret = rc522_nxp_read(scanner, picc, 0x83, read_buf);
|
||||||
|
ESP_LOGI(TAG, "Read config bytes: R=%02X", ret);
|
||||||
|
ESP_RETURN_ON_ERROR(ret, TAG, "Error in READ");
|
||||||
|
|
||||||
|
uint8_t count_en = (read_buf[4] >> 4) & 0x01;
|
||||||
|
ESP_LOGI(TAG, "NFC_CNT_EN bit: %hhu", count_en);
|
||||||
|
|
||||||
|
if (count_en) { // Avoid a NAK since then we need to wake the chip back up
|
||||||
|
uint32_t counter = 0;
|
||||||
|
// Counter argument is always 0x02 on NTAG21x; Ultralight PICCs with
|
||||||
|
// counters usually have 3 and the argument selects which to read
|
||||||
|
ret = rc522_nxp_read_cnt(scanner, picc, 0x02, &counter);
|
||||||
|
ESP_LOGI(TAG, "Read counter: R=%02X", ret);
|
||||||
|
ESP_RETURN_ON_ERROR(ret, TAG, "Error in READ_CNT");
|
||||||
|
ESP_LOGI(TAG, "Counter value: %lu", counter);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ESP_LOGI(TAG, "Counter disabled; skipping");
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: Reading the originality signature from the PICC
|
||||||
|
static esp_err_t example_get_signature(rc522_handle_t rc522, rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
// Signatures can be either 32 or 48 bytes - here we default to 48 and let
|
||||||
|
// the library fill as appropriate. The sig_size field is updated with the
|
||||||
|
// correct size
|
||||||
|
uint8_t sig_buf[48];
|
||||||
|
rc522_nxp_sig_t sig = { .bytes = sig_buf, .buffer_size = sizeof(sig_buf) };
|
||||||
|
|
||||||
|
esp_err_t ret = rc522_nxp_read_sig(scanner, picc, &sig);
|
||||||
|
ESP_LOGI(TAG, "Read signature: R=%02X", ret);
|
||||||
|
ESP_RETURN_ON_ERROR(ret, TAG, "Error in READ_SIG");
|
||||||
|
|
||||||
|
buf_to_hex(sig_buf, sig.sig_size, str_buf, 128);
|
||||||
|
ESP_LOGI(TAG, "Signature: %s", str_buf);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
|
||||||
|
{
|
||||||
|
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
|
||||||
|
rc522_picc_t *picc = event->picc;
|
||||||
|
|
||||||
|
if (picc->state != RC522_PICC_STATE_ACTIVE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (picc->type != RC522_PICC_TYPE_MIFARE_UL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_type MUST be called before rc522_nxp_* functions to ensure the PICC
|
||||||
|
// has been correctly identified
|
||||||
|
rc522_nxp_get_type(scanner, picc, &picc->type);
|
||||||
|
rc522_picc_print(picc);
|
||||||
|
|
||||||
|
example_page_counts(scanner, picc);
|
||||||
|
example_read_write(scanner, picc);
|
||||||
|
example_fast_read(scanner, picc);
|
||||||
|
example_pwd_auth(scanner, picc);
|
||||||
|
example_read_cnt(scanner, picc);
|
||||||
|
example_get_signature(scanner, picc);
|
||||||
|
|
||||||
|
esp_err_t ret = dump_memory(scanner, picc);
|
||||||
|
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Memory dump failed: E=%02X", ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Memory dump success");
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main()
|
||||||
|
{
|
||||||
|
rc522_spi_create(&driver_config, &driver);
|
||||||
|
rc522_driver_install(driver);
|
||||||
|
|
||||||
|
rc522_config_t scanner_config = {
|
||||||
|
.driver = driver,
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_create(&scanner_config, &scanner);
|
||||||
|
rc522_register_events(scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL);
|
||||||
|
rc522_start(scanner);
|
||||||
|
}
|
||||||
1
components/rc522/examples/picc_nxp/sdkconfig.defaults
Normal file
1
components/rc522/examples/picc_nxp/sdkconfig.defaults
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CONFIG_LOG_COLORS=y
|
||||||
8
components/rc522/examples/read_write/CMakeLists.txt
Normal file
8
components/rc522/examples/read_write/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# For more information about build system see
|
||||||
|
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||||
|
# The following five lines of boilerplate have to be in your project's
|
||||||
|
# CMakeLists in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(read_write)
|
||||||
2
components/rc522/examples/read_write/main/CMakeLists.txt
Normal file
2
components/rc522/examples/read_write/main/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
idf_component_register(SRCS "read_write.c"
|
||||||
|
INCLUDE_DIRS ".")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dependencies:
|
||||||
|
abobija/rc522:
|
||||||
|
version: '*'
|
||||||
163
components/rc522/examples/read_write/main/read_write.c
Normal file
163
components/rc522/examples/read_write/main/read_write.c
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_check.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "rc522.h"
|
||||||
|
#include "driver/rc522_spi.h"
|
||||||
|
#include "picc/rc522_mifare.h"
|
||||||
|
|
||||||
|
static const char *TAG = "rc522-read-write-example";
|
||||||
|
|
||||||
|
#define RC522_SPI_BUS_GPIO_MISO (25)
|
||||||
|
#define RC522_SPI_BUS_GPIO_MOSI (23)
|
||||||
|
#define RC522_SPI_BUS_GPIO_SCLK (19)
|
||||||
|
#define RC522_SPI_SCANNER_GPIO_SDA (22)
|
||||||
|
#define RC522_SCANNER_GPIO_RST (-1) // soft-reset
|
||||||
|
|
||||||
|
static rc522_spi_config_t driver_config = {
|
||||||
|
.host_id = SPI3_HOST,
|
||||||
|
.bus_config = &(spi_bus_config_t){
|
||||||
|
.miso_io_num = RC522_SPI_BUS_GPIO_MISO,
|
||||||
|
.mosi_io_num = RC522_SPI_BUS_GPIO_MOSI,
|
||||||
|
.sclk_io_num = RC522_SPI_BUS_GPIO_SCLK,
|
||||||
|
},
|
||||||
|
.dev_config = {
|
||||||
|
.spics_io_num = RC522_SPI_SCANNER_GPIO_SDA,
|
||||||
|
},
|
||||||
|
.rst_io_num = RC522_SCANNER_GPIO_RST,
|
||||||
|
};
|
||||||
|
|
||||||
|
static rc522_driver_handle_t driver;
|
||||||
|
static rc522_handle_t scanner;
|
||||||
|
|
||||||
|
static void dump_block(uint8_t buffer[RC522_MIFARE_BLOCK_SIZE])
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < RC522_MIFARE_BLOCK_SIZE; i++) {
|
||||||
|
esp_log_write(ESP_LOG_INFO, TAG, "%02" RC522_X " ", buffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_log_write(ESP_LOG_INFO, TAG, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t read_write(rc522_handle_t scanner, rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
const char *data_to_write = "rc522 is dope";
|
||||||
|
const uint8_t block_address = 4;
|
||||||
|
rc522_mifare_key_t key = {
|
||||||
|
.value = { RC522_MIFARE_KEY_VALUE_DEFAULT },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (strlen(data_to_write) > 14) {
|
||||||
|
ESP_LOGW(TAG, "Please make sure that data length is no more than 14 characters");
|
||||||
|
ESP_LOGW(TAG, "since we are going to use random values for last two bytes");
|
||||||
|
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_mifare_auth(scanner, picc, block_address, &key), TAG, "auth fail");
|
||||||
|
|
||||||
|
uint8_t read_buffer[RC522_MIFARE_BLOCK_SIZE];
|
||||||
|
uint8_t write_buffer[RC522_MIFARE_BLOCK_SIZE];
|
||||||
|
|
||||||
|
// Read
|
||||||
|
ESP_LOGI(TAG, "Reading data from the block %d", block_address);
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_mifare_read(scanner, picc, block_address, read_buffer), TAG, "read fail");
|
||||||
|
ESP_LOGI(TAG, "Current data:");
|
||||||
|
dump_block(read_buffer);
|
||||||
|
// ~Read
|
||||||
|
|
||||||
|
// Write
|
||||||
|
strncpy((char *)write_buffer, data_to_write, RC522_MIFARE_BLOCK_SIZE);
|
||||||
|
|
||||||
|
// Set random values for the last two bytes in the write buffer
|
||||||
|
// so we are using new data on each call of read_write function
|
||||||
|
int r = rand();
|
||||||
|
write_buffer[RC522_MIFARE_BLOCK_SIZE - 2] = ((r >> 8) & 0xFF);
|
||||||
|
write_buffer[RC522_MIFARE_BLOCK_SIZE - 1] = ((r >> 0) & 0xFF);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Writing data (%s) to the block %d:", data_to_write, block_address);
|
||||||
|
dump_block(write_buffer);
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_mifare_write(scanner, picc, block_address, write_buffer), TAG, "write fail");
|
||||||
|
// ~Write
|
||||||
|
|
||||||
|
// Read again
|
||||||
|
ESP_LOGI(TAG, "Write done. Verifying...");
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_mifare_read(scanner, picc, block_address, read_buffer), TAG, "read fail");
|
||||||
|
ESP_LOGI(TAG, "New data in the block %d:", block_address);
|
||||||
|
dump_block(read_buffer);
|
||||||
|
// ~Read again
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
bool rw_missmatch = false;
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < RC522_MIFARE_BLOCK_SIZE; i++) {
|
||||||
|
if (write_buffer[i] != read_buffer[i]) {
|
||||||
|
rw_missmatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ~Validate
|
||||||
|
|
||||||
|
// Feedback
|
||||||
|
if (!rw_missmatch) {
|
||||||
|
ESP_LOGI(TAG, "Verified.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ESP_LOGE(TAG,
|
||||||
|
"Write failed. RW missmatch on the byte %d (w:%02" RC522_X ", r:%02" RC522_X ")",
|
||||||
|
i,
|
||||||
|
write_buffer[i],
|
||||||
|
read_buffer[i]);
|
||||||
|
|
||||||
|
dump_block(write_buffer);
|
||||||
|
dump_block(read_buffer);
|
||||||
|
}
|
||||||
|
// ~Feedback
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
|
||||||
|
{
|
||||||
|
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
|
||||||
|
rc522_picc_t *picc = event->picc;
|
||||||
|
|
||||||
|
if (picc->state != RC522_PICC_STATE_ACTIVE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_picc_print(picc);
|
||||||
|
|
||||||
|
if (!rc522_mifare_type_is_classic_compatible(picc->type)) {
|
||||||
|
ESP_LOGW(TAG, "Card is not supported by this example");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read_write(scanner, picc) == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Read/Write success");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ESP_LOGE(TAG, "Read/Write failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc522_mifare_deauth(scanner, picc) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Deauth failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main()
|
||||||
|
{
|
||||||
|
srand(time(NULL)); // Initialize random generator
|
||||||
|
|
||||||
|
rc522_spi_create(&driver_config, &driver);
|
||||||
|
rc522_driver_install(driver);
|
||||||
|
|
||||||
|
rc522_config_t scanner_config = {
|
||||||
|
.driver = driver,
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_create(&scanner_config, &scanner);
|
||||||
|
rc522_register_events(scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL);
|
||||||
|
rc522_start(scanner);
|
||||||
|
}
|
||||||
1
components/rc522/examples/read_write/sdkconfig.defaults
Normal file
1
components/rc522/examples/read_write/sdkconfig.defaults
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CONFIG_LOG_COLORS=y
|
||||||
28
components/rc522/include/driver/rc522_i2c.h
Normal file
28
components/rc522/include/driver/rc522_i2c.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
#include "driver/i2c_types.h"
|
||||||
|
#include "rc522_driver.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
i2c_master_bus_handle_t bus;
|
||||||
|
i2c_master_bus_config_t *bus_config; // Only required if `bus` is NULL
|
||||||
|
i2c_device_config_t dev_config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GPIO number of the RC522 RST pin.
|
||||||
|
* Set to -1 if the RST pin is not connected.
|
||||||
|
*/
|
||||||
|
gpio_num_t rst_io_num;
|
||||||
|
} rc522_i2c_config_t;
|
||||||
|
|
||||||
|
esp_err_t rc522_i2c_create(const rc522_i2c_config_t *config, rc522_driver_handle_t *driver);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
32
components/rc522/include/driver/rc522_spi.h
Normal file
32
components/rc522/include/driver/rc522_spi.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <driver/spi_master.h>
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include "rc522_driver.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define RC522_SPI_WRITE (0)
|
||||||
|
#define RC522_SPI_READ (1)
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
spi_host_device_t host_id;
|
||||||
|
spi_bus_config_t *bus_config;
|
||||||
|
spi_device_interface_config_t dev_config;
|
||||||
|
spi_dma_chan_t dma_chan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GPIO number of the RC522 RST pin.
|
||||||
|
* Set to -1 if the RST pin is not connected.
|
||||||
|
*/
|
||||||
|
gpio_num_t rst_io_num;
|
||||||
|
} rc522_spi_config_t;
|
||||||
|
|
||||||
|
esp_err_t rc522_spi_create(const rc522_spi_config_t *config, rc522_driver_handle_t *driver);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
193
components/rc522/include/picc/rc522_mifare.h
Normal file
193
components/rc522/include/picc/rc522_mifare.h
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rc522_types.h"
|
||||||
|
#include "rc522_picc.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define RC522_ERR_MIFARE_BASE (RC522_ERR_BASE + 0xFF)
|
||||||
|
#define RC522_ERR_MIFARE_ACCESS_BITS_INTEGRITY_VIOLATION (RC522_ERR_MIFARE_BASE + 1)
|
||||||
|
#define RC522_ERR_MIFARE_VALUE_BLOCK_INTEGRITY_VIOLATION (RC522_ERR_MIFARE_BASE + 2)
|
||||||
|
#define RC522_ERR_MIFARE_NACK (RC522_ERR_MIFARE_BASE + 3)
|
||||||
|
#define RC522_ERR_MIFARE_AUTHENTICATION_FAILED (RC522_ERR_MIFARE_BASE + 4)
|
||||||
|
#define RC522_ERR_MIFARE_SECTOR_TRAILER_WRITE_NOT_ALLOWED (RC522_ERR_MIFARE_BASE + 5)
|
||||||
|
|
||||||
|
#define RC522_MIFARE_SECTOR_INDEX_MAX (39) // No MIFARE Classic has more than 40 sectors
|
||||||
|
#define RC522_MIFARE_BLOCK_SIZE (16)
|
||||||
|
#define RC522_MIFARE_ACK (0x0A) // The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK.
|
||||||
|
#define RC522_MIFARE_KEY_SIZE (6)
|
||||||
|
#define RC522_MIFARE_KEY_VALUE_DEFAULT 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t number_of_sectors;
|
||||||
|
} rc522_mifare_desc_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t index; // Zero-based index of Sector
|
||||||
|
uint8_t number_of_blocks; // Total number of blocks inside of Sector
|
||||||
|
uint8_t block_0_address; // Zero-based index of the first Block inside of MIFARE memory
|
||||||
|
} rc522_mifare_sector_desc_t;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RC522_MIFARE_KEY_A = 0,
|
||||||
|
RC522_MIFARE_KEY_B,
|
||||||
|
} rc522_mifare_key_type_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rc522_mifare_key_type_t type;
|
||||||
|
uint8_t value[RC522_MIFARE_KEY_SIZE];
|
||||||
|
} rc522_mifare_key_t;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RC522_MIFARE_BLOCK_UNDEFINED = 0,
|
||||||
|
RC522_MIFARE_BLOCK_TRAILER,
|
||||||
|
RC522_MIFARE_BLOCK_DATA,
|
||||||
|
RC522_MIFARE_BLOCK_VALUE,
|
||||||
|
RC522_MIFARE_BLOCK_MANUFACTURER_DATA,
|
||||||
|
} rc522_mifare_block_type_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t c1 :1;
|
||||||
|
uint8_t c2 :1;
|
||||||
|
uint8_t c3 :1;
|
||||||
|
} rc522_mifare_access_bits_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rc522_mifare_access_bits_t access_bits[4]; // [3] = Trailer, [2] = Block 2, [1] = Block 1, [0] = Block 0
|
||||||
|
} rc522_mifare_sector_trailer_info_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int32_t value; // Value stored in the block
|
||||||
|
uint8_t addr; // Value block address (this is not the memory address)
|
||||||
|
} rc522_mifare_sector_value_block_info_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t address;
|
||||||
|
rc522_mifare_block_type_t type;
|
||||||
|
uint8_t bytes[RC522_MIFARE_BLOCK_SIZE];
|
||||||
|
union
|
||||||
|
{
|
||||||
|
rc522_mifare_sector_trailer_info_t trailer_info; // Valid only if type == RC522_MIFARE_BLOCK_TRAILER
|
||||||
|
rc522_mifare_sector_value_block_info_t value_info; // Valid only if type == RC522_MIFARE_BLOCK_VALUE
|
||||||
|
};
|
||||||
|
rc522_mifare_access_bits_t access_bits;
|
||||||
|
esp_err_t error; // e.g. acces bits or value block integrity violation
|
||||||
|
} rc522_mifare_sector_block_t;
|
||||||
|
|
||||||
|
// {{ MIFARE_Specific_Functions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Authenticate a block read/write operations.
|
||||||
|
*
|
||||||
|
* After a successful authentication, communication between
|
||||||
|
* the PCD and the PICC is secured with MF1xxS20, MF1xxS70 or MF1xxS50 encryption.
|
||||||
|
*
|
||||||
|
* Once when read/write operations are authenticated for one block inside of a sector,
|
||||||
|
* read/write operations are authenticated for all blocks inside of that sector as well.
|
||||||
|
*
|
||||||
|
* @note After read/write operations are done, the PICC must be deauthenticated using @c rc522_mifare_deauth().
|
||||||
|
*
|
||||||
|
* @param rc522 RC522 handle
|
||||||
|
* @param picc PICC that is currently selected
|
||||||
|
* @param block_address Address of the block to authenticate
|
||||||
|
* @param key Key to authenticate with
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_mifare_auth(
|
||||||
|
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address, const rc522_mifare_key_t *key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read from a block at the given @c block_address on the PICC.
|
||||||
|
*
|
||||||
|
* @note The block must be authenticated before calling this function.
|
||||||
|
*
|
||||||
|
* @param rc522 RC522 handle
|
||||||
|
* @param picc PICC that is currently selected
|
||||||
|
* @param block_address Address of the block to read
|
||||||
|
* @param[out] out_buffer Buffer of exactly @c RC522_MIFARE_BLOCK_SIZE bytes to store the retrived data in
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_mifare_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
|
||||||
|
uint8_t out_buffer[RC522_MIFARE_BLOCK_SIZE]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write to a block at the given @c block_address on the PICC.
|
||||||
|
*
|
||||||
|
* @note The block must be authenticated before calling this function.
|
||||||
|
*
|
||||||
|
* @param rc522 RC522 handle
|
||||||
|
* @param picc PICC that is currently selected
|
||||||
|
* @param block_address Address of the block to write
|
||||||
|
* @param[in] buffer Buffer of exactly @c RC522_MIFARE_BLOCK_SIZE that contains the data to write
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_mifare_write(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
|
||||||
|
const uint8_t buffer[RC522_MIFARE_BLOCK_SIZE]);
|
||||||
|
|
||||||
|
// }}
|
||||||
|
|
||||||
|
// {{ MIFARE_Utility_Functions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deauthenticates read/write operations and allows PCD to perform other commands
|
||||||
|
*
|
||||||
|
* @param rc522 RC522 handle
|
||||||
|
* @param picc PICC that is currently selected
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_mifare_deauth(const rc522_handle_t rc522, const rc522_picc_t *picc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Authenticates read/write operations
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_mifare_auth_sector(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_key_t *key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if the PICC is MIFARE Classic
|
||||||
|
*/
|
||||||
|
bool rc522_mifare_type_is_classic_compatible(rc522_picc_type_t type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get MIFARE description (e.g number of sectors)
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_mifare_get_desc(const rc522_picc_t *picc, rc522_mifare_desc_t *out_mifare_desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get MIFARE sector description (e.g number of blocks, block 0 address)
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_mifare_get_sector_desc(uint8_t sector_index, rc522_mifare_sector_desc_t *out_sector_desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read and parse MIFARE sector trailer block
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_mifare_read_sector_trailer_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
const rc522_mifare_sector_desc_t *sector_desc, rc522_mifare_sector_block_t *out_trailer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read and parse MIFARE sector (non-trailer) block
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_mifare_read_sector_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_sector_block_t *trailer, uint8_t block_offset,
|
||||||
|
rc522_mifare_sector_block_t *out_block);
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_get_number_of_sectors(rc522_picc_type_t type, uint8_t *out_result);
|
||||||
|
|
||||||
|
uint8_t rc522_mifare_get_sector_index_by_block_address(uint8_t block_address);
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_get_number_of_blocks_in_sector(uint8_t sector_index, uint8_t *out_result);
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_get_sector_block_0_address(uint8_t sector_index, uint8_t *out_result);
|
||||||
|
|
||||||
|
// }}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
377
components/rc522/include/picc/rc522_nxp.h
Normal file
377
components/rc522/include/picc/rc522_nxp.h
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
/** Functions to support interfacing with NXP tags
|
||||||
|
*
|
||||||
|
* This header defines functions for working with ISO14443-3 compliant NXP
|
||||||
|
* PICCs, primarily the Ultralight and NTAG families. These PICCs use
|
||||||
|
* predominately the same command set, with some small variations.
|
||||||
|
*
|
||||||
|
* For MIFARE Classic PICCs, see `include/picc/rc522_mifare.h`.
|
||||||
|
*
|
||||||
|
* Authentication:
|
||||||
|
* Ultralight {C,AES}: Key-based (3DES and AES respectively)
|
||||||
|
* Ultralight EV1, NTAG 21x: Password-based
|
||||||
|
*
|
||||||
|
* Some functions have been commented out; ideally these would be implemented
|
||||||
|
* at some stage to implement full support for these PICC functions, but I don't
|
||||||
|
* have any of the actual PICCs to properly test with.
|
||||||
|
*
|
||||||
|
* CMD/PICC | UL | UL C | UL EV1 | UL AES | UL Nano | NTAG21x |
|
||||||
|
* -------------|---------|---------|---------|---------|---------|---------|
|
||||||
|
* GET_VERSION | | | y | y | y | y |
|
||||||
|
* READ | y | y | y | y | y | y |
|
||||||
|
* FAST_READ | | | y | y | | y |
|
||||||
|
* WRITE | y | y | y | y | y | y |
|
||||||
|
* COMPAT_WRITE | y | y | y | | y | y |
|
||||||
|
* READ_CNT | | | 3 | 3 | | 1 |
|
||||||
|
* INCR_CNT | | | y | y | | |
|
||||||
|
* READ_SIG | | | 32 | 48 | 32 | 32 | (bytes)
|
||||||
|
* WRITE_SIG | | | | y | y | |
|
||||||
|
* LOCK_SIG | | | | y | y | |
|
||||||
|
* AUTHENTICATE | | 3DES | | AES | | |
|
||||||
|
* PWD_AUTH | | | y | | | y |
|
||||||
|
* VCSL | | | y | y | | |
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rc522_picc.h"
|
||||||
|
#include "rc522_types.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define RC522_ERR_NXP_BASE (RC522_ERR_BASE + 0XF0)
|
||||||
|
#define RC522_ERR_NXP_NACK (RC522_ERR_NXP_BASE + 1)
|
||||||
|
#define RC522_ERR_NXP_AUTHENTICATION_FAILED (RC522_ERR_NXP_BASE + 2)
|
||||||
|
#define RC522_ERR_NXP_PACK_MISMATCH (RC522_ERR_NXP_BASE + 3)
|
||||||
|
|
||||||
|
#define RC522_NXP_PWD_SIZE 4
|
||||||
|
#define RC522_NXP_PACK_SIZE 2
|
||||||
|
#define RC522_NXP_PAGE_SIZE 4
|
||||||
|
#define RC522_NXP_READ_SIZE (RC522_NXP_PAGE_SIZE * 4)
|
||||||
|
|
||||||
|
extern const uint8_t RC522_NXP_DEFAULT_PWD[RC522_NXP_PWD_SIZE];
|
||||||
|
extern const uint8_t RC522_NXP_DEFAULT_PACK[RC522_NXP_PACK_SIZE];
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get PICC information.
|
||||||
|
*/
|
||||||
|
RC522_NXP_GET_VERSION = 0x60,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read four pages from memory.
|
||||||
|
*/
|
||||||
|
RC522_NXP_READ = 0x30,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read pages from START to END in memory.
|
||||||
|
*/
|
||||||
|
RC522_NXP_FAST_READ = 0x3A,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a page to memory.
|
||||||
|
*/
|
||||||
|
RC522_NXP_WRITE = 0xA2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the first 4 bytes of 16 to a page in memory.
|
||||||
|
*/
|
||||||
|
RC522_NXP_COMPAT_WRITE = 0xA0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an onboard counter.
|
||||||
|
*/
|
||||||
|
RC522_NXP_READ_CNT = 0x39,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment an onboard counter.
|
||||||
|
*/
|
||||||
|
RC522_NXP_INCR_CNT = 0xA5,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the originality signature.
|
||||||
|
*/
|
||||||
|
RC522_NXP_READ_SIG = 0x3C,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a new originality signature.
|
||||||
|
*/
|
||||||
|
RC522_NXP_WRITE_SIG = 0xA9,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control access to the originality signature.
|
||||||
|
*/
|
||||||
|
RC522_NXP_LOCK_SIG = 0xAC,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform key-based authentication, stage 1.
|
||||||
|
*/
|
||||||
|
RC522_NXP_AUTHENTICATE = 0x1A,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform key-based authentication, stage 2.
|
||||||
|
*/
|
||||||
|
RC522_NXP_AUTHENTICATE_2 = 0xAF,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform password-based authentication.
|
||||||
|
*/
|
||||||
|
RC522_NXP_PWD_AUTH = 0x1B,
|
||||||
|
|
||||||
|
RC522_NXP_VCSL = 0x4B,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L3 GET_VERSION: product_type field
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RC522_NXP_PRODUCT_TYPE_UNKNOWN,
|
||||||
|
RC522_NXP_PRODUCT_TYPE_UL = 0x03,
|
||||||
|
RC522_NXP_PRODUCT_TYPE_NTAG = 0x04,
|
||||||
|
} rc522_nxp_product_type_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L3 GET_VERSION: major_version field
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RC522_NXP_MAJ_VER_UNKNOWN = 0,
|
||||||
|
RC522_NXP_MAJ_VER_NTAG21 = 0x01,
|
||||||
|
RC522_NXP_MAJ_VER_UL_EV1 = 0x01,
|
||||||
|
RC522_NXP_MAJ_VER_UL_NANO = 0x02,
|
||||||
|
RC522_NXP_MAJ_VER_UL_AES = 0x04,
|
||||||
|
} rc522_nxp_major_version_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L3 GET_VERSION result
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t header;
|
||||||
|
uint8_t vendor;
|
||||||
|
rc522_nxp_product_type_t product_type :8;
|
||||||
|
uint8_t product_subtype;
|
||||||
|
rc522_nxp_major_version_t major_version :8;
|
||||||
|
uint8_t minor_version;
|
||||||
|
uint8_t storage_size;
|
||||||
|
uint8_t protocol_type;
|
||||||
|
} rc522_nxp_picc_version_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L3 FAST_READ result
|
||||||
|
*
|
||||||
|
* Contains a buffer, the size of the buffer, and the amount of space occupied
|
||||||
|
* by the data after a read
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t *bytes;
|
||||||
|
uint8_t buffer_size;
|
||||||
|
uint8_t read_size;
|
||||||
|
} rc522_nxp_fast_read_data_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable-length protection signature
|
||||||
|
*
|
||||||
|
* UL EV1: 32 bytes
|
||||||
|
* UL AES: 48 bytes
|
||||||
|
* UL Nano: 32 bytes
|
||||||
|
* NTAG213/215/216: 32 bytes
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t *bytes;
|
||||||
|
uint8_t buffer_size;
|
||||||
|
uint8_t sig_size;
|
||||||
|
} rc522_nxp_sig_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determine the total memory size of an NXP PICC
|
||||||
|
*
|
||||||
|
* Given an NXP PICC type, returns the total size of the memory in pages. This
|
||||||
|
* is not equivalent to generic user memory.
|
||||||
|
*
|
||||||
|
* For invalid types, unknown types, or types without pages, returns 0.
|
||||||
|
*
|
||||||
|
* @sa rc522_nxp_get_user_page_count()
|
||||||
|
*/
|
||||||
|
uint8_t rc522_nxp_get_page_count(rc522_picc_type_t type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determine the user memory size of an NXP PICC
|
||||||
|
*
|
||||||
|
* Given an NXP PICC type (RC522_PICC_TYPE_MIFARE_UL_* or RC522_PICC_TYPE_NTAG*),
|
||||||
|
* returns the total size of the user memory in pages. The user memory always
|
||||||
|
* starts at page 4.
|
||||||
|
*
|
||||||
|
* For invalid types, unknown types, or types without pages, returns 0.
|
||||||
|
*/
|
||||||
|
uint8_t rc522_nxp_get_user_page_count(rc522_picc_type_t type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determine the start page of user memory for an NXP PICC
|
||||||
|
*
|
||||||
|
* Currently this is 0x04 for all PICCs. It's unlikely to change in future
|
||||||
|
* for compatibility reasons, but this function exists in case (and also
|
||||||
|
* as a reference).
|
||||||
|
*
|
||||||
|
* For invalid or unknown types, returns 0.
|
||||||
|
*/
|
||||||
|
uint8_t rc522_nxp_get_user_mem_start(rc522_picc_type_t type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determine the final page of user memory for an NXP PICC
|
||||||
|
*
|
||||||
|
* Returns the address of the final page of user memory for a given PICC
|
||||||
|
* type. This is the last page that can be safely written to without changing
|
||||||
|
* the configuration of the PICC.
|
||||||
|
*
|
||||||
|
* For invalid or unknown types, returns 0.
|
||||||
|
*/
|
||||||
|
uint8_t rc522_nxp_get_user_mem_end(rc522_picc_type_t type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determine the type of an NXP PICC
|
||||||
|
*
|
||||||
|
* The Ultralight and NTAG families both use SAK=0, and need further processing
|
||||||
|
* to identify. This function attempts to determine the PICC in use following
|
||||||
|
* the procedure in NXP Application Note 10833.
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_nxp_get_type(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_type_t *out_type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NXP Level 3 GET_VERSION
|
||||||
|
*
|
||||||
|
* If supported, returns information about the PICC, including manufacturer,
|
||||||
|
* product, and memory size.
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_nxp_get_version(
|
||||||
|
const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_picc_version_t *out_version);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NXP Level 3 READ
|
||||||
|
*
|
||||||
|
* Reads 4 pages (16 bytes) from a given page address on the PICC.
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_nxp_read(
|
||||||
|
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, uint8_t out_buffer[RC522_NXP_PAGE_SIZE * 4]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NXP Level 3 FAST_READ
|
||||||
|
*
|
||||||
|
* If supported, allows a variable number of pages to be read at once, instead
|
||||||
|
* of the fixed 4 in READ.
|
||||||
|
*
|
||||||
|
* @param start Page address to start reading from
|
||||||
|
* @param end Page address to end reading (inclusive)
|
||||||
|
* @param out_buffer Output buffer; should be at least (end-start+1) * 4 bytes
|
||||||
|
*
|
||||||
|
* Supported PICCs: UL EV1, UL AES, NTAG21x
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_nxp_fast_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t start, uint8_t end,
|
||||||
|
rc522_nxp_fast_read_data_t *out_buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NXP Level 3 WRITE
|
||||||
|
*
|
||||||
|
* Writes 4 bytes of data to a single page.
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_nxp_write(
|
||||||
|
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, const uint8_t buffer[RC522_NXP_PAGE_SIZE]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NXP Level 3 READ_CNT
|
||||||
|
*
|
||||||
|
* If supported, reads a counter from the PICC. Some PICCs have more than one
|
||||||
|
* counter; if so counter_no specifies the counter to address.
|
||||||
|
*
|
||||||
|
* Supported PICCs: UL EV1 (3), UL AES (3), NTAG21x (1)
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_nxp_read_cnt(
|
||||||
|
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t counter_no, uint32_t *out_count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NXP Level 3 INCR_CNT
|
||||||
|
*
|
||||||
|
* If supported, increments a counter on the PICC. If the PICC has more than one
|
||||||
|
* counter, counter_no specifies the counter to target.
|
||||||
|
*
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
// esp_err_t rc522_nxp_incr_cnt(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
// uint8_t counter_no);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks for support for AUTHENTICATE in L3 on a PICC
|
||||||
|
*
|
||||||
|
* Used to determine if a chip supports AUTHENTICATE, to differentiate between
|
||||||
|
* Ultralight and Ultralight C. Does not perform actual authentication.
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_nxp_keyauth_supported(const rc522_handle_t rc522, const rc522_picc_t *picc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NXP Level 3 AUTHENTICATE
|
||||||
|
*
|
||||||
|
* If supported, performs key-based authentication with a PICC. Note support
|
||||||
|
* for DES and/or AES algorithms is required.
|
||||||
|
*
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
// esp_err_t rc522_nxp_key_auth(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
// rc522_nxp_key_t *key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NXP Level 3 PWD_AUTH
|
||||||
|
*
|
||||||
|
* If supported, performs password-based authentication with a PICC.
|
||||||
|
*
|
||||||
|
* @param pwd Password to send to pick
|
||||||
|
* @param pack Password ACKnowledgement expected to be returned by PICC
|
||||||
|
* @param[out] out_state PICC state on return. Correct PWD with mismatched PACK
|
||||||
|
* will still result in AUTHENTICATED
|
||||||
|
*
|
||||||
|
* Supported PICCs: UL EV1, NTAG21x
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_nxp_pwd_auth(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
const uint8_t pwd[RC522_NXP_PWD_SIZE], const uint8_t pack[RC522_NXP_PACK_SIZE], rc522_picc_state_t *out_state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NXP Level 3 READ_SIG
|
||||||
|
*
|
||||||
|
* If supported, reads the ECC signature from the PICC. This signature can
|
||||||
|
* be used to verify originality. Does not actually perform verification.
|
||||||
|
*
|
||||||
|
* TODO: Implement verification per AN11350?
|
||||||
|
*
|
||||||
|
* Supported PICCs: UL EV1, UL AES, UL Nano, NTAG21x
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_nxp_read_sig(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_sig_t *out_sig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NXP Level 3 WRITE_SIG
|
||||||
|
*
|
||||||
|
* If supported, writes a new signature to the PICC.
|
||||||
|
*
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
// esp_err_t rc522_nxp_write_sig(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
// rc522_nxp_sig_t *sig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NXP Level 3 LOCK_SIG
|
||||||
|
*
|
||||||
|
* If supported, controls the originality signature lock on the PICC.
|
||||||
|
*
|
||||||
|
* @param argument 0x00 for unlock, 0x01 for lock, 0x02 for permanent lock
|
||||||
|
*
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
// esp_err_t rc522_nxp_lock_sig(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
// rc522_nxp_sig_arg_t argument);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
26
components/rc522/include/rc522.h
Normal file
26
components/rc522/include/rc522.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rc522_types.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ESP_EVENT_DECLARE_BASE(RC522_EVENTS);
|
||||||
|
|
||||||
|
esp_err_t rc522_create(const rc522_config_t *config, rc522_handle_t *out_rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_register_events(
|
||||||
|
const rc522_handle_t rc522, rc522_event_t event, esp_event_handler_t event_handler, void *event_handler_arg);
|
||||||
|
|
||||||
|
esp_err_t rc522_unregister_events(const rc522_handle_t rc522, rc522_event_t event, esp_event_handler_t event_handler);
|
||||||
|
|
||||||
|
esp_err_t rc522_start(rc522_handle_t rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_pause(rc522_handle_t rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_destroy(rc522_handle_t rc522);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
15
components/rc522/include/rc522_driver.h
Normal file
15
components/rc522/include/rc522_driver.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct rc522_driver_handle *rc522_driver_handle_t;
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_install(const rc522_driver_handle_t driver);
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_uninstall(const rc522_driver_handle_t driver);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
33
components/rc522/include/rc522_pcd.h
Normal file
33
components/rc522/include/rc522_pcd.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rc522_types.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum // RC522_PCD_RF_CFG_REG
|
||||||
|
{
|
||||||
|
RC522_PCD_RX_GAIN_2_BIT = BIT6,
|
||||||
|
RC522_PCD_RX_GAIN_1_BIT = BIT5,
|
||||||
|
RC522_PCD_RX_GAIN_0_BIT = BIT4,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receiver (antenna) gain
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RC522_PCD_18_DB_RX_GAIN = (RC522_PCD_RX_GAIN_1_BIT), /* 18 dB */
|
||||||
|
RC522_PCD_23_DB_RX_GAIN = (RC522_PCD_RX_GAIN_1_BIT | RC522_PCD_RX_GAIN_0_BIT), /* 23 dB */
|
||||||
|
RC522_PCD_33_DB_RX_GAIN = (RC522_PCD_RX_GAIN_2_BIT), /* 33 dB */
|
||||||
|
RC522_PCD_38_DB_RX_GAIN = (RC522_PCD_RX_GAIN_2_BIT | RC522_PCD_RX_GAIN_0_BIT), /* 38 dB */
|
||||||
|
RC522_PCD_43_DB_RX_GAIN = (RC522_PCD_RX_GAIN_2_BIT | RC522_PCD_RX_GAIN_1_BIT), /* 43 dB */
|
||||||
|
RC522_PCD_48_DB_RX_GAIN = (RC522_PCD_RX_GAIN_2_BIT | RC522_PCD_RX_GAIN_1_BIT | RC522_PCD_RX_GAIN_0_BIT), /* 48 dB */
|
||||||
|
} rc522_pcd_rx_gain_t;
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_set_rx_gain(const rc522_handle_t rc522, rc522_pcd_rx_gain_t gain);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
170
components/rc522/include/rc522_picc.h
Normal file
170
components/rc522/include/rc522_picc.h
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rc522_types.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define RC522_PICC_UID_SIZE_MAX (10)
|
||||||
|
#define RC522_PICC_UID_SIZE_MIN (4)
|
||||||
|
#define RC522_PICC_UID_STR_BUFFER_SIZE_MAX (RC522_PICC_UID_SIZE_MAX * 3)
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t value[RC522_PICC_UID_SIZE_MAX];
|
||||||
|
uint8_t length;
|
||||||
|
} rc522_picc_uid_t;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RC522_PICC_TYPE_UNKNOWN = -1,
|
||||||
|
RC522_PICC_TYPE_UNDEFINED = 0,
|
||||||
|
RC522_PICC_TYPE_ISO_14443_4, // PICC compliant with ISO/IEC 14443-4
|
||||||
|
RC522_PICC_TYPE_ISO_18092, // PICC compliant with ISO/IEC 18092 (NFC)
|
||||||
|
RC522_PICC_TYPE_MIFARE_MINI, // MIFARE Classic protocol, 320 bytes
|
||||||
|
RC522_PICC_TYPE_MIFARE_1K, // MIFARE Classic protocol, 1KB
|
||||||
|
RC522_PICC_TYPE_MIFARE_4K, // MIFARE Classic protocol, 4KB
|
||||||
|
RC522_PICC_TYPE_MIFARE_UL, // MIFARE Ultralight or Ultralight C
|
||||||
|
RC522_PICC_TYPE_MIFARE_PLUS, // MIFARE Plus
|
||||||
|
RC522_PICC_TYPE_MIFARE_DESFIRE, // MIFARE DESFire
|
||||||
|
RC522_PICC_TYPE_TNP3XXX, // Only mentioned in NXP AN 10833 MIFARE Type Identification Procedure
|
||||||
|
RC522_PICC_TYPE_MIFARE_UL_, // Ultralight MF0ICU1; 16x 4-byte pages
|
||||||
|
RC522_PICC_TYPE_MIFARE_UL_C, // Ultralight C MF0ICU2; 48x 4-byte pages
|
||||||
|
RC522_PICC_TYPE_MIFARE_UL_EV1_1, // Ultralight EV1 MF0UL11; 20x 4-byte pages
|
||||||
|
RC522_PICC_TYPE_MIFARE_UL_EV1_2, // Ultralight EV1 MF0UL21; 41x 4-byte pages
|
||||||
|
RC522_PICC_TYPE_MIFARE_UL_NANO, // Ultralight NANO; 40 bytes (probably 10x 4-byte pages)
|
||||||
|
RC522_PICC_TYPE_MIFARE_UL_AES, // Ultralight AES MF0AES(H)20; 60x 4-byte pages
|
||||||
|
RC522_PICC_TYPE_NTAG2xx, // NTAG 2xx; further information unknown
|
||||||
|
RC522_PICC_TYPE_NTAG213, // NTAG213; 45x 4-byte pages
|
||||||
|
RC522_PICC_TYPE_NTAG215, // NTAG215; 135x 4-byte pages
|
||||||
|
RC522_PICC_TYPE_NTAG216, // NTAG216; 231x 4-byte pages
|
||||||
|
} rc522_picc_type_t;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Not used! Ignore this one.
|
||||||
|
*
|
||||||
|
* In the POWER-OFF State, the PICC is not powered
|
||||||
|
* due to a lack of carrier energy.
|
||||||
|
*/
|
||||||
|
RC522_PICC_STATE_POWER_OFF = -1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PICC is maybe in the field.
|
||||||
|
*
|
||||||
|
* In the IDLE State, the PICC shall recognize REQA and WUPA Commands.
|
||||||
|
* The PICC enters the READY State after it has received a valid
|
||||||
|
* REQA or WUPA Command and transmitted its ATQA.
|
||||||
|
*/
|
||||||
|
RC522_PICC_STATE_IDLE = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In the READY State, either the bit frame anticollision or a proprietary
|
||||||
|
* anticollision method can be applied. Cascade levels are handled inside
|
||||||
|
* this state to get the complete UID.
|
||||||
|
*
|
||||||
|
* The PICC enters the ACTIVE State when it is selected with its complete UID.
|
||||||
|
*/
|
||||||
|
RC522_PICC_STATE_READY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PICC is in the field.
|
||||||
|
*
|
||||||
|
* In the ACTIVE State, the PICC listens to any higher layer message.
|
||||||
|
* The PICC enters the HALT State when a valid HLTA Command is received.
|
||||||
|
*
|
||||||
|
* @note
|
||||||
|
* In the higher layer protocol, specific commands may be defined
|
||||||
|
* to return the PICC to its HALT State.
|
||||||
|
*/
|
||||||
|
RC522_PICC_STATE_ACTIVE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In the HALT State, the PICC shall respond only to a WUPA Command.
|
||||||
|
*
|
||||||
|
* The PICC enters the READY* state after it has received a valid
|
||||||
|
* WUPA Command and transmitted its ATQA.
|
||||||
|
*/
|
||||||
|
RC522_PICC_STATE_HALT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* READY* State
|
||||||
|
*
|
||||||
|
* PICC is woken from HALT state by WUPA command.
|
||||||
|
*
|
||||||
|
* The READY* State is similar to the READY State, either the bit frame
|
||||||
|
* anticollision or a proprietary anticollision method can be applied.
|
||||||
|
* Cascade levels are handled inside this state to get complete UID.
|
||||||
|
*
|
||||||
|
* The PICC enters the ACTIVE* State when it is selected with its complete UID.
|
||||||
|
*/
|
||||||
|
RC522_PICC_STATE_READY_H,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ACTIVE* State
|
||||||
|
*
|
||||||
|
* PICC is still in the field. PICC enters this state
|
||||||
|
* when it is SELECTED in the READY* state.
|
||||||
|
*
|
||||||
|
* The ACTIVE* State is similar to the ACTIVE State, the PICC is selected and
|
||||||
|
* listens to any higher layer message.
|
||||||
|
*
|
||||||
|
* The PICC enters the HALT State when a valid HLTA Command is received.
|
||||||
|
*/
|
||||||
|
RC522_PICC_STATE_ACTIVE_H,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AUTHENTICATED State
|
||||||
|
*
|
||||||
|
* PICC has been authenticated, via either keys or passwords, and accessing
|
||||||
|
* protected areas is now possible. This is a Level 3 extension on some NXP
|
||||||
|
* PICCs.
|
||||||
|
*/
|
||||||
|
RC522_PICC_STATE_AUTHENTICATED,
|
||||||
|
} rc522_picc_state_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All RFU bits shall be set to zero.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint16_t source;
|
||||||
|
uint8_t rfu4 :4;
|
||||||
|
uint8_t prop_coding :4;
|
||||||
|
uint8_t uid_size :2; // 00b - single (4 bytes), 01b - double (7 bytes), 10b - triple (10 bytes)
|
||||||
|
uint8_t rfu1 :1;
|
||||||
|
uint8_t anticollision :5;
|
||||||
|
} rc522_picc_atqa_desc_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rc522_picc_atqa_desc_t atqa;
|
||||||
|
rc522_picc_uid_t uid;
|
||||||
|
uint8_t sak;
|
||||||
|
rc522_picc_type_t type;
|
||||||
|
rc522_picc_state_t state;
|
||||||
|
} rc522_picc_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rc522_picc_state_t old_state;
|
||||||
|
rc522_picc_t *picc;
|
||||||
|
} rc522_picc_state_changed_event_t;
|
||||||
|
|
||||||
|
char *rc522_picc_type_name(rc522_picc_type_t type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert PICC UID to (null-terminated) string
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_picc_uid_to_str(const rc522_picc_uid_t *uid, char *buffer, uint8_t buffer_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print PICC information in a fancy way
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_picc_print(const rc522_picc_t *picc);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
52
components/rc522/include/rc522_types.h
Normal file
52
components/rc522/include/rc522_types.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include <esp_event.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "rc522_driver.h"
|
||||||
|
#include "rc522_picc.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define RC522_X "X"
|
||||||
|
|
||||||
|
#define RC522_ERR_BASE (0xF522)
|
||||||
|
#define RC522_ERR_COLLISION (RC522_ERR_BASE + 1)
|
||||||
|
#define RC522_ERR_COLLISION_UNSOLVABLE (RC522_ERR_BASE + 2)
|
||||||
|
#define RC522_ERR_CRC_WRONG (RC522_ERR_BASE + 3)
|
||||||
|
#define RC522_ERR_INVALID_ATQA (RC522_ERR_BASE + 4)
|
||||||
|
#define RC522_ERR_RX_TIMEOUT (RC522_ERR_BASE + 5)
|
||||||
|
#define RC522_ERR_RX_TIMER_TIMEOUT (RC522_ERR_BASE + 6)
|
||||||
|
#define RC522_ERR_INVALID_SAK (RC522_ERR_BASE + 7)
|
||||||
|
#define RC522_ERR_PICC_POST_HEARTBEAT_MISSMATCH (RC522_ERR_BASE + 8)
|
||||||
|
#define RC522_ERR_PCD_FIFO_BUFFER_OVERFLOW (RC522_ERR_BASE + 9)
|
||||||
|
#define RC522_ERR_PCD_PARITY_CHECK_FAILED (RC522_ERR_BASE + 10)
|
||||||
|
#define RC522_ERR_PCD_PROTOCOL_ERROR (RC522_ERR_BASE + 11)
|
||||||
|
#define RC522_ERR_RST_PIN_UNUSED (RC522_ERR_BASE + 12)
|
||||||
|
#define RC522_ERR_PCD_FIFO_EMPTY (RC522_ERR_BASE + 13)
|
||||||
|
#define RC522_ERR_HLTA_NOT_ACKED (RC522_ERR_BASE + 14)
|
||||||
|
|
||||||
|
typedef struct rc522 *rc522_handle_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rc522_driver_handle_t driver;
|
||||||
|
uint16_t poll_interval_ms; /*<! Delay (in milliseconds) between polls */
|
||||||
|
size_t task_stack_size; /*<! Stack size of rc522 task */
|
||||||
|
uint8_t task_priority; /*<! Priority of rc522 task */
|
||||||
|
SemaphoreHandle_t task_mutex; /*<! Mutex for rc522 task */
|
||||||
|
} rc522_config_t;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RC522_EVENT_ANY = ESP_EVENT_ANY_ID,
|
||||||
|
RC522_EVENT_NONE,
|
||||||
|
RC522_EVENT_PICC_STATE_CHANGED,
|
||||||
|
} rc522_event_t;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
42
components/rc522/internal/rc522_driver_internal.h
Normal file
42
components/rc522/internal/rc522_driver_internal.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include <driver/gpio.h>
|
||||||
|
#include "rc522_types_internal.h"
|
||||||
|
#include "rc522_driver.h"
|
||||||
|
|
||||||
|
#define RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL (0)
|
||||||
|
#define RC522_DRIVER_HARD_RST_PULSE_DURATION_MS (15)
|
||||||
|
|
||||||
|
typedef esp_err_t (*rc522_driver_install_handler_t)(const rc522_driver_handle_t driver);
|
||||||
|
|
||||||
|
typedef esp_err_t (*rc522_driver_send_handler_t)(
|
||||||
|
const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes);
|
||||||
|
|
||||||
|
typedef esp_err_t (*rc522_driver_receive_handler_t)(
|
||||||
|
const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes);
|
||||||
|
|
||||||
|
typedef esp_err_t (*rc522_driver_reset_handler_t)(const rc522_driver_handle_t driver);
|
||||||
|
|
||||||
|
typedef esp_err_t (*rc522_driver_uninstall_handler_t)(const rc522_driver_handle_t driver);
|
||||||
|
|
||||||
|
struct rc522_driver_handle
|
||||||
|
{
|
||||||
|
void *config;
|
||||||
|
void *device;
|
||||||
|
void *meta; /* <! Additional (driver specific) data */
|
||||||
|
rc522_driver_install_handler_t install;
|
||||||
|
rc522_driver_send_handler_t send;
|
||||||
|
rc522_driver_receive_handler_t receive;
|
||||||
|
rc522_driver_reset_handler_t reset;
|
||||||
|
rc522_driver_uninstall_handler_t uninstall;
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_init_rst_pin(gpio_num_t rst_io_num);
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_create(const void *config, size_t config_size, rc522_driver_handle_t *driver);
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes);
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes);
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_reset(const rc522_driver_handle_t driver);
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_destroy(rc522_driver_handle_t driver);
|
||||||
21
components/rc522/internal/rc522_helpers_internal.h
Normal file
21
components/rc522/internal/rc522_helpers_internal.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint32_t rc522_millis();
|
||||||
|
|
||||||
|
void rc522_delay_ms(uint32_t ms);
|
||||||
|
|
||||||
|
esp_err_t rc522_buffer_to_hex_str(
|
||||||
|
const uint8_t *buffer, uint8_t buffer_length, char *str_buffer, uint8_t str_buffer_length);
|
||||||
|
|
||||||
|
esp_err_t rc522_nibbles(uint8_t byte, uint8_t *msb, uint8_t *lsb);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
15
components/rc522/internal/rc522_internal.h
Normal file
15
components/rc522/internal/rc522_internal.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rc522.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void rc522_task(void *arg);
|
||||||
|
|
||||||
|
esp_err_t rc522_dispatch_event(const rc522_handle_t rc522, rc522_event_t event, const void *data, size_t data_size);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
448
components/rc522/internal/rc522_pcd_internal.h
Normal file
448
components/rc522/internal/rc522_pcd_internal.h
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rc522_types_internal.h"
|
||||||
|
#include "rc522_pcd.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define RC522_PCD_MOD_WIDTH_REG_RESET_VALUE (38)
|
||||||
|
#define RC522_PCD_TX_MODE_REG_RESET_VALUE (0x00)
|
||||||
|
#define RC522_PCD_RX_MODE_REG_RESET_VALUE (0x00)
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
// Starts and stops command execution
|
||||||
|
RC522_PCD_COMMAND_REG = 0x01,
|
||||||
|
|
||||||
|
// Communication Interrupt Enable Register.
|
||||||
|
// Control bits to enable and disable the passing of interrupt requests
|
||||||
|
RC522_PCD_COM_INT_EN_REG = 0x02,
|
||||||
|
|
||||||
|
// Diverted Interrupt Enable Register.
|
||||||
|
// Control bits to enable and disable the passing of interrupt requests
|
||||||
|
RC522_PCD_DIV_INT_EN_REG = 0x03,
|
||||||
|
|
||||||
|
// Communication Interrupt request bits
|
||||||
|
RC522_PCD_COM_INT_REQ_REG = 0x04,
|
||||||
|
|
||||||
|
// Diverted Interrupt request bits
|
||||||
|
RC522_PCD_DIV_INT_REQ_REG = 0x05,
|
||||||
|
|
||||||
|
// Error bits showing the error status of the last command executed
|
||||||
|
RC522_PCD_ERROR_REG = 0x06,
|
||||||
|
|
||||||
|
// Contains status bits of the receiver, transmitter and data mode detector
|
||||||
|
RC522_PCD_STATUS_2_REG = 0x08,
|
||||||
|
|
||||||
|
// Input and output of 64 byte FIFO buffer
|
||||||
|
RC522_PCD_FIFO_DATA_REG = 0x09,
|
||||||
|
|
||||||
|
// Number of bytes stored in the FIFO buffer
|
||||||
|
RC522_PCD_FIFO_LEVEL_REG = 0x0A,
|
||||||
|
|
||||||
|
// Shows the MFRC522 software version
|
||||||
|
RC522_PCD_VERSION_REG = 0x37,
|
||||||
|
|
||||||
|
// Controls the logical behavior of the antenna driver pins TX1 and TX2
|
||||||
|
RC522_PCD_TX_CONTROL_REG = 0x14,
|
||||||
|
|
||||||
|
// Configures the receiver gain
|
||||||
|
RC522_PCD_RF_CFG_REG = 0x26,
|
||||||
|
|
||||||
|
// Miscellaneous control register
|
||||||
|
RC522_PCD_CONTROL_REG = 0x0C,
|
||||||
|
|
||||||
|
// Adjustments for bit-oriented frames
|
||||||
|
RC522_PCD_BIT_FRAMING_REG = 0x0D,
|
||||||
|
|
||||||
|
// Defines the first bit-collision detected on the RF interface
|
||||||
|
RC522_PCD_COLL_REG = 0x0E,
|
||||||
|
|
||||||
|
// MSB (higher bits) values of the CRC calculation
|
||||||
|
RC522_PCD_CRC_RESULT_MSB_REG = 0x21,
|
||||||
|
|
||||||
|
// LSB (lower bits) values of the CRC calculation
|
||||||
|
RC522_PCD_CRC_RESULT_LSB_REG = 0x22,
|
||||||
|
|
||||||
|
// Sets the modulation width
|
||||||
|
RC522_PCD_MOD_WIDTH_REG = 0x24,
|
||||||
|
|
||||||
|
// Defines the mode of the timer
|
||||||
|
RC522_PCD_TIMER_MODE_REG = 0x2A,
|
||||||
|
|
||||||
|
// Defines the timer prescaler settings
|
||||||
|
RC522_PCD_TIMER_PRESCALER_REG = 0x2B,
|
||||||
|
|
||||||
|
// MSB (higher bits) value of 16-bit timer reload value
|
||||||
|
RC522_PCD_TIMER_RELOAD_MSB_REG = 0x2C,
|
||||||
|
|
||||||
|
// LSB (lower bits) value of 16-bit timer reload value
|
||||||
|
RC522_PCD_TIMER_RELOAD_LSB_REG = 0x2D,
|
||||||
|
|
||||||
|
// Defines general modes for transmitting and receiving
|
||||||
|
RC522_PCD_MODE_REG = 0x11,
|
||||||
|
|
||||||
|
// Controls the setting of the transmission modulation
|
||||||
|
RC522_PCD_TX_ASK_REG = 0x15,
|
||||||
|
|
||||||
|
// Defines the data rate during transmission
|
||||||
|
RC522_PCD_TX_MODE_REG = 0x12,
|
||||||
|
|
||||||
|
// Defines the data rate during reception
|
||||||
|
RC522_PCD_RX_MODE_REG = 0x13,
|
||||||
|
} rc522_pcd_register_t;
|
||||||
|
|
||||||
|
enum // RC522_PCD_COLL_REG
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* all received bits will be cleared after a collision
|
||||||
|
* only used during bitwise anticollision at 106 kBd,
|
||||||
|
* otherwise it is set to logic 1
|
||||||
|
*/
|
||||||
|
RC522_PCD_VALUES_AFTER_COLL_BIT = BIT7,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* no collision detected or the position of the collision is
|
||||||
|
* out of the range of CollPos[4:0]
|
||||||
|
*/
|
||||||
|
RC522_PCD_COLL_POS_NOT_VALID_BIT = BIT5,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // RC522_PCD_ERROR_REG
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* data is written into the FIFO buffer by the host during the MFAuthent
|
||||||
|
* command or if data is written into the FIFO buffer by the host during the
|
||||||
|
* time between sending the last bit on the RF interface and receiving the
|
||||||
|
* last bit on the RF interface
|
||||||
|
*/
|
||||||
|
RC522_PCD_WR_ERR_BIT = BIT6,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* internal temperature sensor detects overheating, in which case the
|
||||||
|
* antenna drivers are automatically switched off
|
||||||
|
*/
|
||||||
|
RC522_PCD_TEMP_ERR_BIT = BIT5,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the host or a MFRC522’s internal state machine (e.g. receiver) tries to
|
||||||
|
* write data to the FIFO buffer even though it is already full
|
||||||
|
*/
|
||||||
|
RC522_PCD_BUFFER_OVFL_BIT = BIT4,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a bit-collision is detected
|
||||||
|
* cleared automatically at receiver start-up phase
|
||||||
|
* only valid during the bitwise anticollision at 106 kBd
|
||||||
|
* always set to logic 0 during communication protocols at 212 kBd,
|
||||||
|
* 424 kBd and 848 kBd
|
||||||
|
*/
|
||||||
|
RC522_PCD_COLL_ERR_BIT = BIT3,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the RxModeReg register’s RxCRCEn bit is set and the CRC calculation fails
|
||||||
|
* automatically cleared to logic 0 during receiver start-up phase
|
||||||
|
*/
|
||||||
|
RC522_PCD_CRC_ERR_BIT = BIT2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parity check failed
|
||||||
|
* automatically cleared during receiver start-up phase
|
||||||
|
* only valid for ISO/IEC 14443 A/MIFARE communication at 106 kBd
|
||||||
|
*/
|
||||||
|
RC522_PCD_PARITY_ERR_BIT = BIT1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set to logic 1 if the SOF is incorrect
|
||||||
|
* automatically cleared during receiver start-up phase
|
||||||
|
* bit is only valid for 106 kBd
|
||||||
|
* during the MFAuthent command, the ProtocolErr bit is set to logic 1 if the
|
||||||
|
* number of bytes received in one data stream is incorrect
|
||||||
|
*/
|
||||||
|
RC522_PCD_PROTOCOL_ERR_BIT = BIT0,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // RC522_PCD_COM_INT_REQ_REG
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 1 - indicates that the marked bits in the ComIrqReg register are set
|
||||||
|
* 0 - indicates that the marked bits in the ComIrqReg register are cleared
|
||||||
|
*/
|
||||||
|
RC522_PCD_SET_1_BIT = BIT7,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set immediately after the last bit of the transmitted data was sent out
|
||||||
|
*/
|
||||||
|
RC522_PCD_TX_IRQ_BIT = BIT6,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* receiver has detected the end of a valid data stream
|
||||||
|
* if the RxModeReg register’s RxNoErr bit is set to logic 1, the RxIRq bit is
|
||||||
|
* only set to logic 1 when data bytes are available in the FIFO
|
||||||
|
*/
|
||||||
|
RC522_PCD_RX_IRQ_BIT = BIT5,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a command terminates, for example, when the CommandReg changes
|
||||||
|
* its value from any command to the Idle command (see Table 149 on page 70)
|
||||||
|
* if an unknown command is started, the CommandReg register
|
||||||
|
* Command[3:0] value changes to the idle state and the IdleIRq bit is set
|
||||||
|
* The microcontroller starting the Idle command does not set the IdleIRq bit
|
||||||
|
*/
|
||||||
|
RC522_PCD_IDLE_IRQ_BIT = BIT4,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the Status1Reg register’s HiAlert bit is set
|
||||||
|
* in opposition to the HiAlert bit, the HiAlertIRq bit stores this event and
|
||||||
|
* can only be reset as indicated by the Set1 bit in this register
|
||||||
|
*/
|
||||||
|
RC522_PCD_HI_ALERT_IRQ_BIT = BIT3,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status1Reg register’s LoAlert bit is set
|
||||||
|
* in opposition to the LoAlert bit, the LoAlertIRq bit stores this event and
|
||||||
|
* can only be reset as indicated by the Set1 bit in this register
|
||||||
|
*/
|
||||||
|
RC522_PCD_LO_ALERT_IRQ_BIT = BIT2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* any error bit in the ErrorReg register is set
|
||||||
|
*/
|
||||||
|
RC522_PCD_ERR_IRQ_BIT = BIT1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the timer decrements the timer value in register TCounterValReg to zero
|
||||||
|
*/
|
||||||
|
RC522_PCD_TIMER_IRQ_BIT = BIT0,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // RC522_PCD_COMMAND_REG
|
||||||
|
{
|
||||||
|
// Soft power-down mode entered
|
||||||
|
RC522_PCD_POWER_DOWN_BIT = BIT4,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // RC522_PCD_TX_CONTROL_REG
|
||||||
|
{
|
||||||
|
// Output signal on pin TX2 delivers the 13.56 MHz energy carrier modulated by the transmission data
|
||||||
|
RC522_PCD_TX2_RF_EN_BIT = BIT1,
|
||||||
|
|
||||||
|
// Output signal on pin TX1 delivers the 13.56 MHz energy carrier modulated by the transmission data
|
||||||
|
RC522_PCD_TX1_RF_EN_BIT = BIT0,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Places the MFRC522 in Idle mode. The Idle command also terminates itself.
|
||||||
|
*/
|
||||||
|
RC522_PCD_IDLE_CMD = 0b0000,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The FIFO buffer content is transferred to the CRC coprocessor and the CRC calculation is
|
||||||
|
* started. The calculation result is stored in the CRCResultReg register. The CRC
|
||||||
|
* calculation is not limited to a dedicated number of bytes. The calculation is not stopped
|
||||||
|
* when the FIFO buffer is empty during the data stream. The next byte written to the FIFO
|
||||||
|
* buffer is added to the calculation.
|
||||||
|
*
|
||||||
|
* The CRC preset value is controlled by the ModeReg register’s CRCPreset[1:0] bits. The
|
||||||
|
* value is loaded in to the CRC coprocessor when the command starts.
|
||||||
|
*
|
||||||
|
* This command must be terminated by writing a command to the CommandReg register,
|
||||||
|
* such as, the Idle command.
|
||||||
|
*
|
||||||
|
* If the AutoTestReg register’s SelfTest[3:0] bits are set correctly, the MFRC522 enters Self
|
||||||
|
* Test mode. Starting the CalcCRC command initiates a digital self test. The result of the
|
||||||
|
* self test is written to the FIFO buffer.
|
||||||
|
*/
|
||||||
|
RC522_PCD_CALC_CRC_CMD = 0b0011,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This command continuously repeats the transmission of data from the FIFO buffer and the
|
||||||
|
* reception of data from the RF field. The first action is transmit and after transmission the
|
||||||
|
* command is changed to receive a data stream.
|
||||||
|
*
|
||||||
|
* Each transmit process must be started by setting the BitFramingReg register’s StartSend
|
||||||
|
* bit to logic 1. This command must be cleared by writing any command to the
|
||||||
|
* CommandReg register
|
||||||
|
*/
|
||||||
|
RC522_PCD_TRANSCEIVE_CMD = 0b1100,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This command manages MIFARE authentication to enable a secure communication to
|
||||||
|
* any MIFARE Mini, MIFARE 1K and MIFARE 4K card
|
||||||
|
*/
|
||||||
|
RC522_PCD_MF_AUTH_CMD = 0b1110,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This command performs a reset of the device. The configuration data of the internal buffer
|
||||||
|
* remains unchanged. All registers are set to the reset values. This command automatically
|
||||||
|
* terminates when finished.
|
||||||
|
*/
|
||||||
|
RC522_PCD_SOFT_RESET_CMD = 0b1111,
|
||||||
|
} rc522_pcd_command_t;
|
||||||
|
|
||||||
|
enum // RC522_PCD_TIMER_MODE_REG
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* When 1:
|
||||||
|
* - Timer starts automatically at the end of the transmission in
|
||||||
|
* all communication modes at all speeds
|
||||||
|
* - If the RxModeReg register’s RxMultiple bit is not set, the
|
||||||
|
* timer stops immediately after receiving the 5th bit (1 start bit,
|
||||||
|
* 4 data bits)
|
||||||
|
* - If the RxMultiple bit is set to logic 1 the timer never stops, in
|
||||||
|
* which case the timer can be stopped by setting the
|
||||||
|
* ControlReg register’s TStopNow bit to logic 1
|
||||||
|
*
|
||||||
|
* When 0:
|
||||||
|
* - Indicates that the timer is not influenced by the protocol
|
||||||
|
*/
|
||||||
|
RC522_PCD_T_AUTO_BIT = BIT7,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // RC522_PCD_TX_ASK_REG
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Forces a 100 % ASK modulation independent of the ModGsPReg register setting
|
||||||
|
*/
|
||||||
|
RC522_PCD_FORCE_100_ASK_BIT = BIT6,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // RC522_PCD_MODE_REG
|
||||||
|
{
|
||||||
|
// Transmitter can only be started if an RF field is generated
|
||||||
|
RC522_PCD_TX_WAIT_RF_BIT = BIT5,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the polarity of pin MFIN
|
||||||
|
*
|
||||||
|
* 1 - Polarity of pin MFIN is active HIGH
|
||||||
|
* 0 - Polarity of pin MFIN is active LOW
|
||||||
|
*/
|
||||||
|
RC522_PCD_POL_MFIN_BIT = BIT3,
|
||||||
|
|
||||||
|
RC522_PCD_CRC_PRESET_1_BIT = BIT1,
|
||||||
|
RC522_PCD_CRC_PRESET_0_BIT = BIT0,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RC522_PCD_CRC_PRESET_0000H = (0x00), /* 0000h */
|
||||||
|
RC522_PCD_CRC_PRESET_6363H = (RC522_PCD_CRC_PRESET_0_BIT), /* 6363h */
|
||||||
|
RC522_PCD_CRC_PRESET_A671H = (RC522_PCD_CRC_PRESET_1_BIT), /* A671h */
|
||||||
|
RC522_PCD_CRC_PRESET_FFFFH = (RC522_PCD_CRC_PRESET_1_BIT | RC522_PCD_CRC_PRESET_0_BIT), /* FFFFh */
|
||||||
|
} rc522_pcd_crc_preset_value_t;
|
||||||
|
|
||||||
|
enum // RC522_PCD_DIV_INT_REQ_REG
|
||||||
|
{
|
||||||
|
// The CalcCRC command is active and all data is processed (CRC calculation is done)
|
||||||
|
RC522_PCD_CRC_IRQ_BIT = BIT2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // RC522_PCD_FIFO_LEVEL_REG
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Immediately clears the internal FIFO buffer’s read and write pointer
|
||||||
|
* and ErrorReg register’s BufferOvfl bit
|
||||||
|
*
|
||||||
|
* Reading this bit always returns 0
|
||||||
|
*/
|
||||||
|
RC522_PCD_FLUSH_BUFFER_BIT = BIT7,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // RC522_PCD_BIT_FRAMING_REG
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Starts the transmission of data
|
||||||
|
* Only valid in combination with the Transceive command
|
||||||
|
*/
|
||||||
|
RC522_PCD_START_SEND_BIT = BIT7,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // RC522_PCD_STATUS_2_REG
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Indicates that the MIFARE Crypto1 unit is switched on and
|
||||||
|
* therefore all data communication with the card is encrypted
|
||||||
|
* can only be set to logic 1 by a successful execution of the
|
||||||
|
* MFAuthent command.
|
||||||
|
* Only valid in Read/Write mode for MIFARE standard cards.
|
||||||
|
* This bit is cleared by software.
|
||||||
|
*/
|
||||||
|
RC522_PCD_MF_CRYPTO1_ON_BIT = BIT3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum // RC522_PCD_RX_MODE_REG
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* An invalid received data stream (less than 4 bits received) will
|
||||||
|
* be ignored and the receiver remains active
|
||||||
|
*/
|
||||||
|
RC522_PCD_RX_NO_ERR_BIT = BIT3,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RC522_PCD_FIRMWARE_CLONE = 0x88, // clone
|
||||||
|
RC522_PCD_FIRMWARE_00 = 0x90, // v0.0
|
||||||
|
RC522_PCD_FIRMWARE_10 = 0x91, // v1.0
|
||||||
|
RC522_PCD_FIRMWARE_20 = 0x92, // v2.0
|
||||||
|
RC522_PCD_FIRMWARE_COUNTERFEIT = 0x12, // counterfeit chip
|
||||||
|
} rc522_pcd_firmware_t;
|
||||||
|
|
||||||
|
typedef union
|
||||||
|
{
|
||||||
|
uint16_t value;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint8_t lsb;
|
||||||
|
uint8_t msb;
|
||||||
|
};
|
||||||
|
} rc522_pcd_crc_t;
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_reset(const rc522_handle_t rc522, uint32_t timeout_ms);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_calculate_crc(const rc522_handle_t rc522, const rc522_bytes_t *bytes, rc522_pcd_crc_t *result);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_init(const rc522_handle_t rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_firmware(const rc522_handle_t rc522, rc522_pcd_firmware_t *result);
|
||||||
|
|
||||||
|
char *rc522_pcd_firmware_name(rc522_pcd_firmware_t firmware);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_stop_active_command(const rc522_handle_t rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_clear_all_com_interrupts(const rc522_handle_t rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_fifo_write(const rc522_handle_t rc522, const rc522_bytes_t *bytes);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_fifo_read(const rc522_handle_t rc522, rc522_bytes_t *bytes);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_fifo_flush(const rc522_handle_t rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_start_data_transmission(const rc522_handle_t rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_stop_data_transmission(const rc522_handle_t rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_stop_crypto1(const rc522_handle_t rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_rw_test(const rc522_handle_t rc522);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_write_n(const rc522_handle_t rc522, rc522_pcd_register_t addr, const rc522_bytes_t *bytes);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_write(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t val);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_read_n(const rc522_handle_t rc522, rc522_pcd_register_t addr, rc522_bytes_t *bytes);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_read(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t *value_ref);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_set_bits(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t bits);
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_clear_bits(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t bits);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
115
components/rc522/internal/rc522_picc_internal.h
Normal file
115
components/rc522/internal/rc522_picc_internal.h
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rc522_pcd_internal.h"
|
||||||
|
#include "rc522_picc.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commands sent to the PICC
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4)
|
||||||
|
* REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for
|
||||||
|
* anticollision or selection. 7 bit frame.
|
||||||
|
*/
|
||||||
|
RC522_PICC_CMD_REQA = 0x26,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
RC522_PICC_CMD_WUPA = 0x52,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cascade Tag. Not really a command, but used during anti collision.
|
||||||
|
* TODO: move it from here in that case
|
||||||
|
*/
|
||||||
|
RC522_PICC_CMD_CT = 0x88,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anti collision/Select, Cascade Level 1
|
||||||
|
*/
|
||||||
|
RC522_PICC_CMD_SEL_CL1 = 0x93,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anti collision/Select, Cascade Level 2
|
||||||
|
*/
|
||||||
|
RC522_PICC_CMD_SEL_CL2 = 0x95,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anti collision/Select, Cascade Level 3
|
||||||
|
*/
|
||||||
|
RC522_PICC_CMD_SEL_CL3 = 0x97,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT.
|
||||||
|
*/
|
||||||
|
RC522_PICC_CMD_HLTA = 0x50,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request command for Answer To Reset.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
RC522_PICC_CMD_RATS = 0xE0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AUTHenticate command for Ultralight C & AES.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
RC522_PICC_CMD_UL_AUTH = 0x1A,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET Version level 3 command for Ultralight EV1 & NANO, and NTAGs.
|
||||||
|
*/
|
||||||
|
RC522_PICC_CMD_GETV = 0x60,
|
||||||
|
} rc522_picc_command_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rc522_pcd_command_t pcd_command;
|
||||||
|
rc522_bytes_t bytes;
|
||||||
|
uint8_t expected_interrupts;
|
||||||
|
uint8_t rx_align;
|
||||||
|
uint8_t valid_bits;
|
||||||
|
bool check_crc;
|
||||||
|
} rc522_picc_transaction_t;
|
||||||
|
|
||||||
|
typedef struct rc522_picc_transaction_context rc522_picc_transaction_context_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rc522_bytes_t bytes;
|
||||||
|
uint8_t valid_bits;
|
||||||
|
} rc522_picc_transaction_result_t;
|
||||||
|
|
||||||
|
esp_err_t rc522_picc_send(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction,
|
||||||
|
rc522_picc_transaction_context_t *out_context);
|
||||||
|
|
||||||
|
esp_err_t rc522_picc_transceive(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction,
|
||||||
|
rc522_picc_transaction_result_t *out_result);
|
||||||
|
|
||||||
|
esp_err_t rc522_picc_reqa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa);
|
||||||
|
|
||||||
|
esp_err_t rc522_picc_wupa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa);
|
||||||
|
|
||||||
|
esp_err_t rc522_picc_select(
|
||||||
|
const rc522_handle_t rc522, rc522_picc_uid_t *out_uid, uint8_t *out_sak, bool skip_anticoll);
|
||||||
|
|
||||||
|
esp_err_t rc522_picc_halta(const rc522_handle_t rc522, rc522_picc_t *picc);
|
||||||
|
|
||||||
|
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_picc_type_t rc522_picc_get_type(const rc522_picc_t *picc);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
97
components/rc522/internal/rc522_types_internal.h
Normal file
97
components/rc522/internal/rc522_types_internal.h
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_check.h>
|
||||||
|
#include <esp_bit_defs.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/event_groups.h>
|
||||||
|
#include "rc522_types.h"
|
||||||
|
#include "rc522_picc.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define RC522_LOG_TAG "rc522"
|
||||||
|
|
||||||
|
#define RC522_POLL_INTERVAL_MS_DEFAULT (120)
|
||||||
|
#define RC522_POLL_INTERVAL_MS_MIN (50)
|
||||||
|
#define RC522_TASK_STACK_SIZE_DEFAULT (4 * 1024)
|
||||||
|
#define RC522_TASK_PRIORITY_DEFAULT (3)
|
||||||
|
|
||||||
|
#define RC522_TASK_STOPPED_BIT (BIT0)
|
||||||
|
|
||||||
|
#define RC522_LOG_LEVEL LOG_LOCAL_LEVEL
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RC522_STATE_UNDEFINED = 0,
|
||||||
|
RC522_STATE_CREATED,
|
||||||
|
RC522_STATE_POLLING, /*<! Scanning for nearby PICCs */
|
||||||
|
RC522_STATE_PAUSED,
|
||||||
|
} rc522_state_t;
|
||||||
|
|
||||||
|
struct rc522
|
||||||
|
{
|
||||||
|
rc522_config_t *config; /*<! Configuration */
|
||||||
|
bool exit_requested; /*<! Indicates whether polling task exit is requested */
|
||||||
|
TaskHandle_t task_handle; /*<! Handle of task */
|
||||||
|
esp_event_loop_handle_t event_handle; /*<! Handle of event loop */
|
||||||
|
rc522_state_t state; /*<! Current state */
|
||||||
|
rc522_picc_t picc;
|
||||||
|
EventGroupHandle_t bits;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t *ptr;
|
||||||
|
uint8_t length;
|
||||||
|
} rc522_bytes_t;
|
||||||
|
|
||||||
|
#define RC522_LOG_DEFINE_BASE() static const char *TAG = RC522_LOG_TAG
|
||||||
|
|
||||||
|
#define RC522_LOG(esp_log_foo, format, ...) esp_log_foo(TAG, "%s(%d): " format, __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||||
|
#define RC522_LOGE(format, ...) RC522_LOG(ESP_LOGE, format, ##__VA_ARGS__)
|
||||||
|
#define RC522_LOGW(format, ...) RC522_LOG(ESP_LOGW, format, ##__VA_ARGS__)
|
||||||
|
#define RC522_LOGI(format, ...) RC522_LOG(ESP_LOGI, format, ##__VA_ARGS__)
|
||||||
|
#define RC522_LOGD(format, ...) RC522_LOG(ESP_LOGD, format, ##__VA_ARGS__)
|
||||||
|
#define RC522_LOGV(format, ...) RC522_LOG(ESP_LOGV, format, ##__VA_ARGS__)
|
||||||
|
#define RC522_LOG_WRITE(format, ...) esp_log_write(ESP_LOG_INFO, TAG, format, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#define RC522_RETURN_ON_ERROR(x) \
|
||||||
|
do { \
|
||||||
|
esp_err_t err_rc_ = (x); \
|
||||||
|
if (unlikely(err_rc_ != ESP_OK)) { \
|
||||||
|
ESP_LOGE(TAG, "%s(%d): %04" RC522_X "", __FUNCTION__, __LINE__, err_rc_); \
|
||||||
|
return err_rc_; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
while (0)
|
||||||
|
|
||||||
|
#define RC522_LOG_ON_ERROR(x) \
|
||||||
|
do { \
|
||||||
|
esp_err_t err_rc_ = (x); \
|
||||||
|
if (unlikely(err_rc_ != ESP_OK)) { \
|
||||||
|
ESP_LOGE(TAG, "%s(%d): %04" RC522_X "", __FUNCTION__, __LINE__, err_rc_); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
while (0)
|
||||||
|
|
||||||
|
#define RC522_RETURN_ON_FALSE(a, err_code) ESP_RETURN_ON_FALSE(a, err_code, TAG, "")
|
||||||
|
#define RC522_CHECK_WITH_MESSAGE(a, message) ESP_RETURN_ON_FALSE(!(a), ESP_ERR_INVALID_ARG, TAG, message)
|
||||||
|
#define RC522_CHECK_AND_RETURN(a, ret_val) ESP_RETURN_ON_FALSE(!(a), ret_val, TAG, #a)
|
||||||
|
#define RC522_CHECK(a) ESP_RETURN_ON_FALSE(!(a), ESP_ERR_INVALID_ARG, TAG, #a)
|
||||||
|
#define RC522_CHECK_BYTES(b) RC522_CHECK(b == NULL || (b)->ptr == NULL || (b)->length < 1)
|
||||||
|
|
||||||
|
#define RC522_RETURN_ON_ERROR_SILENTLY(x) \
|
||||||
|
do { \
|
||||||
|
esp_err_t err_rc_ = (x); \
|
||||||
|
if (unlikely(err_rc_ != ESP_OK)) { \
|
||||||
|
return err_rc_; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
while (0)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
114
components/rc522/src/driver/rc522_i2c.c
Normal file
114
components/rc522/src/driver/rc522_i2c.c
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "rc522_helpers_internal.h"
|
||||||
|
#include "rc522_types_internal.h"
|
||||||
|
#include "rc522_driver_internal.h"
|
||||||
|
#include "driver/rc522_i2c.h"
|
||||||
|
|
||||||
|
#define RC522_I2C_RW_TIMEOUT_MS 1000
|
||||||
|
|
||||||
|
RC522_LOG_DEFINE_BASE();
|
||||||
|
|
||||||
|
static esp_err_t rc522_i2c_install(const rc522_driver_handle_t driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK(driver->config == NULL);
|
||||||
|
|
||||||
|
rc522_i2c_config_t *conf = (rc522_i2c_config_t *)(driver->config);
|
||||||
|
|
||||||
|
if (!conf->bus) {
|
||||||
|
RC522_CHECK(conf->bus_config == NULL);
|
||||||
|
RC522_RETURN_ON_ERROR(i2c_new_master_bus(conf->bus_config, &conf->bus));
|
||||||
|
}
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(
|
||||||
|
i2c_master_bus_add_device(conf->bus, &conf->dev_config, (i2c_master_dev_handle_t *)(&driver->device)));
|
||||||
|
|
||||||
|
if (conf->rst_io_num > GPIO_NUM_NC) {
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_driver_init_rst_pin(conf->rst_io_num));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_i2c_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK(driver->config == NULL);
|
||||||
|
RC522_CHECK_BYTES(bytes);
|
||||||
|
|
||||||
|
// FIXME: Find a way to send [address + buffer]
|
||||||
|
// without need for second buffer
|
||||||
|
uint8_t buffer2[64];
|
||||||
|
|
||||||
|
buffer2[0] = address;
|
||||||
|
memcpy(buffer2 + 1, bytes->ptr, bytes->length);
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(i2c_master_transmit((i2c_master_dev_handle_t)driver->device,
|
||||||
|
buffer2,
|
||||||
|
bytes->length + 1,
|
||||||
|
RC522_I2C_RW_TIMEOUT_MS));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_i2c_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK(driver->config == NULL);
|
||||||
|
RC522_CHECK_BYTES(bytes);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(i2c_master_transmit_receive((i2c_master_dev_handle_t)driver->device,
|
||||||
|
&address,
|
||||||
|
1,
|
||||||
|
bytes->ptr,
|
||||||
|
bytes->length,
|
||||||
|
RC522_I2C_RW_TIMEOUT_MS));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_i2c_reset(const rc522_driver_handle_t driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK(driver->config == NULL);
|
||||||
|
|
||||||
|
rc522_i2c_config_t *conf = (rc522_i2c_config_t *)(driver->config);
|
||||||
|
|
||||||
|
if (conf->rst_io_num < 0) {
|
||||||
|
return RC522_ERR_RST_PIN_UNUSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
|
||||||
|
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
|
||||||
|
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, !RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
|
||||||
|
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_i2c_uninstall(const rc522_driver_handle_t driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK(driver->config == NULL);
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(i2c_master_bus_rm_device((i2c_master_dev_handle_t)driver->device));
|
||||||
|
// TODO: remove bus if handle was NULL?
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_i2c_create(const rc522_i2c_config_t *config, rc522_driver_handle_t *driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(config == NULL);
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_driver_create(config, sizeof(rc522_i2c_config_t), driver));
|
||||||
|
|
||||||
|
(*driver)->install = rc522_i2c_install;
|
||||||
|
(*driver)->send = rc522_i2c_send;
|
||||||
|
(*driver)->receive = rc522_i2c_receive;
|
||||||
|
(*driver)->reset = rc522_i2c_reset;
|
||||||
|
(*driver)->uninstall = rc522_i2c_uninstall;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
216
components/rc522/src/driver/rc522_spi.c
Normal file
216
components/rc522/src/driver/rc522_spi.c
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "rc522_helpers_internal.h"
|
||||||
|
#include "rc522_types_internal.h"
|
||||||
|
#include "rc522_driver_internal.h"
|
||||||
|
#include "driver/rc522_spi.h"
|
||||||
|
|
||||||
|
RC522_LOG_DEFINE_BASE();
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
gpio_num_t cs_io_num;
|
||||||
|
} rc522_spi_meta_t;
|
||||||
|
|
||||||
|
static void rc522_spi_transaction_pre_cb(spi_transaction_t *trans);
|
||||||
|
|
||||||
|
static void rc522_spi_transaction_post_cb(spi_transaction_t *trans);
|
||||||
|
|
||||||
|
static esp_err_t rc522_spi_uninstall(const rc522_driver_handle_t driver);
|
||||||
|
|
||||||
|
static esp_err_t rc522_spi_install(const rc522_driver_handle_t driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK(driver->config == NULL);
|
||||||
|
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
rc522_spi_meta_t *meta = calloc(1, sizeof(rc522_spi_meta_t));
|
||||||
|
ESP_GOTO_ON_FALSE(meta != NULL, ESP_ERR_NO_MEM, error, TAG, "nomem");
|
||||||
|
driver->meta = (void *)meta;
|
||||||
|
|
||||||
|
rc522_spi_config_t *conf = (rc522_spi_config_t *)(driver->config);
|
||||||
|
|
||||||
|
if (conf->bus_config) {
|
||||||
|
// overwrite bus config
|
||||||
|
conf->bus_config->quadwp_io_num = GPIO_NUM_NC;
|
||||||
|
conf->bus_config->quadhd_io_num = GPIO_NUM_NC;
|
||||||
|
|
||||||
|
ESP_GOTO_ON_ERROR(spi_bus_initialize(conf->host_id, conf->bus_config, conf->dma_chan),
|
||||||
|
error,
|
||||||
|
TAG,
|
||||||
|
"spi bus initialization failed");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RC522_LOGD("skips spi bus initialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{ overwrite device config
|
||||||
|
if (conf->dev_config.clock_speed_hz == 0) {
|
||||||
|
conf->dev_config.clock_speed_hz = 5000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
conf->dev_config.mode = 0;
|
||||||
|
|
||||||
|
if (conf->dev_config.queue_size == 0) {
|
||||||
|
conf->dev_config.queue_size = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conf->dev_config.flags == 0) {
|
||||||
|
conf->dev_config.flags = SPI_DEVICE_HALFDUPLEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
conf->dev_config.command_bits = 8;
|
||||||
|
conf->dev_config.address_bits = 0;
|
||||||
|
conf->dev_config.dummy_bits = 0;
|
||||||
|
// }}
|
||||||
|
|
||||||
|
// ESP32 SPI bus has limitation of 3 CS lines, so we need to use
|
||||||
|
// software control for CS line in order to use more devices.
|
||||||
|
{
|
||||||
|
meta->cs_io_num = conf->dev_config.spics_io_num;
|
||||||
|
conf->dev_config.spics_io_num = GPIO_NUM_NC;
|
||||||
|
|
||||||
|
gpio_config_t cs_conf = {
|
||||||
|
.intr_type = GPIO_INTR_DISABLE,
|
||||||
|
.mode = GPIO_MODE_OUTPUT,
|
||||||
|
.pin_bit_mask = (1ULL << meta->cs_io_num),
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_GOTO_ON_ERROR(gpio_config(&cs_conf), error, TAG, "cs gpio config failed");
|
||||||
|
ESP_GOTO_ON_ERROR(gpio_set_level(meta->cs_io_num, 1), error, TAG, "cs set level failed");
|
||||||
|
|
||||||
|
conf->dev_config.pre_cb = &rc522_spi_transaction_pre_cb;
|
||||||
|
conf->dev_config.post_cb = &rc522_spi_transaction_post_cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_GOTO_ON_ERROR(spi_bus_add_device(conf->host_id, &conf->dev_config, (spi_device_handle_t *)(&driver->device)),
|
||||||
|
error,
|
||||||
|
TAG,
|
||||||
|
"spi bus add device failed");
|
||||||
|
|
||||||
|
if (conf->rst_io_num > GPIO_NUM_NC) {
|
||||||
|
ESP_GOTO_ON_ERROR(rc522_driver_init_rst_pin(conf->rst_io_num), error, TAG, "init rst pin failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
goto exit;
|
||||||
|
error:
|
||||||
|
RC522_LOG_ON_ERROR(rc522_spi_uninstall(driver));
|
||||||
|
exit:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_spi_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK(driver->device == NULL);
|
||||||
|
RC522_CHECK_BYTES(bytes);
|
||||||
|
|
||||||
|
uint8_t cmd_byte = (address << 1) & 0x7E;
|
||||||
|
|
||||||
|
esp_err_t ret = spi_device_polling_transmit((spi_device_handle_t)(driver->device),
|
||||||
|
&(spi_transaction_t) {
|
||||||
|
.cmd = cmd_byte,
|
||||||
|
.length = 8 * bytes->length,
|
||||||
|
.tx_buffer = bytes->ptr,
|
||||||
|
.user = driver,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_spi_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK(driver->device == NULL);
|
||||||
|
RC522_CHECK_BYTES(bytes);
|
||||||
|
|
||||||
|
uint8_t cmd_byte = ((address << 1) & 0x7E) | 0x80;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < bytes->length; i++) {
|
||||||
|
RC522_RETURN_ON_ERROR(spi_device_polling_transmit((spi_device_handle_t)(driver->device),
|
||||||
|
&(spi_transaction_t) {
|
||||||
|
.cmd = cmd_byte,
|
||||||
|
.rxlength = 8,
|
||||||
|
.rx_buffer = (bytes->ptr + i),
|
||||||
|
.user = driver,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_spi_reset(const rc522_driver_handle_t driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK(driver->config == NULL);
|
||||||
|
|
||||||
|
rc522_spi_config_t *conf = (rc522_spi_config_t *)(driver->config);
|
||||||
|
|
||||||
|
if (conf->rst_io_num < 0) {
|
||||||
|
return RC522_ERR_RST_PIN_UNUSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
|
||||||
|
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
|
||||||
|
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, !RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
|
||||||
|
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_spi_uninstall(const rc522_driver_handle_t driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
|
||||||
|
if (driver->meta) {
|
||||||
|
free(driver->meta);
|
||||||
|
driver->meta = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driver->device) {
|
||||||
|
RC522_LOG_ON_ERROR(spi_bus_remove_device((spi_device_handle_t)(driver->device)));
|
||||||
|
driver->device = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driver->config) {
|
||||||
|
rc522_spi_config_t *conf = (rc522_spi_config_t *)(driver->config);
|
||||||
|
|
||||||
|
if (conf->bus_config) {
|
||||||
|
RC522_LOG_ON_ERROR(spi_bus_free(conf->host_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_spi_create(const rc522_spi_config_t *config, rc522_driver_handle_t *driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(config == NULL);
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_driver_create(config, sizeof(rc522_spi_config_t), driver));
|
||||||
|
|
||||||
|
(*driver)->install = rc522_spi_install;
|
||||||
|
(*driver)->send = rc522_spi_send;
|
||||||
|
(*driver)->receive = rc522_spi_receive;
|
||||||
|
(*driver)->reset = rc522_spi_reset;
|
||||||
|
(*driver)->uninstall = rc522_spi_uninstall;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rc522_spi_transaction_pre_cb(spi_transaction_t *trans)
|
||||||
|
{
|
||||||
|
rc522_driver_handle_t driver = (rc522_driver_handle_t)trans->user;
|
||||||
|
rc522_spi_meta_t *meta = (rc522_spi_meta_t *)(driver->meta);
|
||||||
|
RC522_LOG_ON_ERROR(gpio_set_level(meta->cs_io_num, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rc522_spi_transaction_post_cb(spi_transaction_t *trans)
|
||||||
|
{
|
||||||
|
rc522_driver_handle_t driver = (rc522_driver_handle_t)trans->user;
|
||||||
|
rc522_spi_meta_t *meta = (rc522_spi_meta_t *)(driver->meta);
|
||||||
|
RC522_LOG_ON_ERROR(gpio_set_level(meta->cs_io_num, 1));
|
||||||
|
}
|
||||||
604
components/rc522/src/picc/rc522_mifare.c
Normal file
604
components/rc522/src/picc/rc522_mifare.c
Normal file
@@ -0,0 +1,604 @@
|
|||||||
|
/**
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
* # Memory access management via access bits
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* The access conditions for every data block and sector trailer are defined by 3 bits, which
|
||||||
|
* are stored non-inverted and inverted in the sector trailer of the specified sector.
|
||||||
|
*
|
||||||
|
* The access bits control the rights of memory access using the secret keys A and B. The
|
||||||
|
* access conditions may be altered, provided one knows the relevant key and the current
|
||||||
|
* access condition allows this operation.
|
||||||
|
*
|
||||||
|
* @warning
|
||||||
|
* With each memory access the internal logic verifies the format of the access
|
||||||
|
* conditions. If it detects a format violation the whole sector is irreversibly blocked.
|
||||||
|
*
|
||||||
|
* +-----------------------------------------------------------------------------------+
|
||||||
|
* | Sector Trailer |
|
||||||
|
* +=============+===+===+===+===+===+===+===+===+===+===+====+====+====+====+====+====+
|
||||||
|
* | Byte number | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|
||||||
|
* +-------------+---+---+---+---+---+---+---+---+---+---+----+----+----+----+----+----+
|
||||||
|
* | Description | Key A | Access Bits | Key B (optional) |
|
||||||
|
* +-------------+-----------------------+---------------+-----------------------------+
|
||||||
|
*
|
||||||
|
* Bytes 6, 7 and 8 of sector trailer block are bytes that holds access bits.
|
||||||
|
* Next table shows descriptions for each bit of those bytes.
|
||||||
|
*
|
||||||
|
* +-----+------+------+------+------+------+------+------+------+
|
||||||
|
* | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
||||||
|
* +-----+------+------+------+------+------+------+------+------+
|
||||||
|
* | [6] | ~C23 | ~C22 | ~C21 | ~C20 | ~C13 | ~C12 | ~C11 | ~C10 |
|
||||||
|
* +-----+------+------+------+------+------+------+------+------+
|
||||||
|
* | [7] | C13 | C12 | C11 | C10 | ~C33 | ~C32 | ~C31 | ~C30 |
|
||||||
|
* +-----+------+------+------+------+------+------+------+------+
|
||||||
|
* | [8] | C33 | C32 | C31 | C30 | C23 | C22 | C21 | C20 |
|
||||||
|
* +-----+------+------+------+------+------+------+------+------+
|
||||||
|
*
|
||||||
|
* Where Cxy is access bit Cx for the block (group) y, for example:
|
||||||
|
* - C10 is the access bit C1 for the block (group) 0,
|
||||||
|
* - C20 is the access bit C2 for the block (group) 0,
|
||||||
|
* - ...,
|
||||||
|
* - C33 is the access bit C3 for the sector trailer,
|
||||||
|
* - ~C10 is the inverted bit C10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "rc522_types_internal.h"
|
||||||
|
#include "rc522_internal.h"
|
||||||
|
#include "rc522_helpers_internal.h"
|
||||||
|
#include "rc522_pcd_internal.h"
|
||||||
|
#include "rc522_picc_internal.h"
|
||||||
|
#include "picc/rc522_mifare.h"
|
||||||
|
|
||||||
|
RC522_LOG_DEFINE_BASE();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The commands used for MIFARE Classic
|
||||||
|
*/
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Perform authentication with Key A
|
||||||
|
*/
|
||||||
|
RC522_MIFARE_AUTH_KEY_A_CMD = 0x60,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform authentication with Key B
|
||||||
|
*/
|
||||||
|
RC522_MIFARE_AUTH_KEY_B_CMD = 0x61,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads one 16 byte block from the authenticated sector of the PICC
|
||||||
|
*/
|
||||||
|
RC522_MIFARE_READ_CMD = 0x30,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes one 16 byte block to the authenticated sector of the PICC
|
||||||
|
*/
|
||||||
|
RC522_MIFARE_WRITE_CMD = 0xA0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrements the contents of a block and stores the result in the internal data register
|
||||||
|
*/
|
||||||
|
RC522_MIFARE_DECREMENT_CMD = 0xC0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the contents of a block and stores the result in the internal data register
|
||||||
|
*/
|
||||||
|
RC522_MIFARE_INCREMENT_CMD = 0xC1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the contents of a block into the internal data register
|
||||||
|
*/
|
||||||
|
RC522_MIFARE_RESTORE_CMD = 0xC2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the contents of the internal data register to a block
|
||||||
|
*/
|
||||||
|
RC522_MIFARE_TRANSFER_CMD = 0xB0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if PICC type is compatible with MIFARE Classic protocol
|
||||||
|
*/
|
||||||
|
bool rc522_mifare_type_is_classic_compatible(rc522_picc_type_t type)
|
||||||
|
{
|
||||||
|
return type == RC522_PICC_TYPE_MIFARE_MINI || type == RC522_PICC_TYPE_MIFARE_1K
|
||||||
|
|| type == RC522_PICC_TYPE_MIFARE_4K;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_mifare_auth_sector(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_key_t *key)
|
||||||
|
{
|
||||||
|
RC522_CHECK(sector_desc == NULL);
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_auth(rc522, picc, sector_desc->block_0_address, key));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_auth(
|
||||||
|
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address, const rc522_mifare_key_t *key)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(key == NULL);
|
||||||
|
|
||||||
|
// TODO: Validate block_address
|
||||||
|
|
||||||
|
RC522_LOGD("MIFARE AUTH (block_address=%02" RC522_X ")", block_address);
|
||||||
|
|
||||||
|
uint8_t send_data[12];
|
||||||
|
|
||||||
|
switch (key->type) {
|
||||||
|
case RC522_MIFARE_KEY_A:
|
||||||
|
send_data[0] = RC522_MIFARE_AUTH_KEY_A_CMD;
|
||||||
|
break;
|
||||||
|
case RC522_MIFARE_KEY_B:
|
||||||
|
send_data[0] = RC522_MIFARE_AUTH_KEY_B_CMD;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
RC522_LOGE("Invalid key type");
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_data[1] = block_address;
|
||||||
|
memcpy(send_data + 2, key->value, RC522_MIFARE_KEY_SIZE);
|
||||||
|
|
||||||
|
// Use the last uid bytes
|
||||||
|
// 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.
|
||||||
|
memcpy(send_data + 8, picc->uid.value + picc->uid.length - 4, 4);
|
||||||
|
|
||||||
|
// Start the authentication.
|
||||||
|
|
||||||
|
rc522_picc_transaction_t transaction = {
|
||||||
|
.pcd_command = RC522_PCD_MF_AUTH_CMD,
|
||||||
|
.expected_interrupts = RC522_PCD_IDLE_IRQ_BIT,
|
||||||
|
.bytes = { .ptr = send_data, .length = sizeof(send_data) },
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t ret = rc522_picc_send(rc522, &transaction, NULL);
|
||||||
|
|
||||||
|
// 10.3.1.9
|
||||||
|
//
|
||||||
|
// "If an error occurs during authentication,
|
||||||
|
// the ErrorReg register’s ProtocolErr bit is set to logic 1
|
||||||
|
// and the Status2Reg register’s Crypto1On bit is set to logic 0"
|
||||||
|
|
||||||
|
// Timer interrupt fires before ProtocolErr bit is set to 1
|
||||||
|
// so we will only check for Crypto1On bit (even if ret == ESP_OK)
|
||||||
|
uint8_t status2;
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_STATUS_2_REG, &status2));
|
||||||
|
|
||||||
|
if (!(status2 & RC522_PCD_MF_CRYPTO1_ON_BIT)) {
|
||||||
|
return RC522_ERR_MIFARE_AUTHENTICATION_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
|
||||||
|
uint8_t out_buffer[RC522_MIFARE_BLOCK_SIZE])
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(out_buffer == NULL);
|
||||||
|
|
||||||
|
RC522_LOGD("MIFARE READ (block_address=%02" RC522_X ")", block_address);
|
||||||
|
|
||||||
|
uint8_t cmd_buffer[4] = { 0 };
|
||||||
|
|
||||||
|
// Build command buffer
|
||||||
|
cmd_buffer[0] = RC522_MIFARE_READ_CMD;
|
||||||
|
cmd_buffer[1] = block_address;
|
||||||
|
|
||||||
|
// Calculate CRC_A
|
||||||
|
rc522_pcd_crc_t crc = { 0 };
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = cmd_buffer, .length = 2 }, &crc));
|
||||||
|
|
||||||
|
cmd_buffer[2] = crc.lsb;
|
||||||
|
cmd_buffer[3] = crc.msb;
|
||||||
|
|
||||||
|
uint8_t block_buffer[RC522_MIFARE_BLOCK_SIZE + 2] = { 0 }; // +2 for CRC_A
|
||||||
|
|
||||||
|
rc522_picc_transaction_t transaction = {
|
||||||
|
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
|
||||||
|
.check_crc = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_picc_transaction_result_t result = {
|
||||||
|
.bytes = { .ptr = block_buffer, .length = sizeof(block_buffer) },
|
||||||
|
};
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
|
||||||
|
RC522_CHECK_AND_RETURN((result.bytes.length - 2) != RC522_MIFARE_BLOCK_SIZE, ESP_FAIL);
|
||||||
|
|
||||||
|
memcpy(out_buffer, block_buffer, sizeof(block_buffer) - 2); // -2 cuz of CRC_A
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_mifare_send(const rc522_handle_t rc522, const uint8_t *send_data, uint8_t send_length)
|
||||||
|
{
|
||||||
|
RC522_CHECK(send_data == NULL);
|
||||||
|
RC522_CHECK(send_length > RC522_MIFARE_BLOCK_SIZE);
|
||||||
|
|
||||||
|
#pragma GCC diagnostic ignored "-Wcast-qual"
|
||||||
|
uint8_t *sdata = (uint8_t *)send_data;
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
rc522_pcd_crc_t crc = { 0 };
|
||||||
|
RC522_RETURN_ON_ERROR(
|
||||||
|
rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = sdata, .length = send_length }, &crc));
|
||||||
|
|
||||||
|
uint8_t buffer[RC522_MIFARE_BLOCK_SIZE + 2]; // +2 for CRC_A
|
||||||
|
|
||||||
|
memcpy(buffer, send_data, send_length);
|
||||||
|
|
||||||
|
buffer[send_length] = crc.lsb;
|
||||||
|
buffer[send_length + 1] = crc.msb;
|
||||||
|
|
||||||
|
send_length += 2;
|
||||||
|
|
||||||
|
rc522_picc_transaction_t transaction = {
|
||||||
|
.bytes = { .ptr = buffer, .length = send_length },
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_picc_transaction_result_t result = {
|
||||||
|
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
|
||||||
|
};
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
|
||||||
|
|
||||||
|
// The PICC must reply with a 4 bit ACK
|
||||||
|
if (result.bytes.length != 1 || result.valid_bits != 4) {
|
||||||
|
return ESP_FAIL; // TODO: use custom err
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.bytes.ptr[0] != RC522_MIFARE_ACK) {
|
||||||
|
return RC522_ERR_MIFARE_NACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_get_number_of_sectors(rc522_picc_type_t type, uint8_t *out_result)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522_mifare_type_is_classic_compatible(type) == false);
|
||||||
|
RC522_CHECK(out_result == NULL);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case RC522_PICC_TYPE_MIFARE_MINI:
|
||||||
|
// Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes.
|
||||||
|
*out_result = 5;
|
||||||
|
return ESP_OK;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_1K:
|
||||||
|
// Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes.
|
||||||
|
*out_result = 16;
|
||||||
|
return ESP_OK;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_4K:
|
||||||
|
// Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes.
|
||||||
|
*out_result = 40;
|
||||||
|
return ESP_OK;
|
||||||
|
default:
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint8_t rc522_mifare_get_sector_index_by_block_address(uint8_t block_address)
|
||||||
|
{
|
||||||
|
if (block_address < 128) {
|
||||||
|
return block_address / 4;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 32 + ((block_address - 128) / 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_mifare_get_number_of_blocks_in_sector(uint8_t sector_index, uint8_t *out_result)
|
||||||
|
{
|
||||||
|
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
|
||||||
|
RC522_CHECK(out_result == NULL);
|
||||||
|
|
||||||
|
if (sector_index < 32) {
|
||||||
|
*out_result = 4;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_result = 16;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_mifare_get_sector_block_0_address(uint8_t sector_index, uint8_t *out_result)
|
||||||
|
{
|
||||||
|
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
|
||||||
|
RC522_CHECK(out_result == NULL);
|
||||||
|
|
||||||
|
if (sector_index < 32) {
|
||||||
|
*out_result = sector_index * 4;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_result = 128 + (sector_index - 32) * 16;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_get_sector_desc(uint8_t sector_index, rc522_mifare_sector_desc_t *out_sector_desc)
|
||||||
|
{
|
||||||
|
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
|
||||||
|
RC522_CHECK(out_sector_desc == NULL);
|
||||||
|
|
||||||
|
rc522_mifare_sector_desc_t desc;
|
||||||
|
memset(&desc, 0, sizeof(desc));
|
||||||
|
|
||||||
|
desc.index = sector_index;
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_get_number_of_blocks_in_sector(sector_index, &desc.number_of_blocks));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_block_0_address(sector_index, &desc.block_0_address));
|
||||||
|
|
||||||
|
memcpy(out_sector_desc, &desc, sizeof(rc522_mifare_sector_desc_t));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_mifare_block_at_address_is_sector_trailer(uint8_t block_address, bool *result)
|
||||||
|
{
|
||||||
|
RC522_CHECK(result == NULL);
|
||||||
|
|
||||||
|
uint8_t sector_index = rc522_mifare_get_sector_index_by_block_address(block_address);
|
||||||
|
|
||||||
|
rc522_mifare_sector_desc_t sector = { 0 };
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_desc(sector_index, §or));
|
||||||
|
|
||||||
|
*result = (block_address == (sector.block_0_address + sector.number_of_blocks - 1));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_mifare_verify_access_bits_integrity(const uint8_t trailer_bytes[RC522_MIFARE_BLOCK_SIZE])
|
||||||
|
{
|
||||||
|
RC522_CHECK(trailer_bytes == NULL);
|
||||||
|
|
||||||
|
uint8_t c1 = 0, c2 = 0, c3 = 0;
|
||||||
|
uint8_t c1_ = 0, c2_ = 0, c3_ = 0;
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[6], &c2_, &c1_));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[7], &c1, &c3_));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[8], &c3, &c2));
|
||||||
|
|
||||||
|
if ((c1 != (~c1_ & 0x0F)) || (c2 != (~c2_ & 0x0F)) || (c3 != (~c3_ & 0x0F))) {
|
||||||
|
return RC522_ERR_MIFARE_ACCESS_BITS_INTEGRITY_VIOLATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_write(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
|
||||||
|
const uint8_t buffer[RC522_MIFARE_BLOCK_SIZE])
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(buffer == NULL);
|
||||||
|
RC522_CHECK(!rc522_mifare_type_is_classic_compatible(picc->type));
|
||||||
|
|
||||||
|
bool is_trailer = false;
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_block_at_address_is_sector_trailer(block_address, &is_trailer));
|
||||||
|
|
||||||
|
if (is_trailer) {
|
||||||
|
#ifdef CONFIG_RC522_PREVENT_SECTOR_TRAILER_WRITE
|
||||||
|
ESP_LOGE(TAG, "");
|
||||||
|
ESP_LOGE(TAG,
|
||||||
|
"The block at address %d that you are trying to update is a Sector Trailer block.",
|
||||||
|
block_address);
|
||||||
|
ESP_LOGE(TAG, "");
|
||||||
|
ESP_LOGE(TAG, "Writing to Sector Trailer blocks is prevented by default in the component configuration");
|
||||||
|
ESP_LOGE(TAG, "to protect inexperienced users from accidentally overwriting keys");
|
||||||
|
ESP_LOGE(TAG, "or writing incorrect access bits, which could make the sector unusable.");
|
||||||
|
ESP_LOGE(TAG, "");
|
||||||
|
ESP_LOGE(TAG, "If you know what you are doing, please use menuconfig to disable this protection.");
|
||||||
|
ESP_LOGE(TAG, "");
|
||||||
|
|
||||||
|
return RC522_ERR_MIFARE_SECTOR_TRAILER_WRITE_NOT_ALLOWED;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_verify_access_bits_integrity(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
RC522_LOGD("MIFARE WRITE (block_address=%02" RC522_X ")", block_address);
|
||||||
|
|
||||||
|
uint8_t cmd_buffer[] = { RC522_MIFARE_WRITE_CMD, block_address };
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_send(rc522, cmd_buffer, sizeof(cmd_buffer)));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_send(rc522, buffer, RC522_MIFARE_BLOCK_SIZE));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_mifare_deauth(const rc522_handle_t rc522, const rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_stop_crypto1(rc522));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_get_desc(const rc522_picc_t *picc, rc522_mifare_desc_t *out_mifare_desc)
|
||||||
|
{
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(out_mifare_desc == NULL);
|
||||||
|
|
||||||
|
rc522_mifare_desc_t desc;
|
||||||
|
memset(&desc, 0, sizeof(desc));
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_get_number_of_sectors(picc->type, &desc.number_of_sectors));
|
||||||
|
|
||||||
|
memcpy(out_mifare_desc, &desc, sizeof(rc522_mifare_desc_t));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_mifare_nibbles_to_access_bits(
|
||||||
|
uint8_t c1, uint8_t c2, uint8_t c3, uint8_t offset, rc522_mifare_access_bits_t *access_bits)
|
||||||
|
{
|
||||||
|
RC522_CHECK(offset > 3);
|
||||||
|
RC522_CHECK(access_bits == NULL);
|
||||||
|
|
||||||
|
rc522_mifare_access_bits_t bits = { 0 };
|
||||||
|
|
||||||
|
bits.c1 = (c1 & (1 << offset)) >> offset;
|
||||||
|
bits.c2 = (c2 & (1 << offset)) >> offset;
|
||||||
|
bits.c3 = (c3 & (1 << offset)) >> offset;
|
||||||
|
|
||||||
|
memcpy(access_bits, &bits, sizeof(bits));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_mifare_parse_sector_trailer(
|
||||||
|
const uint8_t *trailer_bytes, uint8_t trailer_bytes_size, rc522_mifare_sector_trailer_info_t *out_trailer_info)
|
||||||
|
{
|
||||||
|
RC522_CHECK(trailer_bytes == NULL);
|
||||||
|
RC522_CHECK(trailer_bytes_size != RC522_MIFARE_BLOCK_SIZE);
|
||||||
|
RC522_CHECK(out_trailer_info == NULL);
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_verify_access_bits_integrity(trailer_bytes));
|
||||||
|
|
||||||
|
uint8_t c1 = 0, c2 = 0, c3 = 0;
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[7], &c1, NULL));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[8], &c3, &c2));
|
||||||
|
|
||||||
|
rc522_mifare_sector_trailer_info_t info = { 0 };
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 4; i++) {
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_nibbles_to_access_bits(c1, c2, c3, i, &info.access_bits[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(out_trailer_info, &info, sizeof(info));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if block is Value block based on access bits
|
||||||
|
*/
|
||||||
|
inline static bool rc522_mifare_block_is_value(rc522_mifare_access_bits_t access_bits)
|
||||||
|
{
|
||||||
|
uint8_t bits = (access_bits.c1 << 2) | (access_bits.c2 << 1) | (access_bits.c3 << 0);
|
||||||
|
|
||||||
|
return bits == 0b110 || bits == 0b001;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_mifare_parse_value_block(
|
||||||
|
const uint8_t *bytes, uint8_t bytes_size, rc522_mifare_sector_value_block_info_t *out_value_block_info)
|
||||||
|
{
|
||||||
|
RC522_CHECK(bytes == NULL);
|
||||||
|
RC522_CHECK(bytes_size != RC522_MIFARE_BLOCK_SIZE);
|
||||||
|
RC522_CHECK(out_value_block_info == NULL);
|
||||||
|
|
||||||
|
rc522_mifare_sector_value_block_info_t value_block_info;
|
||||||
|
memset(&value_block_info, 0, sizeof(value_block_info));
|
||||||
|
|
||||||
|
value_block_info.value = 0;
|
||||||
|
|
||||||
|
value_block_info.value |= ((int32_t)(bytes[3]) << (8 * 3));
|
||||||
|
value_block_info.value |= ((int32_t)(bytes[2]) << (8 * 2));
|
||||||
|
value_block_info.value |= ((int32_t)(bytes[1]) << (8 * 1));
|
||||||
|
value_block_info.value |= ((int32_t)(bytes[0]) << (8 * 0));
|
||||||
|
|
||||||
|
value_block_info.addr = bytes[12];
|
||||||
|
|
||||||
|
// TODO: Check for integrity violation
|
||||||
|
// Use RC522_ERR_MIFARE_VALUE_BLOCK_INTEGRITY_VIOLATION
|
||||||
|
|
||||||
|
memcpy(out_value_block_info, &value_block_info, sizeof(value_block_info));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static esp_err_t rc522_mifare_get_sector_block_group_index(
|
||||||
|
const rc522_mifare_sector_desc_t *sector, uint8_t block_offset, uint8_t *out_group)
|
||||||
|
{
|
||||||
|
RC522_CHECK(sector == NULL);
|
||||||
|
RC522_CHECK(out_group == NULL);
|
||||||
|
RC522_CHECK(block_offset >= sector->number_of_blocks);
|
||||||
|
|
||||||
|
if (sector->number_of_blocks > 4) {
|
||||||
|
*out_group = block_offset / 5;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_group = block_offset;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_read_sector_trailer_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
const rc522_mifare_sector_desc_t *sector_desc, rc522_mifare_sector_block_t *out_trailer)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(sector_desc == NULL);
|
||||||
|
RC522_CHECK(out_trailer == NULL);
|
||||||
|
|
||||||
|
rc522_mifare_sector_block_t block;
|
||||||
|
memset(&block, 0x00, sizeof(block));
|
||||||
|
|
||||||
|
block.address = (sector_desc->block_0_address + sector_desc->number_of_blocks - 1);
|
||||||
|
block.type = RC522_MIFARE_BLOCK_TRAILER;
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_read(rc522, picc, block.address, block.bytes));
|
||||||
|
block.error = rc522_mifare_parse_sector_trailer(block.bytes, sizeof(block.bytes), &block.trailer_info);
|
||||||
|
memcpy(&block.access_bits, &block.trailer_info.access_bits[3], sizeof(rc522_mifare_access_bits_t));
|
||||||
|
|
||||||
|
memcpy(out_trailer, &block, sizeof(block));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_mifare_read_sector_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_sector_block_t *trailer, uint8_t block_offset,
|
||||||
|
rc522_mifare_sector_block_t *out_block)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(sector_desc == NULL);
|
||||||
|
RC522_CHECK(trailer == NULL);
|
||||||
|
RC522_CHECK(block_offset >= sector_desc->number_of_blocks);
|
||||||
|
RC522_CHECK(out_block == NULL);
|
||||||
|
RC522_CHECK_WITH_MESSAGE(block_offset == sector_desc->number_of_blocks - 1,
|
||||||
|
"use rc522_mifare_read_sector_trailer_block() to read the sector trailer block");
|
||||||
|
RC522_CHECK(trailer->type != RC522_MIFARE_BLOCK_TRAILER);
|
||||||
|
|
||||||
|
rc522_mifare_sector_block_t block;
|
||||||
|
memset(&block, 0, sizeof(block));
|
||||||
|
|
||||||
|
block.address = (sector_desc->block_0_address + block_offset);
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_read(rc522, picc, block.address, block.bytes));
|
||||||
|
|
||||||
|
uint8_t group = 0;
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_block_group_index(sector_desc, block_offset, &group));
|
||||||
|
|
||||||
|
memcpy(&block.access_bits, &trailer->trailer_info.access_bits[group], sizeof(rc522_mifare_access_bits_t));
|
||||||
|
|
||||||
|
if (block.address == 0) {
|
||||||
|
block.type = RC522_MIFARE_BLOCK_MANUFACTURER_DATA;
|
||||||
|
}
|
||||||
|
else if (rc522_mifare_block_is_value(block.access_bits)) {
|
||||||
|
block.type = RC522_MIFARE_BLOCK_VALUE;
|
||||||
|
block.error = rc522_mifare_parse_value_block(block.bytes, sizeof(block.bytes), &block.value_info);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
block.type = RC522_MIFARE_BLOCK_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(out_block, &block, sizeof(block));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
618
components/rc522/src/picc/rc522_nxp.c
Normal file
618
components/rc522/src/picc/rc522_nxp.c
Normal file
@@ -0,0 +1,618 @@
|
|||||||
|
#include <esp_system.h>
|
||||||
|
#include <esp_check.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "rc522_picc.h"
|
||||||
|
#include "rc522_types_internal.h"
|
||||||
|
#include "rc522_pcd_internal.h"
|
||||||
|
#include "rc522_picc_internal.h"
|
||||||
|
|
||||||
|
#include "picc/rc522_nxp.h"
|
||||||
|
#include "picc/rc522_mifare.h"
|
||||||
|
|
||||||
|
RC522_LOG_DEFINE_BASE();
|
||||||
|
|
||||||
|
const uint8_t RC522_NXP_DEFAULT_PWD[RC522_NXP_PWD_SIZE] = { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
|
const uint8_t RC522_NXP_DEFAULT_PACK[RC522_NXP_PACK_SIZE] = { 0x00 };
|
||||||
|
|
||||||
|
// Helper to check if the card responded with a NACK
|
||||||
|
inline esp_err_t rc522_nxp_check_for_nak(rc522_picc_transaction_result_t *result)
|
||||||
|
{
|
||||||
|
if (result->bytes.length == 1 && result->valid_bits == 4 && result->bytes.ptr[0] != RC522_MIFARE_ACK) {
|
||||||
|
return RC522_ERR_NXP_NACK;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WRITE supported? (not classic write - see COMPAT_WRITE)
|
||||||
|
inline bool rc522_nxp_type_has_write(rc522_picc_type_t type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_C:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_NANO:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_AES:
|
||||||
|
case RC522_PICC_TYPE_NTAG2xx:
|
||||||
|
case RC522_PICC_TYPE_NTAG213:
|
||||||
|
case RC522_PICC_TYPE_NTAG215:
|
||||||
|
case RC522_PICC_TYPE_NTAG216:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FAST_READ supported?
|
||||||
|
inline bool rc522_nxp_type_has_fast_read(rc522_picc_type_t type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_AES:
|
||||||
|
case RC522_PICC_TYPE_NTAG213:
|
||||||
|
case RC522_PICC_TYPE_NTAG215:
|
||||||
|
case RC522_PICC_TYPE_NTAG216:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// READ_CNT supported?
|
||||||
|
inline bool rc522_nxp_type_has_read_cnt(rc522_picc_type_t type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_AES:
|
||||||
|
case RC522_PICC_TYPE_NTAG213:
|
||||||
|
case RC522_PICC_TYPE_NTAG215:
|
||||||
|
case RC522_PICC_TYPE_NTAG216:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PWD_AUTH supported?
|
||||||
|
inline bool rc522_nxp_type_has_pwd_auth(rc522_picc_type_t type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
|
||||||
|
case RC522_PICC_TYPE_NTAG213:
|
||||||
|
case RC522_PICC_TYPE_NTAG215:
|
||||||
|
case RC522_PICC_TYPE_NTAG216:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get originality signature size, or 0 if not supported
|
||||||
|
inline uint8_t rc522_nxp_type_sig_size(rc522_picc_type_t type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_AES:
|
||||||
|
return 48;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_NANO:
|
||||||
|
case RC522_PICC_TYPE_NTAG213:
|
||||||
|
case RC522_PICC_TYPE_NTAG215:
|
||||||
|
case RC522_PICC_TYPE_NTAG216:
|
||||||
|
return 32;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert GET_VERSION response to type
|
||||||
|
static rc522_picc_type_t rc522_nxp_type_from_version(rc522_nxp_picc_version_t *version)
|
||||||
|
{
|
||||||
|
rc522_nxp_product_type_t type = version->product_type;
|
||||||
|
rc522_nxp_major_version_t major_version = version->major_version;
|
||||||
|
uint8_t size = version->storage_size;
|
||||||
|
if (type == RC522_NXP_PRODUCT_TYPE_NTAG) {
|
||||||
|
if (major_version != RC522_NXP_MAJ_VER_NTAG21) { // Not an NTAG21x
|
||||||
|
RC522_LOGW("Unknown NTAG product version: %02" RC522_X, major_version);
|
||||||
|
}
|
||||||
|
else if (size == 0x0F) { // 0x0F => between 128 and 256 user bytes
|
||||||
|
return RC522_PICC_TYPE_NTAG213;
|
||||||
|
}
|
||||||
|
else if (size == 0x11) { // 0x11 => between 256 and 512 user bytes
|
||||||
|
return RC522_PICC_TYPE_NTAG215;
|
||||||
|
}
|
||||||
|
else if (size == 0x13) { // 0x13 => between 512 and 1024 user byets
|
||||||
|
return RC522_PICC_TYPE_NTAG216;
|
||||||
|
}
|
||||||
|
else { // Unknown size?
|
||||||
|
RC522_LOGW("Unknown NTAG size: %02" RC522_X, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == RC522_NXP_PRODUCT_TYPE_UL) {
|
||||||
|
if (major_version == RC522_NXP_MAJ_VER_UL_EV1) {
|
||||||
|
if (size == 0x0B) { // 0x0B => between 32 and 64 user bytes
|
||||||
|
return RC522_PICC_TYPE_MIFARE_UL_EV1_1;
|
||||||
|
}
|
||||||
|
else if (size == 0x0E) { // 0x0E => 128 user bytes
|
||||||
|
return RC522_PICC_TYPE_MIFARE_UL_EV1_2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (major_version == RC522_NXP_MAJ_VER_UL_NANO) {
|
||||||
|
return RC522_PICC_TYPE_MIFARE_UL_NANO;
|
||||||
|
}
|
||||||
|
else if (major_version == RC522_NXP_MAJ_VER_UL_AES) {
|
||||||
|
return RC522_PICC_TYPE_MIFARE_UL_AES;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RC522_LOGW("Unknown UL product version: %02" RC522_X, major_version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RC522_LOGE("Unknown product type: %02" RC522_X, type);
|
||||||
|
}
|
||||||
|
return RC522_PICC_TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rc522_nxp_get_user_page_count(rc522_picc_type_t type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_:
|
||||||
|
return 12;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_C:
|
||||||
|
return 36;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
|
||||||
|
return 12;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
|
||||||
|
return 32;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_NANO:
|
||||||
|
return 10;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_AES:
|
||||||
|
return 36;
|
||||||
|
case RC522_PICC_TYPE_NTAG213:
|
||||||
|
return 36;
|
||||||
|
case RC522_PICC_TYPE_NTAG215:
|
||||||
|
return 126;
|
||||||
|
case RC522_PICC_TYPE_NTAG216:
|
||||||
|
return 222;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rc522_nxp_get_user_mem_start(rc522_picc_type_t type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_C:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_NANO:
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_AES:
|
||||||
|
case RC522_PICC_TYPE_NTAG2xx:
|
||||||
|
case RC522_PICC_TYPE_NTAG213:
|
||||||
|
case RC522_PICC_TYPE_NTAG215:
|
||||||
|
case RC522_PICC_TYPE_NTAG216:
|
||||||
|
return 0x04;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rc522_nxp_get_user_mem_end(rc522_picc_type_t type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_:
|
||||||
|
return 0x0F;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_C:
|
||||||
|
return 0x27;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
|
||||||
|
return 0x0F;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
|
||||||
|
return 0x23;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_NANO:
|
||||||
|
return 0x0D;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_AES:
|
||||||
|
return 0x27;
|
||||||
|
case RC522_PICC_TYPE_NTAG213:
|
||||||
|
return 0x27;
|
||||||
|
case RC522_PICC_TYPE_NTAG215:
|
||||||
|
return 0x81;
|
||||||
|
case RC522_PICC_TYPE_NTAG216:
|
||||||
|
return 0xE1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rc522_nxp_get_page_count(rc522_picc_type_t type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_:
|
||||||
|
return 16;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_C:
|
||||||
|
return 48;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
|
||||||
|
return 20;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
|
||||||
|
return 41;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_NANO:
|
||||||
|
return 14;
|
||||||
|
case RC522_PICC_TYPE_MIFARE_UL_AES:
|
||||||
|
return 60;
|
||||||
|
case RC522_PICC_TYPE_NTAG213:
|
||||||
|
return 45;
|
||||||
|
case RC522_PICC_TYPE_NTAG215:
|
||||||
|
return 135;
|
||||||
|
case RC522_PICC_TYPE_NTAG216:
|
||||||
|
return 231;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_nxp_keyauth_supported(const rc522_handle_t rc522, const rc522_picc_t *picc)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_LOGD("AUTHENTICATE (support check)");
|
||||||
|
|
||||||
|
uint8_t buffer[11] = { 0 }; // combined send(4)/recv(11) buffer
|
||||||
|
buffer[0] = RC522_PICC_CMD_UL_AUTH;
|
||||||
|
// buffer[1] is argument; 0x00 is fine for support check
|
||||||
|
|
||||||
|
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 = 4 },
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_picc_transaction_result_t result = {
|
||||||
|
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
|
||||||
|
if (ret == ESP_OK && buffer[0] != 0xAF) {
|
||||||
|
return RC522_ERR_NXP_NACK;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_nxp_get_type(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_type_t *out_type)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(out_type == NULL);
|
||||||
|
RC522_CHECK(picc->type != RC522_PICC_TYPE_MIFARE_UL);
|
||||||
|
RC522_LOGD("Attempting to get NXP PICC type");
|
||||||
|
RC522_LOGD("Checking for GET_VERSION support...");
|
||||||
|
|
||||||
|
// In case of early exit due to errors
|
||||||
|
rc522_picc_type_t type = RC522_PICC_TYPE_MIFARE_UL;
|
||||||
|
rc522_nxp_picc_version_t version_info = { 0 };
|
||||||
|
esp_err_t ret = rc522_nxp_get_version(rc522, picc, &version_info);
|
||||||
|
|
||||||
|
if (ret == ESP_OK) { // GET_VERSION supported
|
||||||
|
type = rc522_nxp_type_from_version(&version_info);
|
||||||
|
}
|
||||||
|
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT) { // No GET_VERSION
|
||||||
|
// Failure probably resulted in a HALT, so make sure we can still connect
|
||||||
|
RC522_LOGD("No L3 GET_VERSION; reselecting");
|
||||||
|
rc522_picc_uid_t uid;
|
||||||
|
uint8_t sak;
|
||||||
|
rc522_picc_heartbeat(rc522, picc, &uid, &sak);
|
||||||
|
|
||||||
|
// Fall back to checking for authentication availability (C)
|
||||||
|
RC522_LOGD("Checking for authentication support...");
|
||||||
|
ret = rc522_nxp_keyauth_supported(rc522, picc);
|
||||||
|
if (ret == ESP_OK) {
|
||||||
|
// Chip supports authentication
|
||||||
|
// TODO: Do we need to complete authentication, or can we leave it here?
|
||||||
|
// Note datasheet also mentions a "MIFARE Hospitality" here, but there
|
||||||
|
// doesn't seem to be any more information about it
|
||||||
|
type = RC522_PICC_TYPE_MIFARE_UL_C;
|
||||||
|
}
|
||||||
|
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT || ret == RC522_ERR_MIFARE_NACK) { // Not supported
|
||||||
|
// Only Ultralight without auth is the original
|
||||||
|
type = RC522_PICC_TYPE_MIFARE_UL_;
|
||||||
|
}
|
||||||
|
else { // Some other error in AUTHENTICATE
|
||||||
|
RC522_RETURN_ON_ERROR(ret);
|
||||||
|
}
|
||||||
|
rc522_picc_heartbeat(rc522, picc, &uid, &sak);
|
||||||
|
}
|
||||||
|
else { // A different error occured in GET_VERSION
|
||||||
|
RC522_RETURN_ON_ERROR(ret);
|
||||||
|
}
|
||||||
|
RC522_LOGD("Found %s", rc522_picc_type_name(type));
|
||||||
|
*out_type = type;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_nxp_get_version(
|
||||||
|
const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_picc_version_t *out_version)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(out_version == NULL);
|
||||||
|
|
||||||
|
RC522_LOGD("GET_VERSION");
|
||||||
|
|
||||||
|
uint8_t buffer[10]; // Combined buffer; response is 8-byte data + 2-byte CRC
|
||||||
|
buffer[0] = RC522_PICC_CMD_GETV;
|
||||||
|
rc522_pcd_crc_t crc = { 0 };
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 1 }, &crc));
|
||||||
|
|
||||||
|
buffer[1] = crc.lsb;
|
||||||
|
buffer[2] = crc.msb;
|
||||||
|
|
||||||
|
rc522_picc_transaction_t transaction = {
|
||||||
|
.bytes = { .ptr = buffer, .length = 3 },
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_picc_transaction_result_t result = {
|
||||||
|
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
|
||||||
|
};
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
|
||||||
|
|
||||||
|
memcpy(out_version, buffer, sizeof(rc522_nxp_picc_version_t));
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_nxp_read(
|
||||||
|
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, uint8_t out_buffer[RC522_NXP_PAGE_SIZE * 4])
|
||||||
|
{
|
||||||
|
// Same protocol as MIFARE cards, so defer
|
||||||
|
return rc522_mifare_read(rc522, picc, address, out_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_nxp_fast_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t start_page,
|
||||||
|
uint8_t end_page, rc522_nxp_fast_read_data_t *out_buffer)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(out_buffer == NULL);
|
||||||
|
RC522_CHECK(!rc522_nxp_type_has_fast_read(picc->type));
|
||||||
|
// some sanity checks - valid range, output buffer sufficiently large
|
||||||
|
RC522_CHECK(start_page > end_page);
|
||||||
|
RC522_CHECK(out_buffer->buffer_size < (end_page - start_page + 1) * 4);
|
||||||
|
|
||||||
|
RC522_LOGD("NXP FAST_READ (start=%02" RC522_X ", end=%02" RC522_X ")", start_page, end_page);
|
||||||
|
|
||||||
|
uint8_t cmd_buffer[5] = { RC522_NXP_FAST_READ, start_page, end_page, 0, 0 };
|
||||||
|
uint8_t byte_count = (end_page - start_page + 1) * 4;
|
||||||
|
|
||||||
|
rc522_pcd_crc_t crc = { 0 };
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
|
||||||
|
&(rc522_bytes_t) {
|
||||||
|
.ptr = cmd_buffer,
|
||||||
|
.length = 3,
|
||||||
|
},
|
||||||
|
&crc));
|
||||||
|
cmd_buffer[3] = crc.lsb;
|
||||||
|
cmd_buffer[4] = crc.msb;
|
||||||
|
|
||||||
|
// I'd rather not malloc() here, but we need extra space for the CRC which
|
||||||
|
// we're not guaranteed to have in out_buffer
|
||||||
|
// TODO: Allow for separate CRC fetch and processing to avoid this?
|
||||||
|
uint8_t *recv_buffer = malloc(byte_count + 2); // Allow for CRC_A
|
||||||
|
RC522_CHECK_AND_RETURN(recv_buffer == NULL, ESP_ERR_NO_MEM);
|
||||||
|
|
||||||
|
rc522_picc_transaction_t transaction = {
|
||||||
|
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
|
||||||
|
.check_crc = true,
|
||||||
|
};
|
||||||
|
rc522_picc_transaction_result_t result = {
|
||||||
|
.bytes = { .ptr = recv_buffer, .length = byte_count + 2 },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bunch of manual handling here to make sure free() is called
|
||||||
|
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
free(recv_buffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
else if ((result.bytes.length - 2) != byte_count) {
|
||||||
|
free(recv_buffer);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(out_buffer->bytes, recv_buffer, byte_count);
|
||||||
|
out_buffer->read_size = byte_count;
|
||||||
|
free(recv_buffer);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_nxp_write(
|
||||||
|
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, const uint8_t buffer[RC522_NXP_PAGE_SIZE])
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(buffer == NULL);
|
||||||
|
RC522_CHECK(!rc522_nxp_type_has_write(picc->type));
|
||||||
|
|
||||||
|
RC522_LOGD("NXP WRITE (address=%02" RC522_X ")", address);
|
||||||
|
|
||||||
|
uint8_t cmd_buffer[RC522_NXP_PAGE_SIZE + 4] = { 0 };
|
||||||
|
cmd_buffer[0] = RC522_NXP_WRITE;
|
||||||
|
cmd_buffer[1] = address;
|
||||||
|
|
||||||
|
memcpy(&cmd_buffer[2], buffer, RC522_NXP_PAGE_SIZE);
|
||||||
|
|
||||||
|
rc522_pcd_crc_t crc = { 0 };
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
|
||||||
|
&(rc522_bytes_t) {
|
||||||
|
.ptr = cmd_buffer,
|
||||||
|
.length = sizeof(cmd_buffer) - 2,
|
||||||
|
},
|
||||||
|
&crc));
|
||||||
|
|
||||||
|
cmd_buffer[2 + RC522_NXP_PAGE_SIZE] = crc.lsb;
|
||||||
|
cmd_buffer[2 + RC522_NXP_PAGE_SIZE + 1] = crc.msb;
|
||||||
|
|
||||||
|
rc522_picc_transaction_t transaction = {
|
||||||
|
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
|
||||||
|
};
|
||||||
|
rc522_picc_transaction_result_t result = {
|
||||||
|
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
|
||||||
|
};
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
|
||||||
|
return rc522_nxp_check_for_nak(&result);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_nxp_read_cnt(
|
||||||
|
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t counter_no, uint32_t *out_count)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(out_count == NULL);
|
||||||
|
RC522_CHECK(!rc522_nxp_type_has_read_cnt(picc->type));
|
||||||
|
|
||||||
|
RC522_LOGD("NXP READ_CNT (counter=%02" RC522_X ")", counter_no);
|
||||||
|
|
||||||
|
uint8_t cmd_buffer[5] = {
|
||||||
|
// send(4)/recv(5) buffer
|
||||||
|
RC522_NXP_READ_CNT,
|
||||||
|
counter_no,
|
||||||
|
0,
|
||||||
|
0, // CRC
|
||||||
|
0 // Space for reply (3 bytes + CRC)
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_pcd_crc_t crc = { 0 };
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
|
||||||
|
&(rc522_bytes_t) {
|
||||||
|
.ptr = cmd_buffer,
|
||||||
|
.length = sizeof(cmd_buffer) - 2,
|
||||||
|
},
|
||||||
|
&crc));
|
||||||
|
cmd_buffer[2] = crc.lsb;
|
||||||
|
cmd_buffer[3] = crc.msb;
|
||||||
|
|
||||||
|
rc522_picc_transaction_t transaction = {
|
||||||
|
.bytes = { .ptr = cmd_buffer, .length = 4 },
|
||||||
|
};
|
||||||
|
rc522_picc_transaction_result_t result = {
|
||||||
|
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
|
||||||
|
};
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
|
||||||
|
|
||||||
|
// TODO: Verify this counter is correct
|
||||||
|
*out_count = (cmd_buffer[2] << 16) | (cmd_buffer[1] << 8) | cmd_buffer[0];
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_nxp_pwd_auth(const rc522_handle_t rc522, const rc522_picc_t *picc,
|
||||||
|
const uint8_t pwd[RC522_NXP_PWD_SIZE], const uint8_t pack[RC522_NXP_PACK_SIZE], rc522_picc_state_t *out_state)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(pwd == NULL);
|
||||||
|
RC522_CHECK(pack == NULL);
|
||||||
|
RC522_CHECK(!rc522_nxp_type_has_pwd_auth(picc->type));
|
||||||
|
|
||||||
|
RC522_LOGD("NXP PWD_AUTH");
|
||||||
|
|
||||||
|
uint8_t cmd_buffer[RC522_NXP_PWD_SIZE + 3] = { 0 }; // send(11)/recv(4) buffer
|
||||||
|
cmd_buffer[0] = RC522_NXP_PWD_AUTH;
|
||||||
|
memcpy(&cmd_buffer[1], pwd, RC522_NXP_PWD_SIZE);
|
||||||
|
|
||||||
|
rc522_pcd_crc_t crc = { 0 };
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
|
||||||
|
&(rc522_bytes_t) {
|
||||||
|
.ptr = cmd_buffer,
|
||||||
|
.length = sizeof(cmd_buffer) - 2,
|
||||||
|
},
|
||||||
|
&crc));
|
||||||
|
cmd_buffer[1 + RC522_NXP_PWD_SIZE] = crc.lsb;
|
||||||
|
cmd_buffer[1 + RC522_NXP_PWD_SIZE + 1] = crc.msb;
|
||||||
|
|
||||||
|
rc522_picc_transaction_t transaction = {
|
||||||
|
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
|
||||||
|
};
|
||||||
|
rc522_picc_transaction_result_t result = {
|
||||||
|
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
|
||||||
|
};
|
||||||
|
|
||||||
|
// If authentication fails (timeout or NAK), the card will HALT, so assume
|
||||||
|
// it has until we know otherwise
|
||||||
|
*out_state = RC522_PICC_STATE_HALT;
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
|
||||||
|
|
||||||
|
// Successful authentication, set state
|
||||||
|
*out_state = RC522_PICC_STATE_AUTHENTICATED;
|
||||||
|
// Validate the PACK - not critical to authenticate the card, but good
|
||||||
|
// practice to verify
|
||||||
|
RC522_CHECK_AND_RETURN(memcmp(pack, cmd_buffer, RC522_NXP_PACK_SIZE) != 0, RC522_ERR_NXP_PACK_MISMATCH);
|
||||||
|
*out_state = RC522_PICC_STATE_AUTHENTICATED;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_nxp_read_sig(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_sig_t *out_sig)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(picc == NULL);
|
||||||
|
RC522_CHECK(out_sig == NULL);
|
||||||
|
|
||||||
|
uint8_t sig_size = rc522_nxp_type_sig_size(picc->type);
|
||||||
|
RC522_CHECK(sig_size == 0);
|
||||||
|
RC522_CHECK(out_sig->buffer_size < sig_size);
|
||||||
|
|
||||||
|
RC522_LOGD("NXP READ_SIG (size=%d)", sig_size);
|
||||||
|
uint8_t cmd_buffer[4] = {
|
||||||
|
RC522_NXP_READ_SIG,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0 // CRC
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522_pcd_crc_t crc = { 0 };
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
|
||||||
|
&(rc522_bytes_t) {
|
||||||
|
.ptr = cmd_buffer,
|
||||||
|
.length = sizeof(cmd_buffer) - 2,
|
||||||
|
},
|
||||||
|
&crc));
|
||||||
|
cmd_buffer[2] = crc.lsb;
|
||||||
|
cmd_buffer[3] = crc.msb;
|
||||||
|
|
||||||
|
// TODO: Combine to single malloc() for both buffers?
|
||||||
|
// Again, I'd rather not do a large malloc() here, but we need the space for
|
||||||
|
// CRC. See TODO in FAST_READ
|
||||||
|
uint8_t *recv_buffer = malloc(sig_size + 2); // +2 for CRC
|
||||||
|
RC522_CHECK_AND_RETURN(recv_buffer == NULL, ESP_ERR_NO_MEM);
|
||||||
|
|
||||||
|
rc522_picc_transaction_t transaction = {
|
||||||
|
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
|
||||||
|
};
|
||||||
|
rc522_picc_transaction_result_t result = {
|
||||||
|
.bytes = { .ptr = recv_buffer, .length = sig_size + 2 },
|
||||||
|
};
|
||||||
|
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
free(recv_buffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = rc522_nxp_check_for_nak(&result);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
free(recv_buffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(out_sig->bytes, recv_buffer, sig_size);
|
||||||
|
out_sig->sig_size = sig_size;
|
||||||
|
free(recv_buffer);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
342
components/rc522/src/rc522.c
Normal file
342
components/rc522/src/rc522.c
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
#include <esp_system.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_check.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#include <freertos/event_groups.h>
|
||||||
|
|
||||||
|
#include "rc522_pcd_internal.h"
|
||||||
|
#include "rc522_picc_internal.h"
|
||||||
|
#include "rc522_helpers_internal.h"
|
||||||
|
#include "rc522_types_internal.h"
|
||||||
|
#include "rc522_internal.h"
|
||||||
|
|
||||||
|
RC522_LOG_DEFINE_BASE();
|
||||||
|
|
||||||
|
ESP_EVENT_DEFINE_BASE(RC522_EVENTS);
|
||||||
|
|
||||||
|
inline static bool rc522_is_able_to_start(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
return rc522->state >= RC522_STATE_CREATED && rc522->state != RC522_STATE_POLLING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_clone_config(const rc522_config_t *config, rc522_config_t **result)
|
||||||
|
{
|
||||||
|
rc522_config_t *config_clone = calloc(1, sizeof(rc522_config_t));
|
||||||
|
RC522_CHECK_AND_RETURN(config_clone == NULL, ESP_ERR_NO_MEM);
|
||||||
|
|
||||||
|
memcpy(config_clone, config, sizeof(rc522_config_t));
|
||||||
|
|
||||||
|
// defaults
|
||||||
|
if (config_clone->poll_interval_ms < RC522_POLL_INTERVAL_MS_MIN) {
|
||||||
|
config_clone->poll_interval_ms = RC522_POLL_INTERVAL_MS_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_clone->task_stack_size == 0) {
|
||||||
|
config_clone->task_stack_size = RC522_TASK_STACK_SIZE_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_clone->task_priority == 0) {
|
||||||
|
config_clone->task_priority = RC522_TASK_PRIORITY_DEFAULT;
|
||||||
|
}
|
||||||
|
// ~defaults
|
||||||
|
|
||||||
|
*result = config_clone;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_register_events(
|
||||||
|
const rc522_handle_t rc522, rc522_event_t event, esp_event_handler_t event_handler, void *event_handler_arg)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(event_handler == NULL);
|
||||||
|
|
||||||
|
return esp_event_handler_register_with(rc522->event_handle, RC522_EVENTS, event, event_handler, event_handler_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_unregister_events(const rc522_handle_t rc522, rc522_event_t event, esp_event_handler_t event_handler)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(event_handler == NULL);
|
||||||
|
|
||||||
|
return esp_event_handler_unregister_with(rc522->event_handle, RC522_EVENTS, event, event_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_start(rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
|
||||||
|
ESP_RETURN_ON_FALSE(rc522 != NULL, ESP_ERR_INVALID_ARG, TAG, "rc522 is null");
|
||||||
|
ESP_RETURN_ON_FALSE(rc522_is_able_to_start(rc522),
|
||||||
|
ESP_ERR_INVALID_STATE,
|
||||||
|
TAG,
|
||||||
|
"Unable to start (state=%d)",
|
||||||
|
rc522->state);
|
||||||
|
|
||||||
|
if (rc522->state == RC522_STATE_PAUSED) {
|
||||||
|
// Scanning has been paused. No need for reinitialization. Just resume
|
||||||
|
rc522->state = RC522_STATE_POLLING;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_reset(rc522, 150));
|
||||||
|
esp_err_t rw_ret = rc522_pcd_rw_test(rc522);
|
||||||
|
if (rw_ret != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "rw test warning (%s), proceeding with pcd_init...", esp_err_to_name(rw_ret));
|
||||||
|
}
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_pcd_init(rc522), TAG, "unable to init pcd");
|
||||||
|
|
||||||
|
rc522->state = RC522_STATE_POLLING;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_pause(rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
|
||||||
|
ESP_RETURN_ON_FALSE(rc522->state == RC522_STATE_POLLING,
|
||||||
|
ESP_ERR_INVALID_STATE,
|
||||||
|
TAG,
|
||||||
|
"Invalid state (state=%d)",
|
||||||
|
rc522->state);
|
||||||
|
|
||||||
|
rc522->state = RC522_STATE_PAUSED;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Exit and delete the task
|
||||||
|
*/
|
||||||
|
inline static void rc522_request_task_to_exit(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
rc522->exit_requested = true; // task will delete itself
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_create(const rc522_config_t *config, rc522_handle_t *out_rc522)
|
||||||
|
{
|
||||||
|
RC522_CHECK(config == NULL);
|
||||||
|
RC522_CHECK(out_rc522 == NULL);
|
||||||
|
|
||||||
|
rc522_handle_t rc522 = calloc(1, sizeof(struct rc522));
|
||||||
|
ESP_RETURN_ON_FALSE(rc522 != NULL, ESP_ERR_NO_MEM, TAG, "nomem");
|
||||||
|
|
||||||
|
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_IDLE, false);
|
||||||
|
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
ESP_GOTO_ON_ERROR(rc522_clone_config(config, &(rc522->config)), _error, TAG, "clone config failed");
|
||||||
|
|
||||||
|
esp_event_loop_args_t event_args = {
|
||||||
|
.queue_size = 1,
|
||||||
|
.task_name = NULL, // no task will be created
|
||||||
|
};
|
||||||
|
|
||||||
|
rc522->bits = xEventGroupCreate();
|
||||||
|
ESP_GOTO_ON_FALSE(rc522->bits != NULL, ESP_ERR_NO_MEM, _error, TAG, "nomem");
|
||||||
|
|
||||||
|
ESP_GOTO_ON_ERROR(esp_event_loop_create(&event_args, &rc522->event_handle),
|
||||||
|
_error,
|
||||||
|
TAG,
|
||||||
|
"Failed to create event loop");
|
||||||
|
|
||||||
|
BaseType_t task_create_result = xTaskCreate(rc522_task,
|
||||||
|
"rc522_poll",
|
||||||
|
rc522->config->task_stack_size,
|
||||||
|
rc522,
|
||||||
|
rc522->config->task_priority,
|
||||||
|
&rc522->task_handle);
|
||||||
|
|
||||||
|
ESP_GOTO_ON_FALSE(task_create_result == pdTRUE, ESP_FAIL, _error, TAG, "task create failed");
|
||||||
|
|
||||||
|
goto _success;
|
||||||
|
_error:
|
||||||
|
rc522_destroy(rc522);
|
||||||
|
rc522 = NULL;
|
||||||
|
goto _return;
|
||||||
|
_success:
|
||||||
|
rc522->state = RC522_STATE_CREATED;
|
||||||
|
*out_rc522 = rc522;
|
||||||
|
_return:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_destroy(rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
|
||||||
|
ESP_RETURN_ON_FALSE(xTaskGetCurrentTaskHandle() != rc522->task_handle,
|
||||||
|
ESP_ERR_INVALID_STATE,
|
||||||
|
TAG,
|
||||||
|
"Cannot destroy from event handler");
|
||||||
|
|
||||||
|
rc522_request_task_to_exit(rc522);
|
||||||
|
|
||||||
|
if (rc522->bits) {
|
||||||
|
xEventGroupWaitBits(rc522->bits, RC522_TASK_STOPPED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
|
||||||
|
vEventGroupDelete(rc522->bits);
|
||||||
|
rc522->bits = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc522->event_handle) {
|
||||||
|
if (esp_event_loop_delete(rc522->event_handle) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Failed to delete event loop");
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522->event_handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc522->config) {
|
||||||
|
free(rc522->config);
|
||||||
|
rc522->config = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(rc522);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_dispatch_event(const rc522_handle_t rc522, rc522_event_t event, const void *data, size_t data_size)
|
||||||
|
{
|
||||||
|
RC522_RETURN_ON_ERROR(esp_event_post_to(rc522->event_handle, RC522_EVENTS, event, data, data_size, portMAX_DELAY));
|
||||||
|
|
||||||
|
return esp_event_loop_run(rc522->event_handle, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rc522_task(void *arg)
|
||||||
|
{
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
rc522_handle_t rc522 = (rc522_handle_t)arg;
|
||||||
|
uint32_t last_poll_ms = 0;
|
||||||
|
const uint32_t task_delay_ms = 50;
|
||||||
|
const uint32_t picc_heartbeat_failure_threshold_ms = (2 * task_delay_ms);
|
||||||
|
uint32_t picc_heartbeat_failure_at_ms = 0;
|
||||||
|
bool mutex_taken = false;
|
||||||
|
const uint16_t mutex_take_timeout_ms = 4000;
|
||||||
|
|
||||||
|
xEventGroupClearBits(rc522->bits, RC522_TASK_STOPPED_BIT);
|
||||||
|
|
||||||
|
while (!rc522->exit_requested) {
|
||||||
|
if (mutex_taken && rc522->config->task_mutex != NULL) {
|
||||||
|
if (xSemaphoreGive(rc522->config->task_mutex) == pdTRUE) {
|
||||||
|
mutex_taken = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RC522_LOGW("failed to give mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc522->state != RC522_STATE_POLLING) {
|
||||||
|
// waiting for state change to polling
|
||||||
|
rc522_delay_ms(100);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_delay_ms(task_delay_ms);
|
||||||
|
|
||||||
|
if (rc522->config->task_mutex != NULL) {
|
||||||
|
if (xSemaphoreTake(rc522->config->task_mutex, pdMS_TO_TICKS(mutex_take_timeout_ms)) == pdTRUE) {
|
||||||
|
mutex_taken = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RC522_LOGW("failed to take mutex in %d ms", mutex_take_timeout_ms);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool should_poll = (rc522_millis() - last_poll_ms) > rc522->config->poll_interval_ms;
|
||||||
|
|
||||||
|
if (rc522->picc.state == RC522_PICC_STATE_IDLE || rc522->picc.state == RC522_PICC_STATE_HALT) {
|
||||||
|
rc522_picc_atqa_desc_t atqa;
|
||||||
|
|
||||||
|
if (rc522->picc.state == RC522_PICC_STATE_IDLE && ((ret = rc522_picc_reqa(rc522, &atqa)) != ESP_OK)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc522->picc.state == RC522_PICC_STATE_HALT && ((ret = rc522_picc_wupa(rc522, &atqa)) != ESP_OK)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Tag detected in field (ATQA=0x%04X)", atqa.source);
|
||||||
|
|
||||||
|
// card is present
|
||||||
|
rc522->picc.atqa = atqa;
|
||||||
|
|
||||||
|
if (rc522->picc.state == RC522_PICC_STATE_IDLE) {
|
||||||
|
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_READY, true);
|
||||||
|
}
|
||||||
|
else if (rc522->picc.state == RC522_PICC_STATE_HALT) {
|
||||||
|
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_READY_H, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_poll
|
||||||
|
&& (rc522->picc.state == RC522_PICC_STATE_READY || rc522->picc.state == RC522_PICC_STATE_READY_H)) {
|
||||||
|
rc522_picc_uid_t uid;
|
||||||
|
uint8_t sak;
|
||||||
|
|
||||||
|
ret = rc522_picc_select(rc522, &uid, &sak, false);
|
||||||
|
last_poll_ms = rc522_millis();
|
||||||
|
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
if (ret != RC522_ERR_RX_TIMEOUT && ret != RC522_ERR_INVALID_ATQA && ret != RC522_ERR_INVALID_SAK) {
|
||||||
|
RC522_LOGW("select failed (err=%04" RC522_X ")", ret);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RC522_LOGD("select failed (err=%04" RC522_X ")", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_IDLE, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&rc522->picc.uid, &uid, sizeof(rc522_picc_uid_t));
|
||||||
|
rc522->picc.sak = sak;
|
||||||
|
rc522->picc.type = rc522_picc_get_type(&rc522->picc);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Tag selected (SAK=0x%02X, length=%d)", sak, uid.length);
|
||||||
|
|
||||||
|
if (rc522->picc.state == RC522_PICC_STATE_READY) {
|
||||||
|
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_ACTIVE, true);
|
||||||
|
}
|
||||||
|
else if (rc522->picc.state == RC522_PICC_STATE_READY_H) {
|
||||||
|
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_ACTIVE_H, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
picc_heartbeat_failure_at_ms = 0;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc522->picc.state == RC522_PICC_STATE_ACTIVE || rc522->picc.state == RC522_PICC_STATE_ACTIVE_H) {
|
||||||
|
if (picc_heartbeat_failure_at_ms != 0
|
||||||
|
&& ((rc522_millis() - picc_heartbeat_failure_at_ms) > picc_heartbeat_failure_threshold_ms)) {
|
||||||
|
picc_heartbeat_failure_at_ms = 0;
|
||||||
|
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_IDLE, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ret = rc522_picc_heartbeat(rc522, &rc522->picc, NULL, NULL)) == ESP_OK) {
|
||||||
|
picc_heartbeat_failure_at_ms = 0;
|
||||||
|
}
|
||||||
|
else if (picc_heartbeat_failure_at_ms == 0) {
|
||||||
|
picc_heartbeat_failure_at_ms = rc522_millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
RC522_LOGD("heartbeat failed (err=%04" RC522_X ")", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// card is still in the field
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xEventGroupSetBits(rc522->bits, RC522_TASK_STOPPED_BIT);
|
||||||
|
ESP_LOGI(TAG, "Task exited");
|
||||||
|
vTaskDelete(NULL); // self-delete
|
||||||
|
}
|
||||||
108
components/rc522/src/rc522_driver.c
Normal file
108
components/rc522/src/rc522_driver.c
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include "rc522_types_internal.h"
|
||||||
|
#include "rc522_driver_internal.h"
|
||||||
|
|
||||||
|
RC522_LOG_DEFINE_BASE();
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_init_rst_pin(gpio_num_t rst_io_num)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rst_io_num < 0);
|
||||||
|
|
||||||
|
gpio_config_t io_conf = {
|
||||||
|
.intr_type = GPIO_INTR_DISABLE,
|
||||||
|
.mode = GPIO_MODE_OUTPUT,
|
||||||
|
.pin_bit_mask = (1ULL << rst_io_num),
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(gpio_config(&io_conf));
|
||||||
|
RC522_RETURN_ON_ERROR(gpio_set_level(rst_io_num, !RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_driver_install(const rc522_driver_handle_t driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
|
||||||
|
return driver->install(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_driver_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK_BYTES(bytes);
|
||||||
|
|
||||||
|
return driver->send(driver, address, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_driver_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
RC522_CHECK_BYTES(bytes);
|
||||||
|
|
||||||
|
return driver->receive(driver, address, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_driver_reset(const rc522_driver_handle_t driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
|
||||||
|
return driver->reset(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_driver_uninstall(const rc522_driver_handle_t driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
|
||||||
|
return driver->uninstall(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_create(const void *config, size_t config_size, rc522_driver_handle_t *driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(config == NULL);
|
||||||
|
RC522_CHECK(config_size == 0);
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
rc522_driver_handle_t _driver = calloc(1, sizeof(struct rc522_driver_handle));
|
||||||
|
ESP_RETURN_ON_FALSE(_driver != NULL, ESP_ERR_NO_MEM, TAG, "nomem");
|
||||||
|
|
||||||
|
_driver->config = calloc(1, config_size);
|
||||||
|
ESP_GOTO_ON_FALSE(_driver->config != NULL, ESP_ERR_NO_MEM, error, TAG, "nomem");
|
||||||
|
|
||||||
|
memcpy(_driver->config, config, config_size);
|
||||||
|
|
||||||
|
goto success;
|
||||||
|
error:
|
||||||
|
rc522_driver_destroy(_driver);
|
||||||
|
goto exit;
|
||||||
|
success:
|
||||||
|
*driver = _driver;
|
||||||
|
exit:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_driver_destroy(rc522_driver_handle_t driver)
|
||||||
|
{
|
||||||
|
RC522_CHECK(driver == NULL);
|
||||||
|
|
||||||
|
if (driver->config) {
|
||||||
|
free(driver->config);
|
||||||
|
driver->config = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
driver->install = NULL;
|
||||||
|
driver->send = NULL;
|
||||||
|
driver->receive = NULL;
|
||||||
|
driver->uninstall = NULL;
|
||||||
|
|
||||||
|
driver->device = NULL;
|
||||||
|
|
||||||
|
free(driver);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
65
components/rc522/src/rc522_helpers.c
Normal file
65
components/rc522/src/rc522_helpers.c
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#include <esp_system.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_check.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include "rc522_types_internal.h"
|
||||||
|
#include "rc522_helpers_internal.h"
|
||||||
|
|
||||||
|
#include <esp_timer.h>
|
||||||
|
|
||||||
|
RC522_LOG_DEFINE_BASE();
|
||||||
|
|
||||||
|
uint32_t rc522_millis()
|
||||||
|
{
|
||||||
|
return (uint32_t)(esp_timer_get_time() / 1000ULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rc522_delay_ms(uint32_t ms)
|
||||||
|
{
|
||||||
|
TickType_t ticks = pdMS_TO_TICKS(ms);
|
||||||
|
if (ticks == 0 && ms > 0) {
|
||||||
|
ticks = 1;
|
||||||
|
}
|
||||||
|
vTaskDelay(ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_buffer_to_hex_str(
|
||||||
|
const uint8_t *buffer, uint8_t buffer_length, char *str_buffer, uint8_t str_buffer_length)
|
||||||
|
{
|
||||||
|
RC522_CHECK(buffer == NULL);
|
||||||
|
RC522_CHECK(buffer_length < 1);
|
||||||
|
RC522_CHECK(str_buffer == NULL);
|
||||||
|
|
||||||
|
const char *format = "%02" RC522_X " ";
|
||||||
|
const uint8_t formatted_length = 3;
|
||||||
|
|
||||||
|
RC522_CHECK(str_buffer_length < (buffer_length * formatted_length));
|
||||||
|
|
||||||
|
uint16_t len = 0;
|
||||||
|
for (uint16_t i = 0; i < buffer_length; i++) {
|
||||||
|
len += sprintf(str_buffer + len, format, buffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
str_buffer[len - 1] = 0x00;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_nibbles(uint8_t byte, uint8_t *msb, uint8_t *lsb)
|
||||||
|
{
|
||||||
|
RC522_CHECK(msb == NULL && lsb == NULL);
|
||||||
|
|
||||||
|
if (msb) {
|
||||||
|
*msb = byte >> 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lsb) {
|
||||||
|
*lsb = byte & 0x0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
370
components/rc522/src/rc522_pcd.c
Normal file
370
components/rc522/src/rc522_pcd.c
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
#include <esp_system.h>
|
||||||
|
#include <esp_check.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "rc522_types_internal.h"
|
||||||
|
#include "rc522_helpers_internal.h"
|
||||||
|
#include "rc522_driver_internal.h"
|
||||||
|
#include "rc522_pcd_internal.h"
|
||||||
|
|
||||||
|
RC522_LOG_DEFINE_BASE();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://stackoverflow.com/a/48705557
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_pcd_calculate_crc(const rc522_handle_t rc522, const rc522_bytes_t *bytes, rc522_pcd_crc_t *result)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK_BYTES(bytes);
|
||||||
|
RC522_CHECK(result == NULL);
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_stop_active_command(rc522));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_DIV_INT_REQ_REG, RC522_PCD_CRC_IRQ_BIT));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_flush(rc522));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_write(rc522, bytes));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, RC522_PCD_CALC_CRC_CMD));
|
||||||
|
|
||||||
|
uint32_t deadline_ms = rc522_millis() + 90;
|
||||||
|
bool calculation_done = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
uint8_t irq;
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_DIV_INT_REQ_REG, &irq));
|
||||||
|
|
||||||
|
if (irq & RC522_PCD_CRC_IRQ_BIT) {
|
||||||
|
calculation_done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_delay_ms(2);
|
||||||
|
}
|
||||||
|
while (rc522_millis() < deadline_ms);
|
||||||
|
|
||||||
|
if (!calculation_done) { // Deadline reached
|
||||||
|
return ESP_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_pcd_crc_t crc = { 0 };
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_stop_active_command(rc522));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_CRC_RESULT_MSB_REG, &crc.msb));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_CRC_RESULT_LSB_REG, &crc.lsb));
|
||||||
|
|
||||||
|
if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) {
|
||||||
|
char debug_buffer[64];
|
||||||
|
rc522_buffer_to_hex_str(bytes->ptr, bytes->length, debug_buffer, sizeof(debug_buffer));
|
||||||
|
RC522_LOGD("crc(%s) = 0x%04" RC522_X, debug_buffer, crc.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
*result = crc;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_pcd_wait_for_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
bool power_down_bit = true;
|
||||||
|
uint32_t start_ms = rc522_millis();
|
||||||
|
|
||||||
|
// Wait for the PowerDown bit in CommandReg to be cleared
|
||||||
|
do {
|
||||||
|
rc522_delay_ms(25);
|
||||||
|
|
||||||
|
uint8_t cmd;
|
||||||
|
if ((ret = rc522_pcd_read(rc522, RC522_PCD_COMMAND_REG, &cmd)) != ESP_OK) {
|
||||||
|
RC522_LOGD("wait for reset error %04" RC522_X, ret);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(power_down_bit = (cmd & RC522_PCD_POWER_DOWN_BIT))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while ((rc522_millis() - start_ms) < timeout_ms);
|
||||||
|
|
||||||
|
return power_down_bit ? ESP_ERR_TIMEOUT : ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static esp_err_t rc522_pcd_hard_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_LOGD("hard reset");
|
||||||
|
|
||||||
|
esp_err_t ret = rc522_driver_reset(rc522->config->driver);
|
||||||
|
|
||||||
|
return ret == ESP_OK ? rc522_pcd_wait_for_reset(rc522, timeout_ms) : ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static esp_err_t rc522_pcd_soft_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_LOGD("soft reset");
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, RC522_PCD_SOFT_RESET_CMD));
|
||||||
|
|
||||||
|
return rc522_pcd_wait_for_reset(rc522, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
|
||||||
|
{
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
if ((ret = rc522_pcd_hard_reset(rc522, timeout_ms)) != ESP_OK) {
|
||||||
|
if (ret != RC522_ERR_RST_PIN_UNUSED) {
|
||||||
|
RC522_LOGW("hard reset failed, trying soft reset");
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = rc522_pcd_soft_reset(rc522, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static esp_err_t rc522_pcd_tx_enable(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
return rc522_pcd_set_bits(rc522, RC522_PCD_TX_CONTROL_REG, (RC522_PCD_TX2_RF_EN_BIT | RC522_PCD_TX1_RF_EN_BIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_pcd_configure_timer(const rc522_handle_t rc522, uint8_t mode, uint16_t prescaler)
|
||||||
|
{
|
||||||
|
uint8_t prescaler_hi = (prescaler >> 8) & 0x0F;
|
||||||
|
uint8_t timer_mode = (mode & 0xF0) | prescaler_hi;
|
||||||
|
uint8_t prescaler_lo = (prescaler & 0xFF);
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_MODE_REG, timer_mode));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_PRESCALER_REG, prescaler_lo));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static esp_err_t rc522_pcd_set_timer_reload_value(const rc522_handle_t rc522, uint16_t value)
|
||||||
|
{
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_RELOAD_MSB_REG, (value >> 8) & 0xFF));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_RELOAD_LSB_REG, value & 0xFF));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_set_rx_gain(const rc522_handle_t rc522, rc522_pcd_rx_gain_t gain)
|
||||||
|
{
|
||||||
|
uint8_t value = 0;
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_RF_CFG_REG, &value));
|
||||||
|
return rc522_pcd_write(rc522, RC522_PCD_RF_CFG_REG, (value & ~0x70) | (gain & 0x70));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_init(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
|
||||||
|
// Reset baud rates
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TX_MODE_REG, RC522_PCD_TX_MODE_REG_RESET_VALUE));
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_RX_MODE_REG, RC522_PCD_RX_MODE_REG_RESET_VALUE));
|
||||||
|
|
||||||
|
// Reset modulation width
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_MOD_WIDTH_REG, RC522_PCD_MOD_WIDTH_REG_RESET_VALUE));
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds
|
||||||
|
// TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs.
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_configure_timer(rc522, RC522_PCD_T_AUTO_BIT, 169));
|
||||||
|
|
||||||
|
// Reload timer with 0x3E8 = 1000, ie 25ms before timeout.
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_set_timer_reload_value(rc522, 1000));
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TX_ASK_REG, RC522_PCD_FORCE_100_ASK_BIT));
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_MODE_REG, 0x3D));
|
||||||
|
|
||||||
|
// Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset)
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_tx_enable(rc522));
|
||||||
|
|
||||||
|
// Set maximum receiver antenna gain (48dB) for compact antenna on MH-ET LIVE module
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_set_rx_gain(rc522, RC522_PCD_48_DB_RX_GAIN));
|
||||||
|
|
||||||
|
rc522_pcd_firmware_t fw;
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_pcd_firmware(rc522, &fw), TAG, "read fw version failed");
|
||||||
|
|
||||||
|
// When 0x00 or 0xFF is returned, communication probably failed
|
||||||
|
if (fw == 0x00 || fw == 0xFF) {
|
||||||
|
RC522_LOGE("Communication failure, is the MFRC522 properly connected?");
|
||||||
|
|
||||||
|
return ESP_FAIL; // TODO: use custom err
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t mode_reg = 0, tx_ctrl = 0, rf_cfg = 0;
|
||||||
|
rc522_pcd_read(rc522, RC522_PCD_MODE_REG, &mode_reg);
|
||||||
|
rc522_pcd_read(rc522, RC522_PCD_TX_CONTROL_REG, &tx_ctrl);
|
||||||
|
rc522_pcd_read(rc522, RC522_PCD_RF_CFG_REG, &rf_cfg);
|
||||||
|
ESP_LOGI(TAG, "PCD (fw=%s) initialized [ModeReg=0x%02X TxControlReg=0x%02X RFCfgReg=0x%02X]",
|
||||||
|
rc522_pcd_firmware_name(fw), mode_reg, tx_ctrl, rf_cfg);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_firmware(const rc522_handle_t rc522, rc522_pcd_firmware_t *fw)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK(fw == NULL);
|
||||||
|
|
||||||
|
uint8_t value;
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_VERSION_REG, &value));
|
||||||
|
|
||||||
|
*fw = (rc522_pcd_firmware_t)value;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *rc522_pcd_firmware_name(rc522_pcd_firmware_t firmware)
|
||||||
|
{
|
||||||
|
switch (firmware) {
|
||||||
|
case RC522_PCD_FIRMWARE_CLONE:
|
||||||
|
return "clone";
|
||||||
|
case RC522_PCD_FIRMWARE_00:
|
||||||
|
return "v0.0";
|
||||||
|
case RC522_PCD_FIRMWARE_10:
|
||||||
|
return "v1.0";
|
||||||
|
case RC522_PCD_FIRMWARE_20:
|
||||||
|
return "v2.0";
|
||||||
|
case RC522_PCD_FIRMWARE_COUNTERFEIT:
|
||||||
|
return "counterfeit_chip";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_stop_active_command(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
return rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, RC522_PCD_IDLE_CMD);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_clear_all_com_interrupts(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
return rc522_pcd_write(rc522, RC522_PCD_COM_INT_REQ_REG, (uint8_t)(~RC522_PCD_SET_1_BIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_fifo_write(const rc522_handle_t rc522, const rc522_bytes_t *bytes)
|
||||||
|
{
|
||||||
|
return rc522_pcd_write_n(rc522, RC522_PCD_FIFO_DATA_REG, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_fifo_read(const rc522_handle_t rc522, rc522_bytes_t *bytes)
|
||||||
|
{
|
||||||
|
return rc522_pcd_read_n(rc522, RC522_PCD_FIFO_DATA_REG, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_fifo_flush(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
return rc522_pcd_write(rc522, RC522_PCD_FIFO_LEVEL_REG, RC522_PCD_FLUSH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_start_data_transmission(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
return rc522_pcd_set_bits(rc522, RC522_PCD_BIT_FRAMING_REG, RC522_PCD_START_SEND_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_stop_data_transmission(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
return rc522_pcd_clear_bits(rc522, RC522_PCD_BIT_FRAMING_REG, RC522_PCD_START_SEND_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_stop_crypto1(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
return rc522_pcd_clear_bits(rc522, RC522_PCD_STATUS_2_REG, RC522_PCD_MF_CRYPTO1_ON_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_pcd_rw_test(const rc522_handle_t rc522)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
|
||||||
|
uint8_t tmp;
|
||||||
|
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_FIFO_LEVEL_REG, &tmp), TAG, "Cannot read FIFO length");
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_pcd_fifo_flush(rc522), TAG, "Cannot flush FIFO");
|
||||||
|
|
||||||
|
uint8_t buffer1[] = { 0x13, 0x33, 0x37 };
|
||||||
|
const uint8_t buffer_size = sizeof(buffer1);
|
||||||
|
uint8_t buffer2[buffer_size];
|
||||||
|
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_pcd_fifo_write(rc522, &(rc522_bytes_t) { .ptr = buffer1, .length = buffer_size }),
|
||||||
|
TAG,
|
||||||
|
"Cannot write to FIFO");
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_FIFO_LEVEL_REG, &tmp));
|
||||||
|
ESP_RETURN_ON_FALSE(tmp == buffer_size, ESP_FAIL, TAG, "FIFO length missmatch after write");
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_read(rc522, &(rc522_bytes_t) { .ptr = buffer2, .length = buffer_size }));
|
||||||
|
|
||||||
|
if (memcmp(buffer1, buffer2, buffer_size) != 0) {
|
||||||
|
RC522_LOGE("Buffers content missmatch");
|
||||||
|
RC522_LOGE("Buffer1: ");
|
||||||
|
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer1, buffer_size, ESP_LOG_ERROR);
|
||||||
|
RC522_LOGE("Buffer2: ");
|
||||||
|
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer2, buffer_size, ESP_LOG_ERROR);
|
||||||
|
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_write_n(const rc522_handle_t rc522, rc522_pcd_register_t addr, const rc522_bytes_t *bytes)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK_BYTES(bytes);
|
||||||
|
|
||||||
|
if (RC522_LOG_LEVEL >= ESP_LOG_VERBOSE) {
|
||||||
|
char debug_buffer[64];
|
||||||
|
rc522_buffer_to_hex_str(bytes->ptr, bytes->length, debug_buffer, sizeof(debug_buffer));
|
||||||
|
RC522_LOGV("pcd [0x%02" RC522_X "] <<< %s", addr, debug_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_driver_send(rc522->config->driver, addr, bytes));
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_write(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t val)
|
||||||
|
{
|
||||||
|
return rc522_pcd_write_n(rc522, addr, &(rc522_bytes_t) { .ptr = &val, .length = 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_read_n(const rc522_handle_t rc522, rc522_pcd_register_t addr, rc522_bytes_t *bytes)
|
||||||
|
{
|
||||||
|
RC522_CHECK(rc522 == NULL);
|
||||||
|
RC522_CHECK_BYTES(bytes);
|
||||||
|
|
||||||
|
esp_err_t ret = rc522_driver_receive(rc522->config->driver, addr, bytes);
|
||||||
|
|
||||||
|
if (RC522_LOG_LEVEL >= ESP_LOG_VERBOSE) {
|
||||||
|
char debug_buffer[64];
|
||||||
|
rc522_buffer_to_hex_str(bytes->ptr, bytes->length, debug_buffer, sizeof(debug_buffer));
|
||||||
|
RC522_LOGV("pcd [0x%02" RC522_X "] >>> %s", addr, debug_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_read(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t *value_ref)
|
||||||
|
{
|
||||||
|
return rc522_pcd_read_n(rc522, addr, &(rc522_bytes_t) { .ptr = value_ref, .length = 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_set_bits(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t bits)
|
||||||
|
{
|
||||||
|
uint8_t value;
|
||||||
|
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, addr, &value));
|
||||||
|
|
||||||
|
return rc522_pcd_write(rc522, addr, value | bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline esp_err_t rc522_pcd_clear_bits(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t bits)
|
||||||
|
{
|
||||||
|
uint8_t value;
|
||||||
|
ESP_RETURN_ON_ERROR(rc522_pcd_read(rc522, addr, &value), TAG, "");
|
||||||
|
|
||||||
|
return rc522_pcd_write(rc522, addr, value & (~bits));
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
@@ -8,8 +8,8 @@ idf_component_register(SRCS
|
|||||||
"scryfall_client.c"
|
"scryfall_client.c"
|
||||||
"web_server.c"
|
"web_server.c"
|
||||||
"encoder.c"
|
"encoder.c"
|
||||||
"led_manager.c"
|
|
||||||
"buzzer.c"
|
"buzzer.c"
|
||||||
"app_state.c"
|
"app_state.c"
|
||||||
|
"ota_manager.c"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
)
|
)
|
||||||
@@ -12,4 +12,28 @@ menu "MTG RFID Companion"
|
|||||||
bool "Prefetch card data for all mapped UIDs at boot"
|
bool "Prefetch card data for all mapped UIDs at boot"
|
||||||
default y
|
default y
|
||||||
|
|
||||||
|
config MTG_GITEA_URL
|
||||||
|
string "Gitea instance base URL (e.g. https://gitea.com)"
|
||||||
|
default "https://gitea.com"
|
||||||
|
help
|
||||||
|
Base HTTPS URL of the Gitea instance hosting releases.
|
||||||
|
|
||||||
|
config MTG_GITEA_REPO_OWNER
|
||||||
|
string "Gitea repository owner / org"
|
||||||
|
default "user"
|
||||||
|
help
|
||||||
|
Repository owner or organization name.
|
||||||
|
|
||||||
|
config MTG_GITEA_REPO_NAME
|
||||||
|
string "Gitea repository name"
|
||||||
|
default "espMTG"
|
||||||
|
help
|
||||||
|
Repository name.
|
||||||
|
|
||||||
|
config MTG_OTA_AUTO_CHECK
|
||||||
|
bool "Automatically check for updates on startup"
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
When connected to Wi-Fi, query Gitea for new releases shortly after boot.
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
@@ -26,6 +26,7 @@ typedef enum {
|
|||||||
APP_STATE_STORAGE_MANAGER,
|
APP_STATE_STORAGE_MANAGER,
|
||||||
APP_STATE_DEVICE_INFO,
|
APP_STATE_DEVICE_INFO,
|
||||||
APP_STATE_REGISTRATION,
|
APP_STATE_REGISTRATION,
|
||||||
|
APP_STATE_OTA_UPDATE,
|
||||||
} app_state_t;
|
} app_state_t;
|
||||||
|
|
||||||
/* Life counter state */
|
/* Life counter state */
|
||||||
|
|||||||
@@ -46,8 +46,5 @@
|
|||||||
#define HW_ENC_DT_GPIO (GPIO_NUM_33)
|
#define HW_ENC_DT_GPIO (GPIO_NUM_33)
|
||||||
#define HW_ENC_SW_GPIO (GPIO_NUM_25)
|
#define HW_ENC_SW_GPIO (GPIO_NUM_25)
|
||||||
|
|
||||||
/* WS2812B Neopixel */
|
|
||||||
#define HW_NEOPIXEL_GPIO (GPIO_NUM_27)
|
|
||||||
|
|
||||||
/* Piezo buzzer */
|
/* Piezo buzzer */
|
||||||
#define HW_BUZZER_GPIO (GPIO_NUM_14)
|
#define HW_BUZZER_GPIO (GPIO_NUM_14)
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* MTG RFID Companion - WS2812B Neopixel LED manager (RMT)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "driver/rmt_tx.h"
|
|
||||||
#include "driver/rmt_encoder.h"
|
|
||||||
#include "hw_pins.h"
|
|
||||||
#include "led_manager.h"
|
|
||||||
|
|
||||||
static const char *TAG = "led";
|
|
||||||
|
|
||||||
static rmt_channel_handle_t s_tx_chan = NULL;
|
|
||||||
static rmt_encoder_handle_t s_enc = NULL;
|
|
||||||
|
|
||||||
esp_err_t led_manager_init(void)
|
|
||||||
{
|
|
||||||
if (s_tx_chan != NULL) {
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
rmt_tx_channel_config_t chan_cfg = {
|
|
||||||
.gpio_num = HW_NEOPIXEL_GPIO,
|
|
||||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
|
||||||
.resolution_hz = 10 * 1000 * 1000, /* 100 ns per tick */
|
|
||||||
.mem_block_symbols = 64,
|
|
||||||
.trans_queue_depth = 4,
|
|
||||||
};
|
|
||||||
esp_err_t ret = rmt_new_tx_channel(&chan_cfg, &s_tx_chan);
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "rmt_new_tx_channel failed: %s", esp_err_to_name(ret));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
rmt_bytes_encoder_config_t enc_cfg = {
|
|
||||||
.bit0 = {
|
|
||||||
.duration0 = 4, /* T0H ~ 400 ns */
|
|
||||||
.level0 = 1,
|
|
||||||
.duration1 = 8, /* T0L ~ 800 ns */
|
|
||||||
.level1 = 0,
|
|
||||||
},
|
|
||||||
.bit1 = {
|
|
||||||
.duration0 = 9, /* T1H ~ 900 ns */
|
|
||||||
.level0 = 1,
|
|
||||||
.duration1 = 4, /* T1L ~ 400 ns */
|
|
||||||
.level1 = 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
ret = rmt_new_bytes_encoder(&enc_cfg, &s_enc);
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "rmt_new_bytes_encoder failed: %s", esp_err_to_name(ret));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = rmt_enable(s_tx_chan);
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "rmt_enable failed: %s", esp_err_to_name(ret));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "WS2812B LED ready on GPIO %d", HW_NEOPIXEL_GPIO);
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t led_manager_set_color(uint8_t r, uint8_t g, uint8_t b)
|
|
||||||
{
|
|
||||||
if (s_tx_chan == NULL) {
|
|
||||||
esp_err_t ret = led_manager_init();
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* WS2812B expects GRB byte order */
|
|
||||||
uint8_t txbuf[3] = { g, r, b };
|
|
||||||
|
|
||||||
rmt_transmit_config_t tx_cfg = {
|
|
||||||
.loop_count = 0, /* one-shot; LED latches and holds the level */
|
|
||||||
};
|
|
||||||
esp_err_t ret = rmt_transmit(s_tx_chan, s_enc, txbuf, sizeof(txbuf), &tx_cfg);
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return rmt_tx_wait_all_done(s_tx_chan, pdMS_TO_TICKS(50));
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t led_manager_off(void)
|
|
||||||
{
|
|
||||||
return led_manager_set_color(0, 0, 0);
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* MTG RFID Companion - WS2812B Neopixel LED manager (RMT)
|
|
||||||
*
|
|
||||||
* Phase 7 Step 7.3: Single WS2812B driver on GPIO 27. LED holds the last
|
|
||||||
* transmitted color until it is overwritten.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esp_err.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Initialize RMT TX channel for the Neopixel.
|
|
||||||
*/
|
|
||||||
esp_err_t led_manager_init(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the LED color immediately (GRB order internally).
|
|
||||||
*/
|
|
||||||
esp_err_t led_manager_set_color(uint8_t r, uint8_t g, uint8_t b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Turn the LED off.
|
|
||||||
*/
|
|
||||||
esp_err_t led_manager_off(void);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "wifi_manager.h"
|
#include "wifi_manager.h"
|
||||||
#include "scryfall_client.h"
|
#include "scryfall_client.h"
|
||||||
#include "web_server.h"
|
#include "web_server.h"
|
||||||
|
#include "ota_manager.h"
|
||||||
|
|
||||||
static const char *TAG = "MAIN";
|
static const char *TAG = "MAIN";
|
||||||
|
|
||||||
@@ -16,6 +17,9 @@ void app_main(void)
|
|||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "MTG RFID Companion booting...");
|
ESP_LOGI(TAG, "MTG RFID Companion booting...");
|
||||||
|
|
||||||
|
/* Validate OTA partition & cancel rollback if just updated */
|
||||||
|
ota_manager_init();
|
||||||
|
|
||||||
ESP_ERROR_CHECK(storage_manager_init());
|
ESP_ERROR_CHECK(storage_manager_init());
|
||||||
|
|
||||||
/* RFID comes BEFORE the display: the reader lives on its own SPI3 bus
|
/* RFID comes BEFORE the display: the reader lives on its own SPI3 bus
|
||||||
|
|||||||
444
main/ota_manager.c
Normal file
444
main/ota_manager.c
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
/*
|
||||||
|
* MTG RFID Companion - OTA Firmware Update Manager
|
||||||
|
*
|
||||||
|
* Checks Gitea release API, downloads firmware updates, and flashes them
|
||||||
|
* to the passive OTA slot with rollback protection.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp_https_ota.h"
|
||||||
|
#include "esp_http_client.h"
|
||||||
|
#include "esp_crt_bundle.h"
|
||||||
|
#include "esp_app_desc.h"
|
||||||
|
#include "cJSON.h"
|
||||||
|
#include "ota_manager.h"
|
||||||
|
|
||||||
|
static const char *TAG = "ota";
|
||||||
|
|
||||||
|
#ifndef CONFIG_MTG_GITEA_URL
|
||||||
|
#define CONFIG_MTG_GITEA_URL "https://gitea.com"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CONFIG_MTG_GITEA_REPO_OWNER
|
||||||
|
#define CONFIG_MTG_GITEA_REPO_OWNER "user"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CONFIG_MTG_GITEA_REPO_NAME
|
||||||
|
#define CONFIG_MTG_GITEA_REPO_NAME "espMTG"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define GITEA_API_BUF_SIZE (8192)
|
||||||
|
|
||||||
|
static SemaphoreHandle_t s_ota_mux = NULL;
|
||||||
|
static ota_status_t s_status = OTA_STATUS_IDLE;
|
||||||
|
static char s_message[128] = "Idle";
|
||||||
|
static ota_release_info_t s_release = {0};
|
||||||
|
static size_t s_bytes_read = 0;
|
||||||
|
static size_t s_total_bytes = 0;
|
||||||
|
static volatile bool s_cancel_requested = false;
|
||||||
|
static TaskHandle_t s_ota_task_handle = NULL;
|
||||||
|
|
||||||
|
static void set_state(ota_status_t st, const char *msg)
|
||||||
|
{
|
||||||
|
if (s_ota_mux) xSemaphoreTake(s_ota_mux, portMAX_DELAY);
|
||||||
|
s_status = st;
|
||||||
|
if (msg) {
|
||||||
|
strncpy(s_message, msg, sizeof(s_message) - 1);
|
||||||
|
s_message[sizeof(s_message) - 1] = '\0';
|
||||||
|
}
|
||||||
|
if (s_ota_mux) xSemaphoreGive(s_ota_mux);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t ota_manager_init(void)
|
||||||
|
{
|
||||||
|
if (s_ota_mux == NULL) {
|
||||||
|
s_ota_mux = xSemaphoreCreateMutex();
|
||||||
|
}
|
||||||
|
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
esp_ota_img_states_t ota_state;
|
||||||
|
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
|
||||||
|
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
|
||||||
|
ESP_LOGI(TAG, "First boot of new firmware on '%s'! Validating...", running->label);
|
||||||
|
esp_err_t err = esp_ota_mark_app_valid_cancel_rollback();
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "Firmware validated and rollback canceled successfully");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Failed to validate firmware: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const esp_app_desc_t *app_desc = esp_app_get_description();
|
||||||
|
ESP_LOGI(TAG, "Running firmware '%s' version '%s' compiled on %s %s on partition '%s'",
|
||||||
|
app_desc->project_name, app_desc->version, app_desc->date, app_desc->time,
|
||||||
|
running ? running->label : "unknown");
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *ota_manager_get_current_version(void)
|
||||||
|
{
|
||||||
|
return esp_app_get_description()->version;
|
||||||
|
}
|
||||||
|
|
||||||
|
ota_status_t ota_manager_get_status(void)
|
||||||
|
{
|
||||||
|
ota_status_t st;
|
||||||
|
if (s_ota_mux) xSemaphoreTake(s_ota_mux, portMAX_DELAY);
|
||||||
|
st = s_status;
|
||||||
|
if (s_ota_mux) xSemaphoreGive(s_ota_mux);
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *ota_manager_get_message(void)
|
||||||
|
{
|
||||||
|
return s_message;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ota_manager_get_progress(void)
|
||||||
|
{
|
||||||
|
if (s_total_bytes == 0) return 0;
|
||||||
|
int p = (int)((s_bytes_read * 100) / s_total_bytes);
|
||||||
|
if (p > 100) p = 100;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ota_manager_get_bytes(size_t *read_bytes, size_t *total_bytes)
|
||||||
|
{
|
||||||
|
if (s_ota_mux) xSemaphoreTake(s_ota_mux, portMAX_DELAY);
|
||||||
|
if (read_bytes) *read_bytes = s_bytes_read;
|
||||||
|
if (total_bytes) *total_bytes = s_total_bytes;
|
||||||
|
if (s_ota_mux) xSemaphoreGive(s_ota_mux);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ota_release_info_t *ota_manager_get_release_info(void)
|
||||||
|
{
|
||||||
|
return &s_release;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ota_manager_cancel(void)
|
||||||
|
{
|
||||||
|
s_cancel_requested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ota_manager_reset(void)
|
||||||
|
{
|
||||||
|
if (s_status != OTA_STATUS_CHECKING && s_status != OTA_STATUS_DOWNLOADING) {
|
||||||
|
set_state(OTA_STATUS_IDLE, "Idle");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Semver parsing: compares "v1.2.3" or "1.2.3" */
|
||||||
|
static bool parse_semver(const char *ver, int *maj, int *min, int *patch)
|
||||||
|
{
|
||||||
|
if (!ver) return false;
|
||||||
|
while (*ver && !isdigit((unsigned char)*ver)) {
|
||||||
|
ver++;
|
||||||
|
}
|
||||||
|
*maj = 0; *min = 0; *patch = 0;
|
||||||
|
int n = sscanf(ver, "%d.%d.%d", maj, min, patch);
|
||||||
|
return (n >= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_is_newer(const char *remote, const char *local)
|
||||||
|
{
|
||||||
|
int r_maj = 0, r_min = 0, r_patch = 0;
|
||||||
|
int l_maj = 0, l_min = 0, l_patch = 0;
|
||||||
|
|
||||||
|
bool r_ok = parse_semver(remote, &r_maj, &r_min, &r_patch);
|
||||||
|
bool l_ok = parse_semver(local, &l_maj, &l_min, &l_patch);
|
||||||
|
|
||||||
|
if (r_ok && l_ok) {
|
||||||
|
if (r_maj > l_maj) return true;
|
||||||
|
if (r_maj < l_maj) return false;
|
||||||
|
if (r_min > l_min) return true;
|
||||||
|
if (r_min < l_min) return false;
|
||||||
|
return (r_patch > l_patch);
|
||||||
|
}
|
||||||
|
/* Fallback to string comparison */
|
||||||
|
return (strcmp(remote, local) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *buf;
|
||||||
|
int len;
|
||||||
|
int max;
|
||||||
|
} http_rx_buf_t;
|
||||||
|
|
||||||
|
static esp_err_t http_rx_event_handler(esp_http_client_event_t *evt)
|
||||||
|
{
|
||||||
|
http_rx_buf_t *rx = (http_rx_buf_t *)evt->user_data;
|
||||||
|
if (evt->event_id == HTTP_EVENT_ON_DATA && rx && rx->buf) {
|
||||||
|
if (rx->len + evt->data_len < rx->max - 1) {
|
||||||
|
memcpy(rx->buf + rx->len, evt->data, evt->data_len);
|
||||||
|
rx->len += evt->data_len;
|
||||||
|
rx->buf[rx->len] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ota_check_task(void *arg)
|
||||||
|
{
|
||||||
|
set_state(OTA_STATUS_CHECKING, "Connecting to Gitea...");
|
||||||
|
|
||||||
|
char api_url[256];
|
||||||
|
snprintf(api_url, sizeof(api_url), "%s/api/v1/repos/%s/%s/releases/latest",
|
||||||
|
CONFIG_MTG_GITEA_URL, CONFIG_MTG_GITEA_REPO_OWNER, CONFIG_MTG_GITEA_REPO_NAME);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Querying Gitea release API: %s", api_url);
|
||||||
|
|
||||||
|
http_rx_buf_t rx = {
|
||||||
|
.buf = malloc(GITEA_API_BUF_SIZE),
|
||||||
|
.len = 0,
|
||||||
|
.max = GITEA_API_BUF_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!rx.buf) {
|
||||||
|
set_state(OTA_STATUS_FAILED, "Out of memory");
|
||||||
|
s_ota_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rx.buf[0] = '\0';
|
||||||
|
|
||||||
|
esp_http_client_config_t cfg = {
|
||||||
|
.url = api_url,
|
||||||
|
.transport_type = HTTP_TRANSPORT_OVER_SSL,
|
||||||
|
.timeout_ms = 10000,
|
||||||
|
.event_handler = http_rx_event_handler,
|
||||||
|
.user_data = &rx,
|
||||||
|
.buffer_size = 2048,
|
||||||
|
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_http_client_handle_t client = esp_http_client_init(&cfg);
|
||||||
|
if (!client) {
|
||||||
|
free(rx.buf);
|
||||||
|
set_state(OTA_STATUS_FAILED, "HTTP init failed");
|
||||||
|
s_ota_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_http_client_set_method(client, HTTP_METHOD_GET);
|
||||||
|
esp_http_client_set_header(client, "User-Agent", "MTGCompanion/1.0");
|
||||||
|
esp_http_client_set_header(client, "Accept", "application/json");
|
||||||
|
|
||||||
|
esp_err_t ret = esp_http_client_perform(client);
|
||||||
|
int status_code = esp_http_client_get_status_code(client);
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
|
||||||
|
if (ret != ESP_OK || status_code != 200) {
|
||||||
|
char err_msg[64];
|
||||||
|
if (status_code == 404) {
|
||||||
|
snprintf(err_msg, sizeof(err_msg), "No releases found (404)");
|
||||||
|
} else if (ret != ESP_OK) {
|
||||||
|
snprintf(err_msg, sizeof(err_msg), "Network error: %s", esp_err_to_name(ret));
|
||||||
|
} else {
|
||||||
|
snprintf(err_msg, sizeof(err_msg), "HTTP error %d", status_code);
|
||||||
|
}
|
||||||
|
ESP_LOGW(TAG, "Gitea check failed: %s (code %d)", esp_err_to_name(ret), status_code);
|
||||||
|
free(rx.buf);
|
||||||
|
set_state(OTA_STATUS_FAILED, err_msg);
|
||||||
|
s_ota_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse JSON */
|
||||||
|
cJSON *root = cJSON_Parse(rx.buf);
|
||||||
|
free(rx.buf);
|
||||||
|
|
||||||
|
if (!root) {
|
||||||
|
set_state(OTA_STATUS_FAILED, "Invalid JSON from server");
|
||||||
|
s_ota_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON *tag_item = cJSON_GetObjectItem(root, "tag_name");
|
||||||
|
cJSON *title_item = cJSON_GetObjectItem(root, "name");
|
||||||
|
const char *tag_str = (tag_item && tag_item->valuestring) ? tag_item->valuestring : "unknown";
|
||||||
|
const char *title_str = (title_item && title_item->valuestring) ? title_item->valuestring : tag_str;
|
||||||
|
|
||||||
|
if (s_ota_mux) xSemaphoreTake(s_ota_mux, portMAX_DELAY);
|
||||||
|
strncpy(s_release.tag_name, tag_str, sizeof(s_release.tag_name) - 1);
|
||||||
|
strncpy(s_release.release_title, title_str, sizeof(s_release.release_title) - 1);
|
||||||
|
s_release.download_url[0] = '\0';
|
||||||
|
s_release.binary_size = 0;
|
||||||
|
|
||||||
|
/* Search assets for mtg-rfid-companion.bin or *.bin */
|
||||||
|
cJSON *assets = cJSON_GetObjectItem(root, "assets");
|
||||||
|
if (assets && cJSON_IsArray(assets)) {
|
||||||
|
int count = cJSON_GetArraySize(assets);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
cJSON *asset = cJSON_GetArrayItem(assets, i);
|
||||||
|
cJSON *aname = cJSON_GetObjectItem(asset, "name");
|
||||||
|
cJSON *aurl = cJSON_GetObjectItem(asset, "browser_download_url");
|
||||||
|
cJSON *asize = cJSON_GetObjectItem(asset, "size");
|
||||||
|
|
||||||
|
if (aname && aname->valuestring && aurl && aurl->valuestring) {
|
||||||
|
if (strstr(aname->valuestring, ".bin") != NULL) {
|
||||||
|
const char *url_str = aurl->valuestring;
|
||||||
|
if (url_str[0] == '/') {
|
||||||
|
snprintf(s_release.download_url, sizeof(s_release.download_url),
|
||||||
|
"%s%s", CONFIG_MTG_GITEA_URL, url_str);
|
||||||
|
} else {
|
||||||
|
strncpy(s_release.download_url, url_str, sizeof(s_release.download_url) - 1);
|
||||||
|
}
|
||||||
|
if (asize && asize->valuedouble > 0) {
|
||||||
|
s_release.binary_size = (size_t)asize->valuedouble;
|
||||||
|
}
|
||||||
|
if (strstr(aname->valuestring, "mtg-rfid-companion") != NULL) {
|
||||||
|
break; /* Exact match, stop looking */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback URL if not in assets list */
|
||||||
|
if (s_release.download_url[0] == '\0') {
|
||||||
|
snprintf(s_release.download_url, sizeof(s_release.download_url),
|
||||||
|
"%s/%s/%s/releases/download/%s/mtg-rfid-companion.bin",
|
||||||
|
CONFIG_MTG_GITEA_URL, CONFIG_MTG_GITEA_REPO_OWNER, CONFIG_MTG_GITEA_REPO_NAME,
|
||||||
|
s_release.tag_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *current_ver = ota_manager_get_current_version();
|
||||||
|
s_release.is_newer = check_is_newer(s_release.tag_name, current_ver);
|
||||||
|
if (s_ota_mux) xSemaphoreGive(s_ota_mux);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Discovered release '%s' (%s), download: %s, is_newer: %d (current: %s)",
|
||||||
|
s_release.tag_name, s_release.release_title, s_release.download_url,
|
||||||
|
s_release.is_newer, current_ver);
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
|
||||||
|
if (s_release.is_newer) {
|
||||||
|
char msg[128];
|
||||||
|
snprintf(msg, sizeof(msg), "New firmware %s available!", s_release.tag_name);
|
||||||
|
set_state(OTA_STATUS_UPDATE_AVAILABLE, msg);
|
||||||
|
} else {
|
||||||
|
char msg[128];
|
||||||
|
snprintf(msg, sizeof(msg), "Firmware %s is up to date", current_ver);
|
||||||
|
set_state(OTA_STATUS_UP_TO_DATE, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_ota_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ota_download_task(void *arg)
|
||||||
|
{
|
||||||
|
s_cancel_requested = false;
|
||||||
|
s_bytes_read = 0;
|
||||||
|
s_total_bytes = s_release.binary_size;
|
||||||
|
|
||||||
|
set_state(OTA_STATUS_DOWNLOADING, "Starting download...");
|
||||||
|
ESP_LOGI(TAG, "Starting HTTPS OTA from: %s", s_release.download_url);
|
||||||
|
|
||||||
|
esp_http_client_config_t http_cfg = {
|
||||||
|
.url = s_release.download_url,
|
||||||
|
.transport_type = HTTP_TRANSPORT_OVER_SSL,
|
||||||
|
.timeout_ms = 30000,
|
||||||
|
.keep_alive_enable = true,
|
||||||
|
.buffer_size = 2048,
|
||||||
|
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_https_ota_config_t ota_cfg = {
|
||||||
|
.http_config = &http_cfg,
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_https_ota_handle_t ota_handle = NULL;
|
||||||
|
esp_err_t err = esp_https_ota_begin(&ota_cfg, &ota_handle);
|
||||||
|
if (err != ESP_OK || ota_handle == NULL) {
|
||||||
|
ESP_LOGE(TAG, "esp_https_ota_begin failed: %s", esp_err_to_name(err));
|
||||||
|
set_state(OTA_STATUS_FAILED, "Failed to connect to image");
|
||||||
|
s_ota_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if (s_cancel_requested) {
|
||||||
|
ESP_LOGW(TAG, "OTA canceled by user");
|
||||||
|
esp_https_ota_abort(ota_handle);
|
||||||
|
set_state(OTA_STATUS_IDLE, "Update canceled");
|
||||||
|
s_ota_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = esp_https_ota_perform(ota_handle);
|
||||||
|
if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int read = esp_https_ota_get_image_len_read(ota_handle);
|
||||||
|
int total = esp_https_ota_get_image_size(ota_handle);
|
||||||
|
if (read > 0) s_bytes_read = (size_t)read;
|
||||||
|
if (total > 0) s_total_bytes = (size_t)total;
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
if (esp_https_ota_is_complete_data_received(ota_handle)) {
|
||||||
|
esp_err_t finish_err = esp_https_ota_finish(ota_handle);
|
||||||
|
if (finish_err == ESP_OK) {
|
||||||
|
ESP_LOGI(TAG, "OTA upgrade successful! Rebooting in 2 seconds...");
|
||||||
|
set_state(OTA_STATUS_SUCCESS_REBOOTING, "Update complete! Rebooting...");
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||||
|
esp_restart();
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "esp_https_ota_finish failed: %s", esp_err_to_name(finish_err));
|
||||||
|
set_state(OTA_STATUS_FAILED, "Validation failed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
esp_https_ota_abort(ota_handle);
|
||||||
|
set_state(OTA_STATUS_FAILED, "Incomplete download");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "esp_https_ota_perform failed: %s", esp_err_to_name(err));
|
||||||
|
esp_https_ota_abort(ota_handle);
|
||||||
|
set_state(OTA_STATUS_FAILED, "Flash write failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
s_ota_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t ota_manager_check_update_async(void)
|
||||||
|
{
|
||||||
|
if (s_status == OTA_STATUS_CHECKING || s_status == OTA_STATUS_DOWNLOADING) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
BaseType_t ret = xTaskCreatePinnedToCore(
|
||||||
|
ota_check_task, "ota_check", 8192, NULL, 5, &s_ota_task_handle, 0);
|
||||||
|
return (ret == pdPASS) ? ESP_OK : ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t ota_manager_start_update_async(void)
|
||||||
|
{
|
||||||
|
if (s_status == OTA_STATUS_DOWNLOADING) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
if (s_release.download_url[0] == '\0') {
|
||||||
|
return ESP_ERR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
BaseType_t ret = xTaskCreatePinnedToCore(
|
||||||
|
ota_download_task, "ota_update", 8192, NULL, 5, &s_ota_task_handle, 0);
|
||||||
|
return (ret == pdPASS) ? ESP_OK : ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
95
main/ota_manager.h
Normal file
95
main/ota_manager.h
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* MTG RFID Companion - OTA Firmware Update Manager
|
||||||
|
*
|
||||||
|
* Checks Gitea release API, downloads firmware updates, and flashes them
|
||||||
|
* to the passive OTA slot with rollback protection.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OTA_STATUS_IDLE,
|
||||||
|
OTA_STATUS_CHECKING,
|
||||||
|
OTA_STATUS_UPDATE_AVAILABLE,
|
||||||
|
OTA_STATUS_UP_TO_DATE,
|
||||||
|
OTA_STATUS_DOWNLOADING,
|
||||||
|
OTA_STATUS_SUCCESS_REBOOTING,
|
||||||
|
OTA_STATUS_FAILED
|
||||||
|
} ota_status_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char tag_name[32]; /* e.g. "v1.2.0" */
|
||||||
|
char release_title[64]; /* e.g. "Release 1.2.0" */
|
||||||
|
char download_url[256]; /* URL to mtg-rfid-companion.bin */
|
||||||
|
size_t binary_size; /* in bytes, or 0 if unknown */
|
||||||
|
bool is_newer;
|
||||||
|
} ota_release_info_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize OTA manager, validates running partition, and cancels rollback.
|
||||||
|
*/
|
||||||
|
esp_err_t ota_manager_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Current firmware version string (e.g. "1.0.0").
|
||||||
|
*/
|
||||||
|
const char *ota_manager_get_current_version(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current OTA state.
|
||||||
|
*/
|
||||||
|
ota_status_t ota_manager_get_status(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get human-readable status message or error details.
|
||||||
|
*/
|
||||||
|
const char *ota_manager_get_message(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get percentage of download progress (0-100).
|
||||||
|
*/
|
||||||
|
int ota_manager_get_progress(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get bytes downloaded so far and total bytes.
|
||||||
|
*/
|
||||||
|
void ota_manager_get_bytes(size_t *read_bytes, size_t *total_bytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the latest discovered release metadata.
|
||||||
|
*/
|
||||||
|
const ota_release_info_t *ota_manager_get_release_info(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Kick off an asynchronous release check against the configured Gitea instance.
|
||||||
|
* @return ESP_OK if started, ESP_ERR_INVALID_STATE if already in progress.
|
||||||
|
*/
|
||||||
|
esp_err_t ota_manager_check_update_async(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Kick off asynchronous firmware download and flash.
|
||||||
|
* @return ESP_OK if started.
|
||||||
|
*/
|
||||||
|
esp_err_t ota_manager_start_update_async(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Abort any running OTA operation and reset status to IDLE.
|
||||||
|
*/
|
||||||
|
void ota_manager_cancel(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear state back to IDLE.
|
||||||
|
*/
|
||||||
|
void ota_manager_reset(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
415
main/ui_task.c
415
main/ui_task.c
@@ -18,9 +18,9 @@
|
|||||||
#include "wifi_manager.h"
|
#include "wifi_manager.h"
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
#include "encoder.h"
|
#include "encoder.h"
|
||||||
#include "led_manager.h"
|
|
||||||
#include "buzzer.h"
|
#include "buzzer.h"
|
||||||
#include "jpeg_decoder.h"
|
#include "jpeg_decoder.h"
|
||||||
|
#include "ota_manager.h"
|
||||||
#include "ui_task.h"
|
#include "ui_task.h"
|
||||||
|
|
||||||
static const char *TAG = "ui";
|
static const char *TAG = "ui";
|
||||||
@@ -28,7 +28,6 @@ static const char *TAG = "ui";
|
|||||||
#define UI_TASK_STACK (8 * 1024)
|
#define UI_TASK_STACK (8 * 1024)
|
||||||
#define UI_TASK_PRIO (5)
|
#define UI_TASK_PRIO (5)
|
||||||
#define UI_CORE (1)
|
#define UI_CORE (1)
|
||||||
#define LED_HOLD_MS (2500)
|
|
||||||
#define COLOR_AMBER (0xFFB300)
|
#define COLOR_AMBER (0xFFB300)
|
||||||
#define COLOR_GOLD (0xFFCC00)
|
#define COLOR_GOLD (0xFFCC00)
|
||||||
|
|
||||||
@@ -36,13 +35,12 @@ static TaskHandle_t s_ui_task = NULL;
|
|||||||
static char s_uid[RFID_UID_STR_MAX_LEN] = {0};
|
static char s_uid[RFID_UID_STR_MAX_LEN] = {0};
|
||||||
static char s_card_name[128] = {0};
|
static char s_card_name[128] = {0};
|
||||||
static bool s_waiting_fetch = false;
|
static bool s_waiting_fetch = false;
|
||||||
static uint32_t s_led_off_tick = 0;
|
|
||||||
|
|
||||||
static lv_img_dsc_t s_art_img_dsc;
|
static lv_img_dsc_t s_art_img_dsc;
|
||||||
static uint16_t *s_art_pixels = NULL;
|
static uint16_t *s_art_pixels = NULL;
|
||||||
|
|
||||||
/* Menu items */
|
/* Menu items */
|
||||||
#define MENU_ITEM_COUNT 7
|
#define MENU_ITEM_COUNT 8
|
||||||
static int s_menu_idx = 0;
|
static int s_menu_idx = 0;
|
||||||
static const char *s_menu_labels[MENU_ITEM_COUNT] = {
|
static const char *s_menu_labels[MENU_ITEM_COUNT] = {
|
||||||
"1. CARD SCANNER",
|
"1. CARD SCANNER",
|
||||||
@@ -51,7 +49,8 @@ static const char *s_menu_labels[MENU_ITEM_COUNT] = {
|
|||||||
"4. COMMANDER TAX",
|
"4. COMMANDER TAX",
|
||||||
"5. TOKEN & COUNTERS",
|
"5. TOKEN & COUNTERS",
|
||||||
"6. DECK STORAGE (LFS)",
|
"6. DECK STORAGE (LFS)",
|
||||||
"7. DEVICE & WIFI INFO"
|
"7. DEVICE & WIFI INFO",
|
||||||
|
"8. CHECK FIRMWARE OTA"
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Life & Counters */
|
/* Life & Counters */
|
||||||
@@ -127,6 +126,7 @@ static void view_storage(void);
|
|||||||
static void view_device_info(void);
|
static void view_device_info(void);
|
||||||
static void view_unmapped(const char *uid);
|
static void view_unmapped(const char *uid);
|
||||||
static void view_fetching(const char *name);
|
static void view_fetching(const char *name);
|
||||||
|
static void view_ota(void);
|
||||||
|
|
||||||
static void free_art_buffer(void)
|
static void free_art_buffer(void)
|
||||||
{
|
{
|
||||||
@@ -280,13 +280,13 @@ static void view_menu(void)
|
|||||||
ui_clean_screen();
|
ui_clean_screen();
|
||||||
ui_draw_header("MTG COMPANION", "- MAIN MENU -");
|
ui_draw_header("MTG COMPANION", "- MAIN MENU -");
|
||||||
|
|
||||||
/* 7 Menu Items */
|
/* 8 Menu Items */
|
||||||
int start_y = 52;
|
int start_y = 44;
|
||||||
for (int i = 0; i < MENU_ITEM_COUNT; i++) {
|
for (int i = 0; i < MENU_ITEM_COUNT; i++) {
|
||||||
bool selected = (i == s_menu_idx);
|
bool selected = (i == s_menu_idx);
|
||||||
lv_obj_t *item_box = lv_obj_create(lv_scr_act());
|
lv_obj_t *item_box = lv_obj_create(lv_scr_act());
|
||||||
lv_obj_set_size(item_box, DISP_W - 24, 31);
|
lv_obj_set_size(item_box, DISP_W - 24, 28);
|
||||||
lv_obj_align(item_box, LV_ALIGN_TOP_MID, 0, start_y + i * 34);
|
lv_obj_align(item_box, LV_ALIGN_TOP_MID, 0, start_y + i * 31);
|
||||||
lv_obj_set_style_radius(item_box, 6, 0);
|
lv_obj_set_style_radius(item_box, 6, 0);
|
||||||
lv_obj_set_style_pad_all(item_box, 2, 0);
|
lv_obj_set_style_pad_all(item_box, 2, 0);
|
||||||
lv_obj_set_scrollbar_mode(item_box, LV_SCROLLBAR_MODE_OFF);
|
lv_obj_set_scrollbar_mode(item_box, LV_SCROLLBAR_MODE_OFF);
|
||||||
@@ -446,8 +446,6 @@ static void view_life(void)
|
|||||||
if (!s_was_lethal) {
|
if (!s_was_lethal) {
|
||||||
s_was_lethal = true;
|
s_was_lethal = true;
|
||||||
buzzer_play_sound(BUZZER_SOUND_LOSS);
|
buzzer_play_sound(BUZZER_SOUND_LOSS);
|
||||||
led_manager_set_color(255, 0, 0);
|
|
||||||
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(LED_HOLD_MS);
|
|
||||||
}
|
}
|
||||||
lv_obj_t *dead_lbl = lv_label_create(lv_scr_act());
|
lv_obj_t *dead_lbl = lv_label_create(lv_scr_act());
|
||||||
lv_label_set_text(dead_lbl, "*** LETHAL / DEFEAT ***");
|
lv_label_set_text(dead_lbl, "*** LETHAL / DEFEAT ***");
|
||||||
@@ -595,31 +593,21 @@ static void roll_current_dice(void)
|
|||||||
s_dice_result = (int)(esp_random() % 20) + 1;
|
s_dice_result = (int)(esp_random() % 20) + 1;
|
||||||
if (s_dice_result == 20) {
|
if (s_dice_result == 20) {
|
||||||
/* Natural 20 - Victory Fanfare! */
|
/* Natural 20 - Victory Fanfare! */
|
||||||
led_manager_set_color(0, 255, 0);
|
|
||||||
buzzer_play_sound(BUZZER_SOUND_VICTORY);
|
buzzer_play_sound(BUZZER_SOUND_VICTORY);
|
||||||
} else if (s_dice_result == 1) {
|
} else if (s_dice_result == 1) {
|
||||||
/* Critical Fail */
|
/* Critical Fail */
|
||||||
led_manager_set_color(255, 0, 0);
|
|
||||||
buzzer_chirp(220, 150);
|
buzzer_chirp(220, 150);
|
||||||
} else {
|
} else {
|
||||||
led_manager_set_color(0, 180, 255);
|
|
||||||
buzzer_chirp(880, 50);
|
buzzer_chirp(880, 50);
|
||||||
}
|
}
|
||||||
} else if (s_dice_mode == DICE_MODE_D6) {
|
} else if (s_dice_mode == DICE_MODE_D6) {
|
||||||
s_dice_result = (int)(esp_random() % 6) + 1;
|
s_dice_result = (int)(esp_random() % 6) + 1;
|
||||||
led_manager_set_color(0, 180, 255);
|
|
||||||
buzzer_chirp(880, 50);
|
buzzer_chirp(880, 50);
|
||||||
} else {
|
} else {
|
||||||
/* Coin */
|
/* Coin */
|
||||||
s_dice_result = (int)(esp_random() % 2); // 0 = Tails, 1 = Heads
|
s_dice_result = (int)(esp_random() % 2); // 0 = Tails, 1 = Heads
|
||||||
if (s_dice_result == 1) {
|
|
||||||
led_manager_set_color(0, 255, 60); // Green for Heads
|
|
||||||
} else {
|
|
||||||
led_manager_set_color(255, 140, 0); // Amber for Tails
|
|
||||||
}
|
|
||||||
buzzer_chirp(988, 60);
|
buzzer_chirp(988, 60);
|
||||||
}
|
}
|
||||||
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(2000);
|
|
||||||
s_dice_rolled = true;
|
s_dice_rolled = true;
|
||||||
view_dice();
|
view_dice();
|
||||||
}
|
}
|
||||||
@@ -1005,8 +993,9 @@ static void view_device_info(void)
|
|||||||
lv_obj_set_style_text_font(lbl_keys, &lv_font_montserrat_14, 0);
|
lv_obj_set_style_text_font(lbl_keys, &lv_font_montserrat_14, 0);
|
||||||
lv_obj_set_style_text_color(lbl_keys, lv_color_hex(0x00E5FF), 0);
|
lv_obj_set_style_text_color(lbl_keys, lv_color_hex(0x00E5FF), 0);
|
||||||
lv_label_set_text(lbl_keys,
|
lv_label_set_text(lbl_keys,
|
||||||
"WI-FI STA:\n"
|
"VERSION:\n"
|
||||||
"WEB PAIR:\n"
|
"WI-FI IP:\n"
|
||||||
|
"HOTSPOT:\n"
|
||||||
"STORAGE:\n"
|
"STORAGE:\n"
|
||||||
"CARDS:\n"
|
"CARDS:\n"
|
||||||
"FREE RAM:\n"
|
"FREE RAM:\n"
|
||||||
@@ -1021,6 +1010,7 @@ static void view_device_info(void)
|
|||||||
lv_obj_set_style_text_color(lbl_vals, lv_color_hex(COLOR_AMBER), 0);
|
lv_obj_set_style_text_color(lbl_vals, lv_color_hex(COLOR_AMBER), 0);
|
||||||
lv_obj_set_style_text_align(lbl_vals, LV_TEXT_ALIGN_RIGHT, 0);
|
lv_obj_set_style_text_align(lbl_vals, LV_TEXT_ALIGN_RIGHT, 0);
|
||||||
lv_label_set_text_fmt(lbl_vals,
|
lv_label_set_text_fmt(lbl_vals,
|
||||||
|
"%s\n"
|
||||||
"%s\n"
|
"%s\n"
|
||||||
"192.168.4.1\n"
|
"192.168.4.1\n"
|
||||||
"%d / %d KB\n"
|
"%d / %d KB\n"
|
||||||
@@ -1029,6 +1019,7 @@ static void view_device_info(void)
|
|||||||
"%d steps\n"
|
"%d steps\n"
|
||||||
"ESP32+ST7789\n"
|
"ESP32+ST7789\n"
|
||||||
"MFRC522",
|
"MFRC522",
|
||||||
|
ota_manager_get_current_version(),
|
||||||
wifi_manager_get_ip(),
|
wifi_manager_get_ip(),
|
||||||
(int)(used_b / 1024), (int)(total_b / 1024),
|
(int)(used_b / 1024), (int)(total_b / 1024),
|
||||||
json_c,
|
json_c,
|
||||||
@@ -1045,6 +1036,237 @@ static void view_device_info(void)
|
|||||||
lvgl_port_unlock();
|
lvgl_port_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 8. Firmware OTA Update Screen */
|
||||||
|
static ota_status_t s_last_ota_rendered_st = OTA_STATUS_IDLE;
|
||||||
|
static int s_last_ota_percent = -1;
|
||||||
|
|
||||||
|
static void view_ota(void)
|
||||||
|
{
|
||||||
|
lvgl_port_lock(0);
|
||||||
|
ui_clean_screen();
|
||||||
|
|
||||||
|
ota_status_t st = ota_manager_get_status();
|
||||||
|
s_last_ota_rendered_st = st;
|
||||||
|
|
||||||
|
ui_draw_header("FIRMWARE UPDATE", "GITEA OTA");
|
||||||
|
|
||||||
|
switch (st) {
|
||||||
|
case OTA_STATUS_CHECKING: {
|
||||||
|
lv_obj_t *box = lv_obj_create(lv_scr_act());
|
||||||
|
lv_obj_set_size(box, DISP_W - 24, 180);
|
||||||
|
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
|
||||||
|
lv_obj_set_style_bg_color(box, lv_color_hex(0x1C1C1C), 0);
|
||||||
|
lv_obj_set_style_border_color(box, lv_color_hex(0x00E5FF), 0);
|
||||||
|
lv_obj_set_style_border_width(box, 2, 0);
|
||||||
|
lv_obj_set_style_radius(box, 8, 0);
|
||||||
|
|
||||||
|
lv_obj_t *lbl = lv_label_create(box);
|
||||||
|
lv_label_set_text(lbl, "CHECKING GITEA...\n\nConnecting to\nreleases API");
|
||||||
|
lv_obj_set_style_text_font(lbl, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(lbl, lv_color_hex(0x00E5FF), 0);
|
||||||
|
lv_obj_set_style_text_align(lbl, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
|
lv_obj_align(lbl, LV_ALIGN_CENTER, 0, 0);
|
||||||
|
|
||||||
|
lv_obj_t *hint = lv_label_create(lv_scr_act());
|
||||||
|
lv_label_set_text(hint, "HOLD BUTTON: MAIN MENU");
|
||||||
|
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_AMBER), 0);
|
||||||
|
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OTA_STATUS_UPDATE_AVAILABLE: {
|
||||||
|
const ota_release_info_t *rel = ota_manager_get_release_info();
|
||||||
|
const char *cur_ver = ota_manager_get_current_version();
|
||||||
|
|
||||||
|
lv_obj_t *box = lv_obj_create(lv_scr_act());
|
||||||
|
lv_obj_set_size(box, DISP_W - 24, 200);
|
||||||
|
lv_obj_align(box, LV_ALIGN_CENTER, 0, -15);
|
||||||
|
lv_obj_set_style_bg_color(box, lv_color_hex(0x102818), 0);
|
||||||
|
lv_obj_set_style_border_color(box, lv_color_hex(0x00E676), 0);
|
||||||
|
lv_obj_set_style_border_width(box, 2, 0);
|
||||||
|
lv_obj_set_style_radius(box, 8, 0);
|
||||||
|
|
||||||
|
lv_obj_t *t = lv_label_create(box);
|
||||||
|
lv_label_set_text(t, "NEW VERSION AVAILABLE!");
|
||||||
|
lv_obj_set_style_text_font(t, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(t, lv_color_hex(0x00E676), 0);
|
||||||
|
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, 8);
|
||||||
|
|
||||||
|
lv_obj_t *v = lv_label_create(box);
|
||||||
|
lv_label_set_text_fmt(v, "TARGET: %s", rel->tag_name);
|
||||||
|
lv_obj_set_style_text_font(v, &lv_font_montserrat_20, 0);
|
||||||
|
lv_obj_set_style_text_color(v, lv_color_hex(0xFFCC00), 0);
|
||||||
|
lv_obj_align(v, LV_ALIGN_TOP_MID, 0, 38);
|
||||||
|
|
||||||
|
lv_obj_t *c = lv_label_create(box);
|
||||||
|
lv_label_set_text_fmt(c, "Current: %s", cur_ver);
|
||||||
|
lv_obj_set_style_text_font(c, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(c, lv_color_white(), 0);
|
||||||
|
lv_obj_align(c, LV_ALIGN_TOP_MID, 0, 70);
|
||||||
|
|
||||||
|
lv_obj_t *s = lv_label_create(box);
|
||||||
|
if (rel->binary_size > 0) {
|
||||||
|
lv_label_set_text_fmt(s, "Size: %d KB", (int)(rel->binary_size / 1024));
|
||||||
|
} else {
|
||||||
|
lv_label_set_text(s, "Ready to stream");
|
||||||
|
}
|
||||||
|
lv_obj_set_style_text_font(s, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(s, lv_color_hex(0x888888), 0);
|
||||||
|
lv_obj_align(s, LV_ALIGN_TOP_MID, 0, 95);
|
||||||
|
|
||||||
|
lv_obj_t *act = lv_label_create(box);
|
||||||
|
lv_label_set_text(act, "> CLICK: INSTALL NOW <");
|
||||||
|
lv_obj_set_style_text_font(act, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(act, lv_color_hex(0x00E676), 0);
|
||||||
|
lv_obj_align(act, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||||
|
|
||||||
|
lv_obj_t *hint = lv_label_create(lv_scr_act());
|
||||||
|
lv_label_set_text(hint, "ROTATE / HOLD: SKIP FOR NOW");
|
||||||
|
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_AMBER), 0);
|
||||||
|
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OTA_STATUS_UP_TO_DATE: {
|
||||||
|
const char *cur_ver = ota_manager_get_current_version();
|
||||||
|
lv_obj_t *box = lv_obj_create(lv_scr_act());
|
||||||
|
lv_obj_set_size(box, DISP_W - 24, 180);
|
||||||
|
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
|
||||||
|
lv_obj_set_style_bg_color(box, lv_color_hex(0x1C1C1C), 0);
|
||||||
|
lv_obj_set_style_border_color(box, lv_color_hex(0x333333), 0);
|
||||||
|
lv_obj_set_style_border_width(box, 1, 0);
|
||||||
|
lv_obj_set_style_radius(box, 8, 0);
|
||||||
|
|
||||||
|
lv_obj_t *t = lv_label_create(box);
|
||||||
|
lv_label_set_text(t, "UP TO DATE");
|
||||||
|
lv_obj_set_style_text_font(t, &lv_font_montserrat_20, 0);
|
||||||
|
lv_obj_set_style_text_color(t, lv_color_hex(0x00E676), 0);
|
||||||
|
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, 15);
|
||||||
|
|
||||||
|
lv_obj_t *v = lv_label_create(box);
|
||||||
|
lv_label_set_text_fmt(v, "Version: %s\n\nNo newer releases\nfound on Gitea.", cur_ver);
|
||||||
|
lv_obj_set_style_text_font(v, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(v, lv_color_white(), 0);
|
||||||
|
lv_obj_set_style_text_align(v, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
|
lv_obj_align(v, LV_ALIGN_CENTER, 0, 15);
|
||||||
|
|
||||||
|
lv_obj_t *hint = lv_label_create(lv_scr_act());
|
||||||
|
lv_label_set_text(hint, "CLICK: MAIN MENU");
|
||||||
|
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_AMBER), 0);
|
||||||
|
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OTA_STATUS_DOWNLOADING: {
|
||||||
|
int percent = ota_manager_get_progress();
|
||||||
|
s_last_ota_percent = percent;
|
||||||
|
size_t rb = 0, tb = 0;
|
||||||
|
ota_manager_get_bytes(&rb, &tb);
|
||||||
|
|
||||||
|
lv_obj_t *box = lv_obj_create(lv_scr_act());
|
||||||
|
lv_obj_set_size(box, DISP_W - 24, 200);
|
||||||
|
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
|
||||||
|
lv_obj_set_style_bg_color(box, lv_color_hex(0x181828), 0);
|
||||||
|
lv_obj_set_style_border_color(box, lv_color_hex(0x00E5FF), 0);
|
||||||
|
lv_obj_set_style_border_width(box, 2, 0);
|
||||||
|
lv_obj_set_style_radius(box, 8, 0);
|
||||||
|
|
||||||
|
lv_obj_t *t = lv_label_create(box);
|
||||||
|
lv_label_set_text(t, "FLASHING FIRMWARE...");
|
||||||
|
lv_obj_set_style_text_font(t, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(t, lv_color_hex(0x00E5FF), 0);
|
||||||
|
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, 10);
|
||||||
|
|
||||||
|
lv_obj_t *p_lbl = lv_label_create(box);
|
||||||
|
lv_label_set_text_fmt(p_lbl, "%d%%", percent);
|
||||||
|
lv_obj_set_style_text_font(p_lbl, &lv_font_montserrat_48, 0);
|
||||||
|
lv_obj_set_style_text_color(p_lbl, lv_color_hex(0xFFCC00), 0);
|
||||||
|
lv_obj_align(p_lbl, LV_ALIGN_CENTER, 0, -15);
|
||||||
|
|
||||||
|
lv_obj_t *bar = lv_bar_create(box);
|
||||||
|
lv_obj_set_size(bar, DISP_W - 60, 14);
|
||||||
|
lv_obj_align(bar, LV_ALIGN_CENTER, 0, 35);
|
||||||
|
lv_bar_set_range(bar, 0, 100);
|
||||||
|
lv_bar_set_value(bar, percent, LV_ANIM_OFF);
|
||||||
|
lv_obj_set_style_bg_color(bar, lv_color_hex(0x333333), 0);
|
||||||
|
lv_obj_set_style_bg_color(bar, lv_color_hex(0x00E676), LV_PART_INDICATOR);
|
||||||
|
|
||||||
|
lv_obj_t *b_lbl = lv_label_create(box);
|
||||||
|
lv_label_set_text_fmt(b_lbl, "%d / %d KB", (int)(rb / 1024), (int)(tb / 1024));
|
||||||
|
lv_obj_set_style_text_font(b_lbl, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(b_lbl, lv_color_hex(0xAAAAAA), 0);
|
||||||
|
lv_obj_align(b_lbl, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||||
|
|
||||||
|
lv_obj_t *warn = lv_label_create(lv_scr_act());
|
||||||
|
lv_label_set_text(warn, "DO NOT TURN OFF POWER");
|
||||||
|
lv_obj_set_style_text_font(warn, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(warn, lv_color_hex(0xFF1744), 0);
|
||||||
|
lv_obj_align(warn, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OTA_STATUS_SUCCESS_REBOOTING: {
|
||||||
|
lv_obj_t *box = lv_obj_create(lv_scr_act());
|
||||||
|
lv_obj_set_size(box, DISP_W - 24, 180);
|
||||||
|
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
|
||||||
|
lv_obj_set_style_bg_color(box, lv_color_hex(0x102818), 0);
|
||||||
|
lv_obj_set_style_border_color(box, lv_color_hex(0x00E676), 0);
|
||||||
|
lv_obj_set_style_border_width(box, 2, 0);
|
||||||
|
lv_obj_set_style_radius(box, 8, 0);
|
||||||
|
|
||||||
|
lv_obj_t *t = lv_label_create(box);
|
||||||
|
lv_label_set_text(t, "UPDATE SUCCESSFUL!");
|
||||||
|
lv_obj_set_style_text_font(t, &lv_font_montserrat_20, 0);
|
||||||
|
lv_obj_set_style_text_color(t, lv_color_hex(0x00E676), 0);
|
||||||
|
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, 20);
|
||||||
|
|
||||||
|
lv_obj_t *m = lv_label_create(box);
|
||||||
|
lv_label_set_text(m, "New firmware verified.\n\nRebooting device...");
|
||||||
|
lv_obj_set_style_text_font(m, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(m, lv_color_white(), 0);
|
||||||
|
lv_obj_set_style_text_align(m, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
|
lv_obj_align(m, LV_ALIGN_CENTER, 0, 25);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OTA_STATUS_FAILED:
|
||||||
|
default: {
|
||||||
|
lv_obj_t *box = lv_obj_create(lv_scr_act());
|
||||||
|
lv_obj_set_size(box, DISP_W - 24, 180);
|
||||||
|
lv_obj_align(box, LV_ALIGN_CENTER, 0, -10);
|
||||||
|
lv_obj_set_style_bg_color(box, lv_color_hex(0x281010), 0);
|
||||||
|
lv_obj_set_style_border_color(box, lv_color_hex(0xFF1744), 0);
|
||||||
|
lv_obj_set_style_border_width(box, 2, 0);
|
||||||
|
lv_obj_set_style_radius(box, 8, 0);
|
||||||
|
|
||||||
|
lv_obj_t *t = lv_label_create(box);
|
||||||
|
lv_label_set_text(t, "UPDATE FAILED");
|
||||||
|
lv_obj_set_style_text_font(t, &lv_font_montserrat_20, 0);
|
||||||
|
lv_obj_set_style_text_color(t, lv_color_hex(0xFF1744), 0);
|
||||||
|
lv_obj_align(t, LV_ALIGN_TOP_MID, 0, 15);
|
||||||
|
|
||||||
|
lv_obj_t *msg = lv_label_create(box);
|
||||||
|
lv_label_set_text_fmt(msg, "%s\n\nExisting firmware intact.", ota_manager_get_message());
|
||||||
|
lv_obj_set_style_text_font(msg, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(msg, lv_color_white(), 0);
|
||||||
|
lv_obj_set_style_text_align(msg, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
|
lv_obj_align(msg, LV_ALIGN_CENTER, 0, 20);
|
||||||
|
|
||||||
|
lv_obj_t *hint = lv_label_create(lv_scr_act());
|
||||||
|
lv_label_set_text(hint, "CLICK: MAIN MENU");
|
||||||
|
lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_AMBER), 0);
|
||||||
|
lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lvgl_port_unlock();
|
||||||
|
}
|
||||||
|
|
||||||
/* 8. Card View Screen */
|
/* 8. Card View Screen */
|
||||||
static void view_card(void)
|
static void view_card(void)
|
||||||
{
|
{
|
||||||
@@ -1225,22 +1447,39 @@ static void view_unmapped(const char *uid)
|
|||||||
lv_obj_align(u_lbl, LV_ALIGN_TOP_MID, 0, 70);
|
lv_obj_align(u_lbl, LV_ALIGN_TOP_MID, 0, 70);
|
||||||
|
|
||||||
lv_obj_t *sub = lv_label_create(lv_scr_act());
|
lv_obj_t *sub = lv_label_create(lv_scr_act());
|
||||||
lv_label_set_text(sub, "PAIR IT ON THE WEB:");
|
|
||||||
lv_obj_set_style_text_font(sub, &lv_font_montserrat_14, 0);
|
lv_obj_set_style_text_font(sub, &lv_font_montserrat_14, 0);
|
||||||
lv_obj_set_style_text_color(sub, lv_color_white(), 0);
|
lv_obj_set_style_text_color(sub, lv_color_white(), 0);
|
||||||
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 130);
|
|
||||||
|
|
||||||
lv_obj_t *ssid = lv_label_create(lv_scr_act());
|
|
||||||
lv_label_set_text(ssid, "SSID: MTG-Companion");
|
|
||||||
lv_obj_set_style_text_font(ssid, &lv_font_montserrat_14, 0);
|
|
||||||
lv_obj_set_style_text_color(ssid, lv_color_hex(0xFFCC00), 0);
|
|
||||||
lv_obj_align(ssid, LV_ALIGN_TOP_MID, 0, 160);
|
|
||||||
|
|
||||||
lv_obj_t *ip = lv_label_create(lv_scr_act());
|
lv_obj_t *ip = lv_label_create(lv_scr_act());
|
||||||
lv_label_set_text(ip, "http://192.168.4.1");
|
|
||||||
lv_obj_set_style_text_font(ip, &lv_font_montserrat_20, 0);
|
lv_obj_set_style_text_font(ip, &lv_font_montserrat_20, 0);
|
||||||
lv_obj_set_style_text_color(ip, lv_color_hex(0x00E676), 0);
|
lv_obj_set_style_text_color(ip, lv_color_hex(0x00E676), 0);
|
||||||
lv_obj_align(ip, LV_ALIGN_TOP_MID, 0, 190);
|
|
||||||
|
if (wifi_manager_connected()) {
|
||||||
|
lv_label_set_text(sub, "PAIR VIA WI-FI:");
|
||||||
|
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 105);
|
||||||
|
|
||||||
|
lv_label_set_text_fmt(ip, "http://%s", wifi_manager_get_ip());
|
||||||
|
lv_obj_align(ip, LV_ALIGN_TOP_MID, 0, 135);
|
||||||
|
|
||||||
|
lv_obj_t *alt = lv_label_create(lv_scr_act());
|
||||||
|
lv_label_set_text(alt, "or Hotspot: MTG-Companion\n(http://192.168.4.1)");
|
||||||
|
lv_obj_set_style_text_font(alt, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(alt, lv_color_hex(0x888888), 0);
|
||||||
|
lv_obj_set_style_text_align(alt, LV_TEXT_ALIGN_CENTER, 0);
|
||||||
|
lv_obj_align(alt, LV_ALIGN_TOP_MID, 0, 175);
|
||||||
|
} else {
|
||||||
|
lv_label_set_text(sub, "PAIR ON HOTSPOT:");
|
||||||
|
lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 105);
|
||||||
|
|
||||||
|
lv_obj_t *ssid = lv_label_create(lv_scr_act());
|
||||||
|
lv_label_set_text(ssid, "SSID: MTG-Companion");
|
||||||
|
lv_obj_set_style_text_font(ssid, &lv_font_montserrat_14, 0);
|
||||||
|
lv_obj_set_style_text_color(ssid, lv_color_hex(0xFFCC00), 0);
|
||||||
|
lv_obj_align(ssid, LV_ALIGN_TOP_MID, 0, 135);
|
||||||
|
|
||||||
|
lv_label_set_text(ip, "http://192.168.4.1");
|
||||||
|
lv_obj_align(ip, LV_ALIGN_TOP_MID, 0, 165);
|
||||||
|
}
|
||||||
|
|
||||||
lv_obj_t *hint = lv_label_create(lv_scr_act());
|
lv_obj_t *hint = lv_label_create(lv_scr_act());
|
||||||
lv_label_set_text(hint, "HOLD BUTTON: MAIN MENU");
|
lv_label_set_text(hint, "HOLD BUTTON: MAIN MENU");
|
||||||
@@ -1281,40 +1520,6 @@ static void view_fetching(const char *name)
|
|||||||
lvgl_port_unlock();
|
lvgl_port_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* LED helpers */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
static void led_for_card(const char *mana)
|
|
||||||
{
|
|
||||||
uint8_t r = 255, g = 200, b = 0;
|
|
||||||
if (mana != NULL && mana[0] != '\0') {
|
|
||||||
if (strstr(mana, "{W}")) { r = 255; g = 255; b = 200; }
|
|
||||||
else if (strstr(mana, "{U}")) { r = 40; g = 100; b = 255; }
|
|
||||||
else if (strstr(mana, "{B}")) { r = 90; g = 20; b = 150; }
|
|
||||||
else if (strstr(mana, "{R}")) { r = 255; g = 60; b = 20; }
|
|
||||||
else if (strstr(mana, "{G}")) { r = 20; g = 200; b = 60; }
|
|
||||||
}
|
|
||||||
led_manager_set_color(r, g, b);
|
|
||||||
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(LED_HOLD_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void read_cached_color(const char *uid)
|
|
||||||
{
|
|
||||||
char json_path[72];
|
|
||||||
snprintf(json_path, sizeof(json_path), STORAGE_CARDS_DIR "/%s.json", uid);
|
|
||||||
char *buf = NULL;
|
|
||||||
size_t len = 0;
|
|
||||||
if (storage_read_text(json_path, &buf, &len) == ESP_OK && buf != NULL) {
|
|
||||||
cJSON *root = cJSON_Parse(buf);
|
|
||||||
if (root != NULL) {
|
|
||||||
led_for_card(get_json_str(root, "mana_cost", ""));
|
|
||||||
cJSON_Delete(root);
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handle_new_card(const char *uid)
|
static void handle_new_card(const char *uid)
|
||||||
{
|
{
|
||||||
snprintf(s_uid, sizeof(s_uid), "%s", uid);
|
snprintf(s_uid, sizeof(s_uid), "%s", uid);
|
||||||
@@ -1325,8 +1530,6 @@ static void handle_new_card(const char *uid)
|
|||||||
if (ret != ESP_OK || name == NULL) {
|
if (ret != ESP_OK || name == NULL) {
|
||||||
app_set_view(APP_STATE_REGISTRATION);
|
app_set_view(APP_STATE_REGISTRATION);
|
||||||
view_unmapped(uid);
|
view_unmapped(uid);
|
||||||
led_manager_set_color(60, 60, 200);
|
|
||||||
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(LED_HOLD_MS);
|
|
||||||
buzzer_chirp(392, 80);
|
buzzer_chirp(392, 80);
|
||||||
free(name);
|
free(name);
|
||||||
return;
|
return;
|
||||||
@@ -1336,7 +1539,6 @@ static void handle_new_card(const char *uid)
|
|||||||
app_set_view(APP_STATE_CARD_VIEW);
|
app_set_view(APP_STATE_CARD_VIEW);
|
||||||
|
|
||||||
buzzer_chirp(880, 60);
|
buzzer_chirp(880, 60);
|
||||||
read_cached_color(uid);
|
|
||||||
|
|
||||||
char json_path[72];
|
char json_path[72];
|
||||||
snprintf(json_path, sizeof(json_path), STORAGE_CARDS_DIR "/%s.json", uid);
|
snprintf(json_path, sizeof(json_path), STORAGE_CARDS_DIR "/%s.json", uid);
|
||||||
@@ -1375,12 +1577,9 @@ static void handle_fetch_ready(void)
|
|||||||
scryfall_consume_result();
|
scryfall_consume_result();
|
||||||
s_waiting_fetch = false;
|
s_waiting_fetch = false;
|
||||||
if (success) {
|
if (success) {
|
||||||
read_cached_color(s_uid);
|
|
||||||
view_card();
|
view_card();
|
||||||
} else {
|
} else {
|
||||||
buzzer_chirp(220, 150);
|
buzzer_chirp(220, 150);
|
||||||
led_manager_set_color(255, 0, 0);
|
|
||||||
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(LED_HOLD_MS);
|
|
||||||
|
|
||||||
lvgl_port_lock(0);
|
lvgl_port_lock(0);
|
||||||
ui_clean_screen();
|
ui_clean_screen();
|
||||||
@@ -1465,11 +1664,31 @@ static void handle_encoder_click(void)
|
|||||||
app_set_view(APP_STATE_DEVICE_INFO);
|
app_set_view(APP_STATE_DEVICE_INFO);
|
||||||
view_device_info();
|
view_device_info();
|
||||||
break;
|
break;
|
||||||
|
case 7:
|
||||||
|
app_set_view(APP_STATE_OTA_UPDATE);
|
||||||
|
buzzer_chirp(988, 40);
|
||||||
|
ota_manager_check_update_async();
|
||||||
|
view_ota();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case APP_STATE_OTA_UPDATE: {
|
||||||
|
ota_status_t st = ota_manager_get_status();
|
||||||
|
if (st == OTA_STATUS_UPDATE_AVAILABLE) {
|
||||||
|
buzzer_chirp(1046, 100);
|
||||||
|
ota_manager_start_update_async();
|
||||||
|
view_ota();
|
||||||
|
} else if (st == OTA_STATUS_UP_TO_DATE || st == OTA_STATUS_FAILED) {
|
||||||
|
ota_manager_reset();
|
||||||
|
app_set_view(APP_STATE_MENU);
|
||||||
|
view_menu();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case APP_STATE_LIFE_COUNTER:
|
case APP_STATE_LIFE_COUNTER:
|
||||||
s_life_mode = (s_life_mode + 1) % 3;
|
s_life_mode = (s_life_mode + 1) % 3;
|
||||||
buzzer_chirp(880, 30);
|
buzzer_chirp(880, 30);
|
||||||
@@ -1506,8 +1725,6 @@ static void handle_encoder_click(void)
|
|||||||
if (s_storage_option_idx == 0) {
|
if (s_storage_option_idx == 0) {
|
||||||
storage_manager_clear_all_cards(true);
|
storage_manager_clear_all_cards(true);
|
||||||
buzzer_chirp(1046, 100);
|
buzzer_chirp(1046, 100);
|
||||||
led_manager_set_color(0, 255, 60);
|
|
||||||
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(1500);
|
|
||||||
s_storage_confirm_dialog = false;
|
s_storage_confirm_dialog = false;
|
||||||
view_storage();
|
view_storage();
|
||||||
} else {
|
} else {
|
||||||
@@ -1654,6 +1871,16 @@ static void handle_encoder_rotation(int32_t delta)
|
|||||||
view_storage();
|
view_storage();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case APP_STATE_OTA_UPDATE: {
|
||||||
|
ota_status_t st = ota_manager_get_status();
|
||||||
|
if (st == OTA_STATUS_UPDATE_AVAILABLE) {
|
||||||
|
ota_manager_reset();
|
||||||
|
app_set_view(APP_STATE_MENU);
|
||||||
|
view_menu();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1669,13 +1896,17 @@ static void ui_task(void *arg)
|
|||||||
ESP_LOGI(TAG, "UI task on core %d", xPortGetCoreID());
|
ESP_LOGI(TAG, "UI task on core %d", xPortGetCoreID());
|
||||||
|
|
||||||
encoder_init();
|
encoder_init();
|
||||||
led_manager_init();
|
|
||||||
buzzer_init();
|
buzzer_init();
|
||||||
|
|
||||||
/* Start on Main Menu at boot */
|
/* Start on Main Menu at boot */
|
||||||
app_set_view(APP_STATE_MENU);
|
app_set_view(APP_STATE_MENU);
|
||||||
view_menu();
|
view_menu();
|
||||||
|
|
||||||
|
#if CONFIG_MTG_OTA_AUTO_CHECK
|
||||||
|
static bool s_boot_ota_checked = false;
|
||||||
|
static uint32_t s_wifi_up_ms = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
/* 1. New RFID card - scans immediately anywhere */
|
/* 1. New RFID card - scans immediately anywhere */
|
||||||
if (rfid_manager_has_pending_card()) {
|
if (rfid_manager_has_pending_card()) {
|
||||||
@@ -1697,8 +1928,6 @@ static void ui_task(void *arg)
|
|||||||
buzzer_chirp(1046, 50);
|
buzzer_chirp(1046, 50);
|
||||||
vTaskDelay(pdMS_TO_TICKS(40));
|
vTaskDelay(pdMS_TO_TICKS(40));
|
||||||
buzzer_chirp(1318, 70);
|
buzzer_chirp(1318, 70);
|
||||||
led_manager_set_color(180, 0, 255);
|
|
||||||
s_led_off_tick = xTaskGetTickCount() + pdMS_TO_TICKS(800);
|
|
||||||
app_set_view(APP_STATE_MENU);
|
app_set_view(APP_STATE_MENU);
|
||||||
view_menu();
|
view_menu();
|
||||||
}
|
}
|
||||||
@@ -1716,12 +1945,34 @@ static void ui_task(void *arg)
|
|||||||
handle_encoder_rotation(delta);
|
handle_encoder_rotation(delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 5. Auto-dim LED */
|
/* 5. OTA screen live refresh / status change */
|
||||||
if (s_led_off_tick != 0 && xTaskGetTickCount() >= s_led_off_tick) {
|
if (app_get_view() == APP_STATE_OTA_UPDATE) {
|
||||||
s_led_off_tick = 0;
|
ota_status_t cur_ota_st = ota_manager_get_status();
|
||||||
led_manager_off();
|
int cur_percent = ota_manager_get_progress();
|
||||||
|
if (cur_ota_st != s_last_ota_rendered_st ||
|
||||||
|
(cur_ota_st == OTA_STATUS_DOWNLOADING && cur_percent != s_last_ota_percent)) {
|
||||||
|
view_ota();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CONFIG_MTG_OTA_AUTO_CHECK
|
||||||
|
/* 6. Automatic background OTA check on startup after Wi-Fi connects */
|
||||||
|
if (!s_boot_ota_checked && wifi_manager_connected()) {
|
||||||
|
s_wifi_up_ms += 25;
|
||||||
|
if (s_wifi_up_ms >= 4000) {
|
||||||
|
s_boot_ota_checked = true;
|
||||||
|
ESP_LOGI(TAG, "Triggering startup Gitea OTA check...");
|
||||||
|
ota_manager_check_update_async();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s_boot_ota_checked && ota_manager_get_status() == OTA_STATUS_UPDATE_AVAILABLE &&
|
||||||
|
app_get_view() == APP_STATE_MENU) {
|
||||||
|
app_set_view(APP_STATE_OTA_UPDATE);
|
||||||
|
buzzer_chirp(1046, 120);
|
||||||
|
view_ota();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(25));
|
vTaskDelay(pdMS_TO_TICKS(25));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "rfid_manager.h"
|
#include "rfid_manager.h"
|
||||||
#include "storage_manager.h"
|
#include "storage_manager.h"
|
||||||
#include "scryfall_client.h"
|
#include "scryfall_client.h"
|
||||||
|
#include "wifi_manager.h"
|
||||||
#include "web_server.h"
|
#include "web_server.h"
|
||||||
|
|
||||||
static const char *TAG = "web";
|
static const char *TAG = "web";
|
||||||
@@ -397,6 +398,7 @@ esp_err_t web_server_start(void)
|
|||||||
ESP_LOGE(TAG, "failed to create AP netif");
|
ESP_LOGE(TAG, "failed to create AP netif");
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
esp_netif_set_hostname(ap, "mtg-companion");
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t ret = esp_wifi_stop(); /* safe even if not started */
|
esp_err_t ret = esp_wifi_stop(); /* safe even if not started */
|
||||||
@@ -471,7 +473,7 @@ esp_err_t web_server_start(void)
|
|||||||
ESP_ERROR_CHECK(httpd_register_uri_handler(s_server, &URI_ART));
|
ESP_ERROR_CHECK(httpd_register_uri_handler(s_server, &URI_ART));
|
||||||
ESP_ERROR_CHECK(httpd_register_uri_handler(s_server, &URI_UNBIND));
|
ESP_ERROR_CHECK(httpd_register_uri_handler(s_server, &URI_UNBIND));
|
||||||
|
|
||||||
ESP_LOGI(TAG, "SoftAP '%s' at 192.168.4.1, web server ready", AP_SSID);
|
ESP_LOGI(TAG, "Web server ready on all interfaces (Hotspot: 192.168.4.1, Wi-Fi: %s)", wifi_manager_get_ip());
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,10 @@ esp_err_t wifi_manager_init(void)
|
|||||||
|
|
||||||
ESP_ERROR_CHECK(esp_netif_init());
|
ESP_ERROR_CHECK(esp_netif_init());
|
||||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||||
esp_netif_create_default_wifi_sta();
|
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
|
||||||
|
if (sta_netif != NULL) {
|
||||||
|
esp_netif_set_hostname(sta_netif, "mtg-companion");
|
||||||
|
}
|
||||||
|
|
||||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
# Name, Type, SubType, Offset, Size
|
# Name, Type, SubType, Offset, Size
|
||||||
# 4MB ESP32 flash layout:
|
# 4MB ESP32 dual-OTA layout:
|
||||||
# nvs 24KB 0x9000 - NVS settings (required by esp_wifi / http / storage)
|
# nvs 16KB 0x9000 - NVS settings
|
||||||
# phy_init 4KB 0xF000 - PHY init data (optional reserved)
|
# otadata 8KB 0xD000 - OTA selection & rollback data
|
||||||
# factory 2MB app - firmware
|
# phy_init 4KB 0xF000 - PHY calibration data
|
||||||
# storage ~1.9MB littlefs - card database (/spiffs)
|
# ota_0 1.56MB 0x10000 - App slot 0
|
||||||
nvs, data, nvs, 0x9000, 0x6000
|
# ota_1 1.56MB 0x1A0000 - App slot 1
|
||||||
phy_init, data, phy, 0xf000, 0x1000
|
# storage 832KB 0x330000 - LittleFS database (/spiffs)
|
||||||
factory, app, factory, 0x10000, 0x200000
|
nvs, data, nvs, 0x9000, 0x4000
|
||||||
storage, data, littlefs, 0x210000, 0x1F0000
|
otadata, data, ota, 0xd000, 0x2000
|
||||||
|
phy_init, data, phy, 0xf000, 0x1000
|
||||||
|
ota_0, app, ota_0, 0x10000, 0x190000
|
||||||
|
ota_1, app, ota_1, 0x1a0000, 0x190000
|
||||||
|
storage, data, littlefs, 0x330000, 0xd0000
|
||||||
|
@@ -437,6 +437,10 @@ CONFIG_PARTITION_TABLE_MD5=y
|
|||||||
CONFIG_MTG_WIFI_SSID="WiFimodem-FD58"
|
CONFIG_MTG_WIFI_SSID="WiFimodem-FD58"
|
||||||
CONFIG_MTG_WIFI_PASS="gzhrt5yzm3"
|
CONFIG_MTG_WIFI_PASS="gzhrt5yzm3"
|
||||||
CONFIG_MTG_SCAN_PREFETCH=y
|
CONFIG_MTG_SCAN_PREFETCH=y
|
||||||
|
CONFIG_MTG_GITEA_URL="https://gitea.com"
|
||||||
|
CONFIG_MTG_GITEA_REPO_OWNER="user"
|
||||||
|
CONFIG_MTG_GITEA_REPO_NAME="espMTG"
|
||||||
|
CONFIG_MTG_OTA_AUTO_CHECK=y
|
||||||
# end of MTG RFID Companion
|
# end of MTG RFID Companion
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -14,4 +14,10 @@ CONFIG_JD_FORMAT_RGB565=y
|
|||||||
CONFIG_JD_FASTDECODE_32BIT=y
|
CONFIG_JD_FASTDECODE_32BIT=y
|
||||||
|
|
||||||
# Some ESP32 boards ship a Boya flash chip; use the optimized driver
|
# Some ESP32 boards ship a Boya flash chip; use the optimized driver
|
||||||
CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y
|
CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y
|
||||||
|
|
||||||
|
# Set network hostname for DHCP / router discovery
|
||||||
|
CONFIG_LWIP_LOCAL_HOSTNAME="mtg-companion"
|
||||||
|
|
||||||
|
# Enable OTA rollback protection
|
||||||
|
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
|
||||||
Reference in New Issue
Block a user