Add PAC/Stanley T55XX write support

Add pac_t55xx_writer() for encoding PAC card data into T55XX blocks,
along with the T5577_PAC_CONFIG (NRZ/Direct, RF/32, password-protected,
4 data blocks). Wire DATA_CMD_PAC_WRITE_TO_T55XX (3011) through the
command processor, dispatch table, and Python client.
This commit is contained in:
Kevin Yuan
2026-03-24 14:38:46 +00:00
parent 17ff2abf60
commit 8442bea4c1
9 changed files with 69 additions and 2 deletions
+15
View File
@@ -826,6 +826,20 @@ static data_frame_tx_t *cmd_processor_viking_write_to_t55xx(uint16_t cmd, uint16
return data_frame_make(cmd, status, 0, NULL);
}
static data_frame_tx_t *cmd_processor_pac_write_to_t55xx(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
typedef struct {
uint8_t id[LF_PAC_TAG_ID_SIZE];
uint8_t new_key[4];
uint8_t old_keys[4];
} PACKED payload_t;
payload_t *payload = (payload_t *)data;
if (length < sizeof(payload_t) || (length - offsetof(payload_t, old_keys)) % sizeof(payload->old_keys) != 0) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
status = write_pac_to_t55xx(payload->id, payload->new_key, payload->old_keys, (length - offsetof(payload_t, old_keys)) / sizeof(payload->old_keys));
return data_frame_make(cmd, status, 0, NULL);
}
#define GENERIC_READ_LEN 800
#define GENERIC_READ_TIMEOUT_MS 500
static data_frame_tx_t *cmd_processor_generic_read(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
@@ -1828,6 +1842,7 @@ static cmd_data_map_t m_data_cmd_map[] = {
{ DATA_CMD_IOPROX_SCAN, before_reader_run, cmd_processor_ioprox_scan, NULL },
{ DATA_CMD_IOPROX_WRITE_TO_T55XX, before_reader_run, cmd_processor_ioprox_write_to_t55xx, NULL },
{ DATA_CMD_PAC_SCAN, before_reader_run, cmd_processor_pac_scan, NULL },
{ DATA_CMD_PAC_WRITE_TO_T55XX, before_reader_run, cmd_processor_pac_write_to_t55xx, NULL },
{ DATA_CMD_ADC_GENERIC_READ, before_reader_run, cmd_processor_generic_read, NULL },
{ DATA_CMD_HF14A_SET_FIELD_ON, before_reader_run, cmd_processor_hf14a_set_field_on, NULL },
+1
View File
@@ -94,6 +94,7 @@
#define DATA_CMD_HIDPROX_SCAN (3002)
#define DATA_CMD_HIDPROX_WRITE_TO_T55XX (3003)
#define DATA_CMD_PAC_SCAN (3010)
#define DATA_CMD_PAC_WRITE_TO_T55XX (3011)
#define DATA_CMD_VIKING_SCAN (3004)
#define DATA_CMD_VIKING_WRITE_TO_T55XX (3005)
#define DATA_CMD_ADC_GENERIC_READ (3009)
@@ -6,6 +6,7 @@
#include "nordic_common.h"
#include "nrf_pwm.h"
#include "protocols.h"
#include "t55xx.h"
#include "tag_base_type.h"
#define PAC_DATA_SIZE 8 // 8-byte ASCII card ID
@@ -333,12 +334,29 @@ static const nrf_pwm_sequence_t *pac_modulator(pac_codec *d, uint8_t *buf) {
// Per nRF52840 PS: compare = 0 → pin held LOW; compare >= counter_top → pin held HIGH.
// No polarity bits needed — avoids edge-case ambiguity with compare = 0.
for (int i = 0; i < PAC_FRAME_BITS; i++) {
m_pac_pwm_seq_vals[i].channel_0 = bits[i] ? 32 : 0;
m_pac_pwm_seq_vals[i].counter_top = 32;
m_pac_pwm_seq_vals[i].channel_0 = bits[i] ? PAC_RF_PER_BIT : 0;
m_pac_pwm_seq_vals[i].counter_top = PAC_RF_PER_BIT;
}
return &m_pac_pwm_seq;
}
#define PAC_T55XX_BLOCK_COUNT 5 // 1 config + 4 data blocks
uint8_t pac_t55xx_writer(uint8_t *data, uint32_t *blks) {
uint8_t bits[PAC_FRAME_BITS];
pac_build_bitstream(data, bits);
blks[0] = T5577_PAC_CONFIG;
for (int b = 0; b < 4; b++) {
uint32_t word = 0;
for (int i = 0; i < 32; i++) {
word = (word << 1) | bits[b * 32 + i];
}
blks[b + 1] = word;
}
return PAC_T55XX_BLOCK_COUNT;
}
const protocol pac = {
.tag_type = TAG_TYPE_PAC,
.data_size = PAC_DATA_SIZE,
@@ -3,3 +3,4 @@
#include "protocols.h"
extern const protocol pac;
uint8_t pac_t55xx_writer(uint8_t *data, uint32_t *blks);
@@ -75,6 +75,12 @@ extern "C" {
T5577_PWD | \
(2 << T5577_MAXBLOCK_SHIFT))
#define T5577_PAC_CONFIG ( \
T5577_MODULATION_DIRECT | \
T5577_BITRATE_RF_32 | \
T5577_PWD | \
(4 << T5577_MAXBLOCK_SHIFT))
void t55xx_write_data(uint32_t passwd, uint32_t *blks, uint8_t blk_count);
void t55xx_reset_passwd(uint32_t old_passwd, uint32_t new_passwd);
@@ -198,6 +198,13 @@ uint8_t write_viking_to_t55xx(uint8_t *uid, uint8_t *new_passwd, uint8_t *old_pa
return write_t55xx(blks, blk_count, new_passwd, old_passwds, old_passwd_count);
}
uint8_t write_pac_to_t55xx(uint8_t *data, uint8_t *new_passwd, uint8_t *old_passwds, uint8_t old_passwd_count) {
uint32_t blks[7] = {0x00};
uint8_t blk_count = pac_t55xx_writer(data, blks);
if (blk_count == 0) return STATUS_PAR_ERR;
return write_t55xx(blks, blk_count, new_passwd, old_passwds, old_passwd_count);
}
/**
* Set the LF card scanning timeout value (in milliseconds).
*/
@@ -20,3 +20,4 @@ uint8_t write_em410x_electra_to_t55xx(uint8_t *uid, uint8_t *newkey, uint8_t *ol
uint8_t write_hidprox_to_t55xx(uint8_t format, uint32_t fc, uint64_t cn, uint32_t il, uint32_t oem, uint8_t *new_passwd, uint8_t *old_passwds, uint8_t old_passwd_count);
uint8_t write_ioprox_to_t55xx(uint8_t *raw_data, uint8_t *new_passwd, uint8_t *old_passwds, uint8_t old_passwd_count);
uint8_t write_viking_to_t55xx(uint8_t *uid, uint8_t *newkey, uint8_t *old_keys, uint8_t old_key_count);
uint8_t write_pac_to_t55xx(uint8_t *data, uint8_t *new_passwd, uint8_t *old_passwds, uint8_t old_passwd_count);
+17
View File
@@ -592,6 +592,23 @@ class ChameleonCMD:
resp.parsed = resp.data[:8]
return resp
@expect_response(Status.LF_TAG_OK)
def pac_write_to_t55xx(self, id_bytes: bytes, new_key: bytes = b'\x00\x00\x00\x00', old_keys: list = None):
"""
Write PAC/Stanley card data to a T55XX tag.
:param id_bytes: 8-byte ASCII card ID
:param new_key: new password (4 bytes)
:param old_keys: list of old passwords to try (each 4 bytes)
:return:
"""
if old_keys is None:
old_keys = [b'\x00\x00\x00\x00']
if len(id_bytes) != 8:
raise ValueError("The id bytes length must equal 8")
data = struct.pack(f'!8s4s{4*len(old_keys)}s', id_bytes, new_key, b''.join(old_keys))
return self.device.send_cmd_sync(Command.PAC_WRITE_TO_T55XX, data)
@expect_response(Status.LF_TAG_OK)
def adc_generic_read(self):
"""
+1
View File
@@ -84,6 +84,7 @@ class Command(enum.IntEnum):
VIKING_SCAN = 3004
VIKING_WRITE_TO_T55XX = 3005
PAC_SCAN = 3010
PAC_WRITE_TO_T55XX = 3011
ADC_GENERIC_READ = 3009
IOPROX_SCAN = 3010
IOPROX_WRITE_TO_T55XX = 3011