https://github.com/clusterm/ha-proxmark3
Home Assistant integration for Proxmark3 (Iceman): MF tag UID, block data, auto USB detect
https://github.com/clusterm/ha-proxmark3
hacs home-assistant mifare nfc proxmark3 rfid
Last synced: 9 days ago
JSON representation
Home Assistant integration for Proxmark3 (Iceman): MF tag UID, block data, auto USB detect
- Host: GitHub
- URL: https://github.com/clusterm/ha-proxmark3
- Owner: ClusterM
- License: gpl-3.0
- Created: 2026-05-23T11:58:43.000Z (11 days ago)
- Default Branch: master
- Last Pushed: 2026-05-23T14:29:32.000Z (11 days ago)
- Last Synced: 2026-05-23T16:14:06.280Z (11 days ago)
- Topics: hacs, home-assistant, mifare, nfc, proxmark3, rfid
- Language: Python
- Size: 556 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# Proxmark3 for Home Assistant
Home Assistant custom integration for [Proxmark3](https://github.com/RfidResearchGroup/proxmark3) (Iceman firmware). Polls for MIFARE / ISO14443-A tags and exposes UID, tag type, and optional block data as a sensor.
## Features
- Single Proxmark3 via USB serial (auto-detect)
- Configurable MIFARE Classic keys and blocks to read
- NDEF decoding (Ultralight/NTAG and optional MIFARE Classic) into a JSON `ntag` attribute
- Device info (MCU, firmware, memory) cached while offline
- Automatic reconnect when the device is unplugged
- Options flow to tune keys, blocks, and poll intervals without re-adding the integration
## Installation
### HACS
> **Note:** The integration is not in the [default HACS repository](https://github.com/hacs/integration) yet. Until it is included, add this repo as a [custom repository](https://hacs.xyz/docs/faq/custom_repositories/) (category: **Integration**), then install **Proxmark3**.
### Manual
Copy `custom_components/proxmark3` into your Home Assistant `config/custom_components/` directory and restart Home Assistant.
## Requirements
- Proxmark3 with Iceman firmware ([RfidResearchGroup](https://github.com/RfidResearchGroup/proxmark3))
- USB access from the Home Assistant host (Docker: pass through `/dev/ttyACM0` or the device by-id path)
- Python packages `pyserial` and `proxmark3py` (installed automatically via manifest)
## Configuration
Setup is a short wizard (USB port is auto-detected):
1. **Reading** — NDEF options, raw MIFARE Classic blocks toggle, poll intervals
2. **Raw blocks** (only if enabled) — blocks 0–3 of MIFARE Classic sector 0
3. **MIFARE Classic keys** (only if raw blocks enabled) — Key A presets for block reads
Use **Options** later to change the same settings.
**UID only:** leave everything off on screen 1. The sensor state is always the tag UID; `tag_type` is still reported.
| Option | Tag types | Speed |
|--------|-----------|-------|
| Read NDEF (Ultralight / NTAG) | Type 2 tags | Fast |
| Read NDEF on MIFARE Classic (slow) | Phone-formatted MIFARE Classic MAD/NDEF | Slow (several sector reads) |
| Read raw blocks on MIFARE Classic | MIFARE Classic sector 0 blocks | Depends on enabled blocks + keys |
More enabled options increase read time on each new tag presentation.
### NDEF
- **Ultralight / NTAG:** enable **Read NDEF** — no keys required.
- **MIFARE Classic (phone NDEF):** enable **Read NDEF on MIFARE Classic (slow)** separately. Uses MAD key (`a0a1a2a3a4a5`) and NDEF sector keys automatically (same as `hf mf ndefread`).
#### Writing NDEF (phone is easiest)
For **Ultralight / NTAG** and **MIFARE Classic tags formatted as NDEF**, writing on a phone is usually the simplest option — no Proxmark3 required for programming.
1. Install a free NFC app, e.g. **[NFC Tools](https://www.wakdev.com/en/apps/nfc-tools.html)** (Android / iOS).
2. Choose **Write** → add a record:
- **URL / URI** — links, `https://…`
- **Text** — plain text with language code
- **Custom** — other NDEF types supported by the app
3. Hold the tag to the phone and write.
4. In Home Assistant, enable **Read NDEF** (and **Read NDEF on MIFARE Classic** for MIFARE Classic tags), then verify the `ntag` attribute on `sensor.proxmark3_nfc_tag`.
NFC Tools (and similar apps) produce standard NDEF that this integration parses into JSON. MIFARE Classic tags are written as MAD/NDEF automatically by the phone OS or app.
**Example `ntag` values** (attribute is a JSON **string** containing an array):
```json
[{"tnf":"well_known","type":"U","uri":"https://example.com/room/kitchen"}]
```
```json
[{"tnf":"well_known","type":"T","language":"en","text":"Kitchen light"}]
```
Multiple records in one tag appear as several objects in the array.
### Keys
Keys appear on **screen 3** only when **Read raw blocks on MIFARE Classic** is enabled.
The integration reads **MIFARE Classic** blocks with **Key A**, trying keys **in the order** they appear in settings. It stops at the first key that works.
| Preset | Hex | When to enable |
|--------|-----|----------------|
| Factory default | `ffffffffffff` | **Most blank or retail tags.** Default choice; enable this first. |
| MAD key A | `a0a1a2a3a4a5` | Multi-application MAD sectors, some transport cards. |
| NDEF | `d3f7d3f7d3f7` | NTAG / NDEF-formatted tags (often used with MAD). |
| Null key | `000000000000` | Tags explicitly programmed with an all-zero key. |
| Custom | 12 hex chars | Your own key; appended **after** the checked presets. |
**Tips**
- Start with **factory default only** for fastest reads.
- Add more presets only if reads fail (`block_*` attributes are empty or missing while UID is present).
- Put the most likely key first (order in the UI = try order).
- Custom key is useful when you already know the sector key from `hf mf chk` / `hf mf autopwn` in the PM3 client.
Keys are **not** used for UID detection, Ultralight/NTAG NDEF, or MIFARE Classic NDEF (those paths use built-in MAD/NDEF keys).
### Raw blocks (MIFARE Classic, 0–3)
Enable **Read raw blocks on MIFARE Classic** on screen 1, then pick blocks on screen 2.
The integration reads **blocks 0–3** (sector 0 on MIFARE Classic 1K). Block reading is **optional** — leave the toggle off for fastest UID-only polling.
| Block | MIFARE Classic 1K (sector 0) | Typical use in automations |
|-------|--------------------------------|----------------------------|
| **0** | Manufacturer data, UID/BCC (partly read-only) | Read-only for most users; **do not write** without knowing the format |
| **1** | User data | **Good default** — often used for custom bytes, counters, magic markers |
| **2** | User data | Second user data block in sector 0 |
| **3** | Sector trailer (Key A / access bits / Key B) | Advanced; contains keys and ACLs, not arbitrary user data |
**How to use in Home Assistant**
- Write a pattern to block 1 on the tag (see PM3 examples below), e.g. `A1B2C3D4…`.
- Enable **block 1** (and the correct key) in integration options.
- Trigger automations on `sensor.proxmark3_nfc_tag` state (UID) and/or `block_1` attribute.
**Tag types**
| Tag type | Raw blocks 0–3 | NDEF |
|----------|----------------|------|
| MIFARE Classic 1K / 4K / Mini | Yes — with keys | Optional (**MIFARE Classic NDEF**, slow) |
| MIFARE Ultralight / NTAG | No (MIFARE Classic-only toggle) | Optional (**Read NDEF**, fast) |
| MIFARE DESFire, Java cards, random ISO14443-A | **No** | **No** |
Disable unused blocks to speed up polling (each block is a separate read).
**Block 0** on MIFARE Classic holds manufacturer bytes and UID-related data (often only part of the block is writable). Writing garbage there can corrupt the tag identity or make it unreadable. On Ultralight/NTAG, page 0 is similarly reserved (UID, lock bits). Treat block/page 0 as **read-only** unless you know exactly what you are changing.
**Block 3** on MIFARE Classic is the sector trailer (keys and access bits) — see warnings in the writing section below.
### Intervals
| Option | Default | Description |
|--------|---------|-------------|
| Poll interval | 0.05 s | Delay when idle (no tag / tag held). Lower = more responsive, more USB traffic. |
| Reconnect interval | 5 s | Delay between attempts when the device is unplugged or the port is missing. |
## Sensor
| State | Description |
|-------|-------------|
| UID hex string | Tag on the reader |
| `unknown` / empty | No tag present |
| unavailable | Proxmark3 disconnected (device info still cached on the device page) |
**Attributes:** `tag_type`, `block_0` … `block_3` (when raw MIFARE Classic blocks are enabled), `ntag` (JSON array of decoded NDEF records), plus cached `bootrom`, `compiler`, `fpga`, `memory` when known.
`ntag` is filled when **Read NDEF** and/or **Read NDEF on MIFARE Classic** matches the detected tag type.
Default entity id is usually `sensor.proxmark3_nfc_tag` (depends on your device name).
## Writing raw blocks (Proxmark3 client)
Use the Iceman **client** (`pm3` / `proxmark3`) when you need **raw hex bytes** in MIFARE Classic blocks — not for everyday NDEF (use a phone; see above). Stop Home Assistant polling or unload the integration while writing, so nothing else holds the serial port.
**Read block 1 (verify):**
```text
hf mf rdbl --blk 1 -k FFFFFFFFFFFF
```
**Write 16 bytes to block 1** (factory key, sector 0):
```text
hf mf wrbl --blk 1 -k FFFFFFFFFFFF -d 0102030405060708090a0b0c0d0e0f
```
**Write ASCII marker** (e.g. `HA` + padding to 16 bytes):
```text
hf mf wrbl --blk 1 -k FFFFFFFFFFFF -d 4841FFFFFFFFFFFFFFFFFFFFFFFFFFFF
```
**Ultralight / NTAG page 4** (user area often starts at page 4; pages 0–3 are reserved — integration still reads 0–3 if enabled):
```text
hf mf wrbl --blk 4 -d 0102030405060708090a0b0c0d0e0f
```
Do **not** write **block 0** or **block 3** on MIFARE Classic unless you understand the data layout. Block 0 carries manufacturer/UID-related data; block 3 is the sector trailer (keys and access conditions). A bad write to either can brick the tag or the whole sector. On Ultralight/NTAG, avoid writing pages 0–2 for the same reason — use page 4+ for user data, or prefer **NDEF via NFC Tools** instead of raw page writes.
## Example automations
Adjust `entity_id` / `notify.*` to match your setup.
### UID and MIFARE Classic block 1
Notify when a tag is placed on the reader (UID + block 1). Requires **Read raw blocks on MIFARE Classic** and block 1 enabled.
```yaml
alias: NFC Tag (UID + block)
description: ""
triggers:
- trigger: state
entity_id:
- sensor.proxmark3_nfc_tag
conditions:
- condition: not
conditions:
- condition: state
entity_id: sensor.proxmark3_nfc_tag
state:
- unknown
- unavailable
actions:
- action: notify.persistent_notification
data:
title: NFC Tag
message: >-
Detected NFC tag, ID={{ states('sensor.proxmark3_nfc_tag') }},
block_1={{ state_attr('sensor.proxmark3_nfc_tag', 'block_1') }}
mode: single
```
To react only to a specific payload in block 1, add a template condition on `state_attr('sensor.proxmark3_nfc_tag', 'block_1')`.
### NDEF (URL or text from `ntag` JSON)
Requires **Read NDEF** and/or **Read NDEF on MIFARE Classic**. The `ntag` attribute is a JSON string; parse it with the `from_json` filter.
**Open a URL from the first NDEF record** (e.g. tag written with NFC Tools → URL record):
```yaml
alias: NFC Tag NDEF URL
description: ""
triggers:
- trigger: state
entity_id: sensor.proxmark3_nfc_tag
attribute: ntag
conditions:
- condition: template
value_template: >-
{% set raw = state_attr('sensor.proxmark3_nfc_tag', 'ntag') %}
{{ raw not in [none, ''] }}
actions:
- variables:
ndef_uri: >-
{% set records = state_attr('sensor.proxmark3_nfc_tag', 'ntag') | from_json %}
{{ records[0].uri | default('') }}
- condition: template
value_template: "{{ ndef_uri != '' }}"
- action: notify.persistent_notification
data:
title: NFC tag URL
message: "NDEF link: {{ ndef_uri }}"
mode: single
```
**Run different actions by NDEF text** (e.g. tag with text `Kitchen` / `Bedroom` from NFC Tools):
```yaml
alias: NFC Tag NDEF text room
description: ""
triggers:
- trigger: state
entity_id: sensor.proxmark3_nfc_tag
attribute: ntag
conditions:
- condition: template
value_template: >-
{% set raw = state_attr('sensor.proxmark3_nfc_tag', 'ntag') %}
{% if raw in [none, ''] %}
false
{% else %}
{% set records = raw | from_json %}
{{ records | selectattr('text', 'defined') | list | length > 0 }}
{% endif %}
actions:
- choose:
- conditions:
- condition: template
value_template: >-
{% set records = state_attr('sensor.proxmark3_nfc_tag', 'ntag') | from_json %}
{{ records[0].text | default('') == 'Kitchen' }}
sequence:
- action: light.turn_on
target:
entity_id: light.kitchen
- conditions:
- condition: template
value_template: >-
{% set records = state_attr('sensor.proxmark3_nfc_tag', 'ntag') | from_json %}
{{ records[0].text | default('') == 'Bedroom' }}
sequence:
- action: light.turn_on
target:
entity_id: light.bedroom
mode: single
```
**Reusable template** — first URL or text from any record:
```jinja2
{% set raw = state_attr('sensor.proxmark3_nfc_tag', 'ntag') %}
{% if raw in [none, ''] %}
none
{% else %}
{% set records = raw | from_json %}
{{ records[0].uri | default(records[0].text | default(none)) }}
{% endif %}
```
## License
GPLv3 License
## Support the Developer and the Project
* [GitHub Sponsors](https://github.com/sponsors/ClusterM)
* [Patreon](https://www.patreon.com/c/ClusterMeerkat)
* [Buy Me A Coffee](https://www.buymeacoffee.com/cluster)
* [Sber](https://messenger.online.sberbank.ru/sl/Lnb2OLE4JsyiEhQgC)
* [Donation Alerts](https://www.donationalerts.com/r/clustermeerkat)
* [Boosty](https://boosty.to/cluster)