Add PAC/Stanley LF tag reading support

Implements NRZ/Direct modulation decoder for PAC/Stanley 125kHz cards
using SAADC ADC sampling with spike-aware threshold calibration.
The LC antenna produces brief high-amplitude transients at NRZ transitions
which are clipped before the moving-average filter to isolate the actual
data levels.
This commit is contained in:
Kevin Yuan
2026-03-24 14:37:25 +00:00
parent e5d615d512
commit c494a2cc81
13 changed files with 521 additions and 1 deletions
+2
View File
@@ -37,6 +37,7 @@ SRC_FILES += \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/em410x.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/ioprox.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/hidprox.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/pac.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/viking.c \
$(PROJ_DIR)/rfid/nfctag/lf/protocols/wiegand.c \
$(PROJ_DIR)/utils/dataframe.c \
@@ -346,6 +347,7 @@ ifeq (${CURRENT_DEVICE_TYPE}, ${CHAMELEON_ULTRA})
$(PROJ_DIR)/rfid/reader/lf/lf_t55xx_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_ioprox_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_hidprox_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_pac_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_viking_data.c \
$(PROJ_DIR)/rfid/reader/lf/lf_reader_generic.c \
+10
View File
@@ -802,6 +802,15 @@ static data_frame_tx_t *cmd_processor_viking_scan(uint16_t cmd, uint16_t status,
return data_frame_make(cmd, STATUS_LF_TAG_OK, sizeof(card_buffer), card_buffer);
}
static data_frame_tx_t *cmd_processor_pac_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
uint8_t card_id[8] = {0x00};
status = scan_pac(card_id);
if (status != STATUS_LF_TAG_OK) {
return data_frame_make(cmd, status, 0, NULL);
}
return data_frame_make(cmd, STATUS_LF_TAG_OK, sizeof(card_id), card_id);
}
static data_frame_tx_t *cmd_processor_viking_write_to_t55xx(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
typedef struct {
uint8_t id[4];
@@ -1798,6 +1807,7 @@ static cmd_data_map_t m_data_cmd_map[] = {
{ DATA_CMD_VIKING_WRITE_TO_T55XX, before_reader_run, cmd_processor_viking_write_to_t55xx, NULL },
{ 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_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
@@ -93,6 +93,7 @@
#define DATA_CMD_EM410X_ELECTRA_WRITE_TO_T55XX (3006)
#define DATA_CMD_HIDPROX_SCAN (3002)
#define DATA_CMD_HIDPROX_WRITE_TO_T55XX (3003)
#define DATA_CMD_PAC_SCAN (3010)
#define DATA_CMD_VIKING_SCAN (3004)
#define DATA_CMD_VIKING_WRITE_TO_T55XX (3005)
#define DATA_CMD_ADC_GENERIC_READ (3009)
@@ -0,0 +1,297 @@
#include "pac.h"
#include <stdlib.h>
#include <string.h>
#include "protocols.h"
#include "tag_base_type.h"
#define PAC_DATA_SIZE 8 // 8-byte ASCII card ID
// NRZ at RF/32: 32 carrier cycles per bit.
// With SAADC sampling at 1 sample per carrier cycle, 32 samples = 1 bit.
#define PAC_RF_PER_BIT 32
#define PAC_HALF_BIT 16 // Half-bit for rounding interval → nbits
#define PAC_MAX_BITS_RUN 20 // Max consecutive same-polarity bits we accept
// PAC frame is exactly 128 bits on T55xx (4 blocks × 32 bits):
// 8-bit sync marker (0xFF) + 12 × 10-bit UART frames = 128 bits
#define PAC_FRAME_BITS 128
#define PAC_PREAMBLE_BITS 19
// Preamble: 1111111100100000010 (19 bits) = 0x7F902
#define PAC_PREAMBLE 0x7F902UL
#define PAC_PREAMBLE_INV 0x006FDUL // Bitwise inverse masked to 19 bits
#define PAC_UART_FRAME_BITS 10
#define PAC_PAYLOAD_BYTES 12 // STX + '2' + '0' + 8 card ID + XOR checksum
#define PAC_STX 0x02
// ADC demodulation parameters
#define PAC_AVG_WINDOW 32 // Moving average window = 1 NRZ bit period
#define PAC_PRESCAN_SAMPLES 128 // Samples for raw min/max detection (spike cap)
#define PAC_WARMUP_SAMPLES 600 // Samples for threshold calibration (~5ms)
#define PAC_MIN_HYST_SUM (10 * PAC_AVG_WINDOW) // Min hysteresis in sum units
#define PAC_SPIKE_MULT 3 // Samples > raw_min * SPIKE_MULT are spike transients
typedef struct {
// NRZ shift register (128 bits)
uint64_t raw_hi; // upper 64 bits
uint64_t raw_lo; // lower 64 bits
bool polarity; // current NRZ level (toggled on each edge)
uint16_t bit_count; // total bits shifted in (capped at PAC_FRAME_BITS)
uint8_t card_id[PAC_DATA_SIZE];
// ADC → NRZ demodulation state (moving-average based)
int16_t avg_buf[PAC_AVG_WINDOW]; // circular buffer for moving average
int32_t avg_sum; // running sum of last PAC_AVG_WINDOW samples
uint8_t avg_idx; // circular index into avg_buf
uint16_t total_samples; // total samples processed
int16_t raw_min; // minimum raw sample seen (for spike detection)
int16_t spike_cap; // clip level: raw values above this are transients
int32_t avg_max; // max of avg_sum during warmup (spike-free)
int32_t avg_min; // min of avg_sum during warmup (spike-free)
int32_t threshold; // center threshold (in sum units, not divided)
int32_t hysteresis; // hysteresis margin (in sum units)
bool adc_state; // current demodulated binary level
bool has_signal; // true after first threshold crossing
uint16_t sample_count; // samples since last transition
} pac_codec;
// Shift one bit into the 128-bit register.
static void shift_bit(pac_codec *d, bool bit) {
d->raw_hi = (d->raw_hi << 1) | (d->raw_lo >> 63);
d->raw_lo = (d->raw_lo << 1) | (bit ? 1 : 0);
}
// Extract a single bit from the 128-bit register.
// Position 0 = MSB of raw_hi (oldest), position 127 = LSB of raw_lo (newest).
static bool get_bit(pac_codec *d, uint16_t pos) {
if (pos < 64) {
return (d->raw_hi >> (63 - pos)) & 1;
}
return (d->raw_lo >> (127 - pos)) & 1;
}
// Decode a 10-bit UART frame at bit position 'start'.
// Frame: start(0) + 7 data bits LSB-first + odd parity + stop(1).
static int decode_uart_byte(pac_codec *d, uint16_t start, bool inverted) {
#define RD(pos) (inverted ? !get_bit(d, (pos)) : get_bit(d, (pos)))
if (RD(start)) {
return -1;
}
uint8_t byte_val = 0;
uint8_t ones = 0;
for (int i = 0; i < 7; i++) {
if (RD(start + 1 + i)) {
byte_val |= (1 << i);
ones++;
}
}
if (RD(start + 8)) {
ones++;
}
if ((ones & 1) == 0) {
return -1;
}
if (!RD(start + 9)) {
return -1;
}
#undef RD
return byte_val;
}
// Check if the 128-bit register contains a valid PAC frame.
static bool try_decode_frame(pac_codec *d, bool inverted) {
uint32_t preamble = 0;
for (int i = 0; i < PAC_PREAMBLE_BITS; i++) {
preamble = (preamble << 1) | (get_bit(d, i) ? 1 : 0);
}
uint32_t expected = inverted ? PAC_PREAMBLE_INV : PAC_PREAMBLE;
if (preamble != expected) {
return false;
}
uint8_t decoded[PAC_PAYLOAD_BYTES];
for (int i = 0; i < PAC_PAYLOAD_BYTES; i++) {
uint16_t frame_start = 8 + i * PAC_UART_FRAME_BITS;
int val = decode_uart_byte(d, frame_start, inverted);
if (val < 0) {
return false;
}
decoded[i] = (uint8_t)val;
}
if (decoded[0] != PAC_STX) {
return false;
}
uint8_t xor_check = 0;
for (int i = 3; i < 3 + PAC_DATA_SIZE; i++) {
xor_check ^= decoded[i];
}
if (xor_check != decoded[11]) {
return false;
}
memcpy(d->card_id, &decoded[3], PAC_DATA_SIZE);
return true;
}
// Process a demodulated NRZ edge interval (in samples = carrier cycles).
static bool pac_process_interval(pac_codec *d, uint16_t interval) {
uint16_t nbits = (interval + PAC_HALF_BIT) / PAC_RF_PER_BIT;
if (nbits < 1 || nbits > PAC_MAX_BITS_RUN) {
d->raw_hi = 0;
d->raw_lo = 0;
d->polarity = false;
d->bit_count = 0;
return false;
}
for (uint16_t i = 0; i < nbits; i++) {
shift_bit(d, d->polarity);
if (d->bit_count < PAC_FRAME_BITS) {
d->bit_count++;
}
if (d->bit_count >= PAC_FRAME_BITS) {
if (try_decode_frame(d, false) || try_decode_frame(d, true)) {
return true;
}
}
}
d->polarity = !d->polarity;
return false;
}
static pac_codec *pac_alloc(void) {
pac_codec *codec = malloc(sizeof(pac_codec));
return codec;
}
static void pac_free(pac_codec *d) {
free(d);
}
static uint8_t *pac_get_data(pac_codec *d) {
return d->card_id;
}
static void pac_decoder_start(pac_codec *d, uint8_t format) {
memset(d, 0, sizeof(pac_codec));
// Initialize min/max to extremes so first comparison updates them
d->raw_min = 32767; // INT16_MAX for raw min tracking
d->spike_cap = 32767; // No capping until prescan completes
d->avg_max = -1048576;
d->avg_min = 1048576;
}
// Feed a raw ADC sample (one per carrier cycle at 125kHz).
// The antenna signal has brief 0xFF transient spikes at NRZ transitions due to
// LC circuit ringing. The actual NRZ data is encoded as two lower amplitude
// levels. We clip spikes before averaging, then threshold the clean signal.
static bool pac_decoder_feed(pac_codec *d, uint16_t raw_sample) {
int16_t sample = (int16_t)raw_sample;
d->total_samples++;
// Phase 1: Prescan — track raw minimum to characterize data levels.
// The NRZ data levels are the lowest values; spikes are 5-15x higher.
if (d->total_samples <= PAC_PRESCAN_SAMPLES) {
if (sample < d->raw_min && sample > 0) {
d->raw_min = sample;
}
if (d->total_samples == PAC_PRESCAN_SAMPLES) {
// Set spike cap: anything above 3x the minimum is a transient.
// This keeps both NRZ data levels but removes the LC ringing spikes.
d->spike_cap = d->raw_min * PAC_SPIKE_MULT;
}
return false;
}
// Clip spikes: replace transient values with the cap level.
// This prevents the LC ringing spikes from dominating the moving average.
if (sample > d->spike_cap) {
sample = d->spike_cap;
}
// Update 32-sample moving average (window = 1 NRZ bit period at RF/32).
d->avg_sum -= d->avg_buf[d->avg_idx];
d->avg_buf[d->avg_idx] = sample;
d->avg_sum += sample;
d->avg_idx = (d->avg_idx + 1) % PAC_AVG_WINDOW;
uint16_t avg_samples = d->total_samples - PAC_PRESCAN_SAMPLES;
// Need a full window before the average is valid
if (avg_samples < PAC_AVG_WINDOW) {
return false;
}
// Warmup: track min/max of the spike-free averaged signal.
if (avg_samples < PAC_WARMUP_SAMPLES) {
if (d->avg_sum > d->avg_max) d->avg_max = d->avg_sum;
if (d->avg_sum < d->avg_min) d->avg_min = d->avg_sum;
return false;
}
// Compute threshold once at end of warmup
if (avg_samples == PAC_WARMUP_SAMPLES) {
d->threshold = (d->avg_max + d->avg_min) / 2;
d->hysteresis = (d->avg_max - d->avg_min) / 4;
if (d->hysteresis < PAC_MIN_HYST_SUM) {
d->hysteresis = PAC_MIN_HYST_SUM;
}
}
// Detection: threshold the averaged signal with hysteresis
d->sample_count++;
bool new_state;
if (d->avg_sum > d->threshold + d->hysteresis) {
new_state = true;
} else if (d->avg_sum < d->threshold - d->hysteresis) {
new_state = false;
} else {
return false;
}
if (!d->has_signal) {
d->has_signal = true;
d->adc_state = new_state;
d->sample_count = 0;
return false;
}
if (new_state == d->adc_state) {
return false;
}
// Transition detected — process the interval
uint16_t interval = d->sample_count;
d->sample_count = 0;
d->adc_state = new_state;
return pac_process_interval(d, interval);
}
const protocol pac = {
.tag_type = TAG_TYPE_PAC,
.data_size = PAC_DATA_SIZE,
.alloc = (codec_alloc)pac_alloc,
.free = (codec_free)pac_free,
.get_data = (codec_get_data)pac_get_data,
.modulator = NULL,
.decoder =
{
.start = (decoder_start)pac_decoder_start,
.feed = (decoder_feed)pac_decoder_feed,
},
};
@@ -0,0 +1,5 @@
#pragma once
#include "protocols.h"
extern const protocol pac;
@@ -43,6 +43,7 @@ typedef enum {
// securakey
// gallagher
// PAC/Stanley
TAG_TYPE_PAC = 150,
// Presco
// Visa2000
// Viking
@@ -107,7 +108,7 @@ typedef enum {
}
#define TAG_SPECIFIC_TYPE_LF_VALUES \
TAG_TYPE_EM410X, TAG_TYPE_EM410X_ELECTRA, TAG_TYPE_HID_PROX, TAG_TYPE_IOPROX, TAG_TYPE_VIKING
TAG_TYPE_EM410X, TAG_TYPE_EM410X_ELECTRA, TAG_TYPE_PAC, TAG_TYPE_HID_PROX, TAG_TYPE_IOPROX, TAG_TYPE_VIKING
#define TAG_SPECIFIC_TYPE_HF_VALUES \
TAG_TYPE_MIFARE_Mini, TAG_TYPE_MIFARE_1024, TAG_TYPE_MIFARE_2048, \
@@ -0,0 +1,70 @@
#include <string.h>
#include "bsp_delay.h"
#include "bsp_time.h"
#include "circular_buffer.h"
#include "lf_125khz_radio.h"
#include "lf_reader_data.h"
#include "nrfx_saadc.h"
#include "protocols/pac.h"
#include "protocols/protocols.h"
#define NRF_LOG_MODULE_NAME pac_reader
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
NRF_LOG_MODULE_REGISTER();
#define PAC_BUFFER_SIZE (6144)
static circular_buffer cb;
// SAADC callback — push raw ADC samples to circular buffer.
// NRZ/Direct modulation requires ADC sampling (not GPIOTE edge timing)
// because the comparator may not produce clean digital edges for NRZ signals.
static void pac_saadc_cb(nrf_saadc_value_t *vals, size_t size) {
for (size_t i = 0; i < size; i++) {
nrf_saadc_value_t val = vals[i];
if (!cb_push_back(&cb, &val)) {
return;
}
}
}
static void init_pac_hw(void) {
lf_125khz_radio_saadc_enable(pac_saadc_cb);
}
static void uninit_pac_hw(void) {
lf_125khz_radio_saadc_disable();
}
bool pac_read(uint8_t *data, uint32_t timeout_ms) {
void *codec = pac.alloc();
pac.decoder.start(codec, 0);
cb_init(&cb, PAC_BUFFER_SIZE, sizeof(uint16_t));
init_pac_hw();
start_lf_125khz_radio();
bool ok = false;
autotimer *p_at = bsp_obtain_timer(0);
while (!ok && NO_TIMEOUT_1MS(p_at, timeout_ms)) {
uint16_t val = 0;
while (!ok && NO_TIMEOUT_1MS(p_at, timeout_ms) && cb_pop_front(&cb, &val)) {
if (pac.decoder.feed(codec, val)) {
memcpy(data, pac.get_data(codec), pac.data_size);
ok = true;
break;
}
}
}
bsp_return_timer(p_at);
stop_lf_125khz_radio();
uninit_pac_hw();
cb_free(&cb);
pac.free(codec);
return ok;
}
@@ -21,6 +21,7 @@ void clear_lf_counter_value(void);
bool em410x_read(uint8_t *data, uint32_t timeout_ms);
bool ioprox_read(uint8_t *data, uint8_t format_hint, uint32_t timeout_ms);
bool hidprox_read(uint8_t *data, uint8_t format_hint, uint32_t timeout_ms);
bool pac_read(uint8_t *data, uint32_t timeout_ms);
bool viking_read(uint8_t *data, uint32_t timeout_ms);
bool raw_read_to_buffer(uint8_t *data, size_t maxlen, uint32_t timeout_ms, size_t *outlen);
@@ -9,6 +9,7 @@
#include "protocols/ioprox.h"
#include "protocols/hidprox.h"
#include "protocols/t55xx.h"
#include "protocols/pac.h"
#include "protocols/viking.h"
#define NRF_LOG_MODULE_NAME lf_main
@@ -80,6 +81,16 @@ uint8_t encode_ioprox_params(uint8_t ver, uint8_t fc, uint16_t cn, uint8_t *out)
return STATUS_CMD_ERR;
}
/**
* Search PAC/Stanley tag
*/
uint8_t scan_pac(uint8_t *card_id) {
if (pac_read(card_id, g_timeout_readem_ms)) {
return STATUS_LF_TAG_OK;
}
return STATUS_LF_TAG_NO_FOUND;
}
/**
* Search Viking tag
*/
@@ -13,6 +13,7 @@ uint8_t scan_ioprox(uint8_t *uid, uint8_t format_hint);
uint8_t decode_ioprox_raw(uint8_t *raw8, uint8_t *output);
uint8_t encode_ioprox_params(uint8_t ver, uint8_t fc, uint16_t cn, uint8_t *out);
uint8_t scan_hidprox(uint8_t *uid, uint8_t format_hint);
uint8_t scan_pac(uint8_t *card_id);
uint8_t scan_viking(uint8_t *uid);
uint8_t write_em410x_to_t55xx(uint8_t *uid, uint8_t *newkey, uint8_t *old_keys, uint8_t old_key_count);
uint8_t write_em410x_electra_to_t55xx(uint8_t *uid, uint8_t *newkey, uint8_t *old_keys, uint8_t old_key_count);
+105
View File
@@ -757,6 +757,7 @@ lf_em_410x = lf_em.subgroup("410x", "EM410x commands")
lf_hid = lf.subgroup("hid", "HID commands")
lf_hid_prox = lf_hid.subgroup("prox", "HID Prox commands")
lf_ioprox = lf.subgroup("ioprox", "ioProx commands")
lf_pac = lf.subgroup("pac", "PAC/Stanley commands")
lf_viking = lf.subgroup("viking", "Viking commands")
lf_generic = lf.subgroup("generic", "Generic commands")
@@ -5763,6 +5764,110 @@ class LFIOProxEconfig(SlotIndexArgsAndGoUnit, LFIOProxIdArgsUnit):
print(f" ID: {color_string((CY, cn))}")
print(f" Raw: {color_string((CY, raw8.hex().upper()))}")
@lf_pac.command('read')
class LFPacRead(ReaderRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit:
parser = ArgumentParserNoExit()
parser.description = 'Scan PAC/Stanley tag and print card ID'
return parser
def on_exec(self, args: argparse.Namespace):
card_id = self.cmd.pac_scan()
card_id_ascii = ''.join(chr(b) if 0x20 <= b < 0x7f else '.' for b in card_id)
print(f" PAC/Stanley Card ID: {color_string((CG, card_id_ascii))} ({card_id.hex().upper()})")
@lf_pac.command('debug')
class LFPacDebug(ReaderRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit:
parser = ArgumentParserNoExit()
parser.description = 'Capture raw ADC data and analyze NRZ signal for PAC debugging'
return parser
def on_exec(self, args: argparse.Namespace):
resp = self.cmd.adc_generic_read()
if resp is None:
print("ADC read failed")
return
data = list(resp)
min_val = min(data)
max_val = max(data)
print(f" Samples: {len(data)}")
print(f" ADC range: {min_val} - {max_val} (spread: {max_val - min_val})")
# Spike removal: clip values above 3x the minimum (LC ringing transients)
spike_cap = min_val * 3 if min_val > 0 else max_val
clipped = [min(v, spike_cap) for v in data]
c_min = min(clipped)
c_max = max(clipped)
c_thresh = (c_min + c_max) / 2
print(f" Spike cap: {spike_cap} (3x raw min {min_val})")
print(f" Clipped range: {c_min} - {c_max} (spread: {c_max - c_min})")
# Moving-average of clipped signal (32-sample window = 1 NRZ bit)
window = 32
if len(clipped) >= window:
filtered = []
s = sum(clipped[:window])
for i in range(window, len(clipped)):
filtered.append(s / window)
s += clipped[i] - clipped[i - window]
filtered.append(s / window)
f_min = min(filtered)
f_max = max(filtered)
f_thresh = (f_min + f_max) / 2
f_hyst = (f_max - f_min) / 4
print(f"\n Spike-free moving average (window={window}):")
print(f" Range: {f_min:.1f} - {f_max:.1f} (spread: {f_max - f_min:.1f})")
print(f" Threshold: {f_thresh:.1f}, Hysteresis: {f_hyst:.1f}")
# Binary decode with hysteresis
f_binary = []
state = filtered[0] > f_thresh
for v in filtered:
if v > f_thresh + f_hyst:
state = True
elif v < f_thresh - f_hyst:
state = False
f_binary.append(1 if state else 0)
f_trans = sum(1 for i in range(1, len(f_binary)) if f_binary[i] != f_binary[i - 1])
f_runs = []
cur = f_binary[0]
cnt = 1
for i in range(1, len(f_binary)):
if f_binary[i] == cur:
cnt += 1
else:
f_runs.append(cnt)
cur = f_binary[i]
cnt = 1
f_runs.append(cnt)
print(f" Transitions: {f_trans}")
print(f" Run lengths: {f_runs[:40]}{'...' if len(f_runs) > 40 else ''}")
run_bits = [f"{r / 32:.1f}" for r in f_runs[:40]]
print(f" Run bits: {run_bits}")
# Show bit-period averages (each = avg of 32 clipped samples)
print(f"\n Bit-period averages (32 samples each):")
bit_avgs = []
for i in range(0, len(clipped) - window + 1, window):
chunk = clipped[i:i + window]
bit_avgs.append(sum(chunk) / len(chunk))
bit_str = " ".join(f"{a:.0f}" for a in bit_avgs)
print(f" {bit_str}")
# Raw hex dump (first 512 bytes)
print(f"\n Raw ADC (8-bit, min={min_val} max={max_val}):")
for i in range(0, min(len(data), 512), 50):
chunk = data[i:i + 50]
hexpart = " ".join(f"{b:02x}" for b in chunk)
print(f" {i:04x} {hexpart}")
@lf_viking.command("read")
class LFVikingRead(ReaderRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit:
+12
View File
@@ -580,6 +580,18 @@ class ChameleonCMD:
data = struct.pack(f'!4s4s{4*len(old_keys)}s', id_bytes, new_key, b''.join(old_keys))
return self.device.send_cmd_sync(Command.VIKING_WRITE_TO_T55XX, data)
@expect_response(Status.LF_TAG_OK)
def pac_scan(self):
"""
Read the card ID of PAC/Stanley.
:return:
"""
resp = self.device.send_cmd_sync(Command.PAC_SCAN)
if resp.status == Status.LF_TAG_OK:
resp.parsed = resp.data[:8]
return resp
@expect_response(Status.LF_TAG_OK)
def adc_generic_read(self):
"""
+4
View File
@@ -83,6 +83,7 @@ class Command(enum.IntEnum):
HIDPROX_WRITE_TO_T55XX = 3003
VIKING_SCAN = 3004
VIKING_WRITE_TO_T55XX = 3005
PAC_SCAN = 3010
ADC_GENERIC_READ = 3009
IOPROX_SCAN = 3010
IOPROX_WRITE_TO_T55XX = 3011
@@ -276,6 +277,7 @@ class TagSpecificType(enum.IntEnum):
# securakey
# gallagher
# PAC/Stanley
PAC = 150
# Presco
# Visa2000
Viking = 170
@@ -369,6 +371,8 @@ class TagSpecificType(enum.IntEnum):
return "HIDProx"
elif self == TagSpecificType.ioProx:
return "ioProx"
elif self == TagSpecificType.PAC:
return "PAC/Stanley"
elif self == TagSpecificType.Viking:
return "Viking"
elif self == TagSpecificType.MIFARE_Mini: