@@ -754,6 +754,7 @@ def on_exec(self, args: argparse.Namespace):
754754lf = root .subgroup ("lf" , "Low Frequency commands" )
755755lf_em = lf .subgroup ("em" , "EM commands" )
756756lf_em_4x05 = lf_em .subgroup ("4x05" , "EM4x05/EM4x69 commands" )
757+
757758lf_em_410x = lf_em .subgroup ("410x" , "EM410x commands" )
758759lf_hid = lf .subgroup ("hid" , "HID commands" )
759760lf_hid_prox = lf_hid .subgroup ("prox" , "HID Prox commands" )
@@ -6767,3 +6768,98 @@ def on_exec(self, args: argparse.Namespace):
67676768 print (f" UID : { CG } { uid :08x} { C0 } " )
67686769
67696770
6771+ @lf .command ('sniff' )
6772+ class LFSniff (ReaderRequiredUnit ):
6773+ def args_parser (self ) -> ArgumentParserNoExit :
6774+ parser = ArgumentParserNoExit ()
6775+ parser .description = (
6776+ "Capture raw LF field ADC samples (125kHz, 8µs/sample). "
6777+ "~0x80 = field on, lower values = gap or no field."
6778+ )
6779+ parser .add_argument (
6780+ '--timeout' , type = int , default = 2000 , metavar = 'MS' ,
6781+ help = 'Capture duration in milliseconds (default: 2000, max: 10000, firmware blocks for full duration)'
6782+ )
6783+ parser .add_argument (
6784+ '--out' , type = str , default = None , metavar = 'FILE' ,
6785+ help = 'Save raw samples to binary file (for offline analysis)'
6786+ )
6787+ parser .add_argument (
6788+ '--hex' , action = 'store_true' ,
6789+ help = 'Print hex dump of samples to screen'
6790+ )
6791+ return parser
6792+
6793+ def on_exec (self , args : argparse .Namespace ):
6794+ timeout = max (1 , min (10000 , args .timeout ))
6795+ print (f" Capturing LF field for { timeout } ms at 125kHz (8µs/sample)..." )
6796+ resp = self .cmd .lf_sniff (timeout_ms = timeout )
6797+
6798+ if resp .status != Status .LF_TAG_OK or not resp .data :
6799+ print (f"{ CR } No samples captured{ C0 } " )
6800+ return
6801+
6802+ import chameleon_cli_unit as _self_mod
6803+ data = bytes (resp .data )
6804+ _self_mod ._last_capture = data
6805+
6806+ n = len (data )
6807+ duration_ms = n * 8 / 1000
6808+ print (f" Captured : { CG } { n } { C0 } bytes ({ duration_ms :.1f} ms)" )
6809+
6810+ mn = min (data )
6811+ mx = max (data )
6812+ mean = sum (data ) // len (data )
6813+ print (f" Range : { CG } 0x{ mn :02x} { C0 } – { CG } 0x{ mx :02x} { C0 } mean: { CG } 0x{ mean :02x} { C0 } " )
6814+
6815+ # Detect real field gaps — they drop to near zero (0x00-0x40),
6816+ # well below the steady carrier (~0xb0). Use half of mean as threshold
6817+ # to avoid false positives from the antenna startup transient.
6818+ gap_threshold = mean // 2
6819+ # Skip first 200 samples (1.6ms) to ignore startup ringing
6820+ steady_data = data [200 :]
6821+ gap_count = sum (1 for b in steady_data if b < gap_threshold )
6822+ if gap_count > 0 :
6823+ print (f" Gaps : { CG } { gap_count } { C0 } samples below 0x{ gap_threshold :02x} (real field drops)" )
6824+ else :
6825+ print (f" Gaps : { CR } none detected — flat carrier (no gap commands sent){ C0 } " )
6826+
6827+ if args .hex :
6828+ print ()
6829+ print (f" addr { 'hex bytes' :47s} level" )
6830+ print (f" ---- { '-' * 47 } ----------------" )
6831+ for i in range (0 , min (n , 256 ), 16 ):
6832+ row = data [i :i + 16 ]
6833+ hex_part = ' ' .join (f'{ b :02x} ' for b in row )
6834+ bar = ''
6835+ for b in row :
6836+ if b < 0x10 :
6837+ bar += '_' # gap / field off
6838+ elif b < 0x40 :
6839+ bar += '.' # ringing decay
6840+ elif b < 0x80 :
6841+ bar += '-' # low
6842+ elif b < 0xa0 :
6843+ bar += '+' # mid
6844+ elif b < 0xc0 :
6845+ bar += 'o' # steady carrier
6846+ elif b < 0xe0 :
6847+ bar += 'O' # high
6848+ else :
6849+ bar += '#' # clipped 0xff
6850+ print (f" { i :04x} { hex_part :<47s} { bar } " )
6851+ if n > 256 :
6852+ print (f" ... ({ n - 256 } more bytes, use --out to save all)" )
6853+ print ()
6854+ print (" _ gap . ringing - low + mid o carrier O high # clipped" )
6855+
6856+ if args .out :
6857+ try :
6858+ with open (args .out , 'wb' ) as f :
6859+ f .write (data )
6860+ print (f" Saved : { CG } { args .out } { C0 } ({ n } bytes)" )
6861+ except Exception as e :
6862+ print (f"{ CR } Failed to save: { e } { C0 } " )
6863+
6864+
6865+
0 commit comments