From: Benjamin Larsson Date: Wed, 11 May 2016 18:40:54 +0000 (+0200) Subject: Add decoder for em4305 RFID protocol X-Git-Tag: libsigrokdecode-0.5.0~167 X-Git-Url: https://sigrok.org/gitweb/?p=libsigrokdecode.git;a=commitdiff_plain;h=ae2181cd35e84ce81cfe58d2e0e05fdb3dfcb263 Add decoder for em4305 RFID protocol --- diff --git a/decoders/em4305/__init__.py b/decoders/em4305/__init__.py new file mode 100644 index 0000000..1c1896a --- /dev/null +++ b/decoders/em4305/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Benjamin Larsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +EM4305 is a 100-150kHz RFID protocol. +''' + +from .pd import Decoder diff --git a/decoders/em4305/pd.py b/decoders/em4305/pd.py new file mode 100644 index 0000000..73ed955 --- /dev/null +++ b/decoders/em4305/pd.py @@ -0,0 +1,396 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Benjamin Larsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 2 + id = 'em4305' + name = 'EM4305' + longname = 'RFID EM4205/EM4305' + desc = 'EM4205/EM4305 100-150kHz RFID protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['em4305'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + options = ( + {'id': 'coilfreq', 'desc': 'Coil frequency', 'default': 125000}, + {'id': 'first_field_stop', 'desc': 'First field stop min', 'default': 40}, + {'id': 'w_gap', 'desc': 'Write gap min', 'default': 12}, + {'id': 'w_one_max', 'desc': 'Write one max', 'default': 32}, + {'id': 'w_zero_on_min', 'desc': 'Write zero on min', 'default': 15}, + {'id': 'w_zero_off_max', 'desc': 'Write zero off max', 'default': 27}, + {'id': 'em4100_decode', 'desc': 'EM4100 decode', 'default': 'on', + 'values': ('on', 'off')}, + ) + annotations = ( + ('bit_value', 'Bit value'), + ('first_field_stop', 'First field stop'), + ('write_gap', 'Write gap'), + ('write_mode_exit', 'Write mode exit'), + ('bit', 'Bit'), + ('opcode', 'Opcode'), + ('lock', 'Lock'), + ('data', 'Data'), + ('password', 'Password'), + ('address', 'Address'), + ('bitrate', 'Bitrate'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('structure', 'Structure', (1, 2, 3, 4)), + ('fields', 'Fields', (5, 6, 7, 8, 9)), + ('decode', 'Decode', (10,)), + ) + + def __init__(self): + self.samplerate = None + self.oldpin = None + self.last_samplenum = None + self.state = 'FFS_SEARCH' + self.bits_pos = [[0 for col in range(3)] for row in range(70)] + self.br_string = ['RF/8', 'RF/16', 'Unused', 'RF/32', 'RF/40', + 'Unused', 'Unused', 'RF/64',] + self.encoder = ['not used', 'Manchester', 'Bi-phase', 'not used'] + self.delayed_on = ['No delay', 'Delayed on - BP/8', 'Delayed on - BP/4', 'No delay'] + self.em4100_decode1_partial = 0 + self.cmds = ['Invalid', 'Login', 'Write word', 'Invalid', 'Read word', 'Disable', 'Protect', 'Invalid'] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.field_clock = self.samplerate / self.options['coilfreq'] + self.wzmax = self.options['w_zero_off_max'] * self.field_clock + self.wzmin = self.options['w_zero_on_min'] * self.field_clock + self.womax = self.options['w_one_max'] * self.field_clock + self.ffs = self.options['first_field_stop'] * self.field_clock + self.writegap = self.options['w_gap'] * self.field_clock + self.nogap = 300 * self.field_clock + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode_config(self, idx): + bitrate = self.get_3_bits(idx+2) + self.put(self.bits_pos[idx][1], self.bits_pos[idx+5][2], + self.out_ann, [10, ['Data rate: ' + \ + self.br_string[bitrate], self.br_string[bitrate]]]) + encoding = self.bits_pos[idx+6][0]<<0 | self.bits_pos[idx+7][0]<<1 + self.put(self.bits_pos[idx+6][1], self.bits_pos[idx+10][2], + self.out_ann, [10, ['Encoder: ' + \ + self.encoder[encoding], self.encoder[encoding]]]) + self.put(self.bits_pos[idx+11][1], self.bits_pos[idx+12][2], self.out_ann, + [10, ['Zero bits', 'ZB']]) + delay_on = self.bits_pos[idx+13][0]<<0 | self.bits_pos[idx+14][0]<<1 + self.put(self.bits_pos[idx+13][1], self.bits_pos[idx+14][2], + self.out_ann, [10, ['Delayed on: ' + \ + self.delayed_on[delay_on], self.delayed_on[delay_on]]]) + lwr = self.bits_pos[idx+15][0]<<3 | self.bits_pos[idx+16][0]<<2 | \ + self.bits_pos[idx+18][0]<<1 | self.bits_pos[idx+19][0]<<0 + self.put(self.bits_pos[idx+15][1], self.bits_pos[idx+19][2], + self.out_ann, [10, ['Last default read word: %d' % lwr, 'LWR: %d' % lwr, '%d' % lwr]]) + self.put(self.bits_pos[idx+20][1], self.bits_pos[idx+20][2], + self.out_ann, [10, ['Read login: %d' % self.bits_pos[idx+20][0], '%d' % self.bits_pos[idx+20][0]]]) + self.put(self.bits_pos[idx+21][1], self.bits_pos[idx+21][2], self.out_ann, + [10, ['Zero bits', 'ZB']]) + self.put(self.bits_pos[idx+22][1], self.bits_pos[idx+22][2], + self.out_ann, [10, ['Write login: %d' % self.bits_pos[idx+22][0], '%d' % self.bits_pos[idx+22][0]]]) + self.put(self.bits_pos[idx+23][1], self.bits_pos[idx+24][2], self.out_ann, + [10, ['Zero bits', 'ZB']]) + self.put(self.bits_pos[idx+25][1], self.bits_pos[idx+25][2], + self.out_ann, [10, ['Disable: %d' % self.bits_pos[idx+25][0], '%d' % self.bits_pos[idx+25][0]]]) + self.put(self.bits_pos[idx+27][1], self.bits_pos[idx+27][2], + self.out_ann, [10, ['Reader talk first: %d' % self.bits_pos[idx+27][0], 'RTF: %d' % self.bits_pos[idx+27][0]]]) + self.put(self.bits_pos[idx+28][1], self.bits_pos[idx+28][2], self.out_ann, + [10, ['Zero bits', 'ZB']]) + self.put(self.bits_pos[idx+29][1], self.bits_pos[idx+29][2], + self.out_ann, [10, ['Pigeon mode: %d' % self.bits_pos[idx+29][0], '%d' % self.bits_pos[idx+29][0]]]) + self.put(self.bits_pos[idx+30][1], self.bits_pos[idx+34][2], + self.out_ann, [10, ['Reserved', 'Res', 'R']]) + + def put4bits(self, idx): + bits = self.bits_pos[idx][0]<<3 | self.bits_pos[idx+1][0]<<2 | \ + self.bits_pos[idx+2][0]<<1 | self.bits_pos[idx+3][0] + self.put(self.bits_pos[idx][1], self.bits_pos[idx+3][2], self.out_ann, + [10, ['%X' % bits]]) + + def em4100_decode1(self, idx): + self.put(self.bits_pos[idx][1], self.bits_pos[idx+9][2], self.out_ann, + [10, ['EM4100 header', 'EM header', 'Header', 'H']]) + self.put4bits(idx+10) + bits = self.bits_pos[idx+15][0]<<3 | self.bits_pos[idx+16][0]<<2 | \ + self.bits_pos[idx+18][0]<<1 | self.bits_pos[idx+19][0]<<0 + self.put(self.bits_pos[idx+15][1], self.bits_pos[idx+19][2], self.out_ann, + [10, ['%X' % bits]]) + self.put4bits(idx+21) + self.put4bits(idx+27) + self.em4100_decode1_partial = self.bits_pos[idx+32][0]<<3 | \ + self.bits_pos[idx+33][0]<<2 | self.bits_pos[idx+34][0]<<1 + self.put(self.bits_pos[idx+32][1], self.bits_pos[idx+34][2], + self.out_ann, [10, ['Partial nibble']]) + + def em4100_decode2(self, idx): + if self.em4100_decode1_partial != 0: + bits = self.em4100_decode1_partial + self.bits_pos[idx][0] + self.put(self.bits_pos[idx][1], self.bits_pos[idx][2], + self.out_ann, [10, ['%X' % bits]]) + self.em4100_decode1_partial = 0 + else: + self.put(self.bits_pos[idx][1], self.bits_pos[idx][2], + self.out_ann, [10, ['Partial nibble']]) + + self.put4bits(idx+2) + bits = self.bits_pos[idx+7][0]<<3 | self.bits_pos[idx+9][0]<<2 | \ + self.bits_pos[idx+10][0]<<1 | self.bits_pos[idx+11][0]<<0 + self.put(self.bits_pos[idx+7][1], self.bits_pos[idx+11][2], self.out_ann, + [10, ['%X' % bits]]) + self.put4bits(idx+13) + self.put4bits(idx+19) + bits = self.bits_pos[idx+24][0]<<3 | self.bits_pos[idx+25][0]<<2 | \ + self.bits_pos[idx+27][0]<<1 | self.bits_pos[idx+28][0]<<0 + self.put(self.bits_pos[idx+24][1], self.bits_pos[idx+28][2], self.out_ann, + [10, ['%X' % bits]]) + self.put(self.bits_pos[idx+30][1], self.bits_pos[idx+34][2], + self.out_ann, [10, ['EM4100 trailer']]) + + def get_32_bits(self, idx): + return self.get_8_bits(idx+27)<<24 | self.get_8_bits(idx+18)<<16 | \ + self.get_8_bits(idx+9)<<8 | self.get_8_bits(idx) + + def get_8_bits(self, idx): + retval = 0 + for i in range(0, 8): + retval <<= 1 + retval |= self.bits_pos[i+idx][0] + return retval + + def get_3_bits(self, idx): + return self.bits_pos[idx][0]<<2 | self.bits_pos[idx+1][0]<<1 | \ + self.bits_pos[idx+2][0] + + def get_4_bits(self, idx): + return self.bits_pos[idx][0]<<0 | self.bits_pos[idx+1][0]<<1 | \ + self.bits_pos[idx+2][0]<<2 | self.bits_pos[idx+3][0]<<3 + + def print_row_parity(self, idx, length): + parity = 0 + for i in range(0, length): + parity += self.bits_pos[i+idx][0] + parity = parity & 0x1 + if parity == self.bits_pos[idx+length][0]: + self.put(self.bits_pos[idx+length][1], self.bits_pos[idx+length][2], self.out_ann, + [5, ['Row parity OK', 'Parity OK', 'OK']]) + else: + self.put(self.bits_pos[idx+length][1], self.bits_pos[idx+length][2], self.out_ann, + [5, ['Row parity failed', 'Parity failed', 'Fail']]) + + def print_col_parity(self, idx): + data_1 = self.get_8_bits(idx) + data_2 = self.get_8_bits(idx+9) + data_3 = self.get_8_bits(idx+9+9) + data_4 = self.get_8_bits(idx+9+9+9) + col_par = self.get_8_bits(idx+9+9+9+9) + col_par_calc = data_1^data_2^data_3^data_4 + + if col_par == col_par_calc: + self.put(self.bits_pos[idx+9+9+9+9][1], self.bits_pos[idx+9+9+9+9+7][2], self.out_ann, + [5, ['Column parity OK', 'Parity OK', 'OK']]) + else: + self.put(self.bits_pos[idx+9+9+9+9][1], self.bits_pos[idx+9+9+9+9+7][2], self.out_ann, + [5, ['Column parity failed', 'Parity failed', 'Fail']]) + + def print_8bit_data(self, idx): + data = self.get_8_bits(idx) + self.put(self.bits_pos[idx][1], self.bits_pos[idx+7][2], self.out_ann, + [9, ['Data' + ': %X' % data, '%X' % data]]) + + def put_fields(self): + if self.bit_nr == 50: + self.put(self.bits_pos[0][1], self.bits_pos[0][2], self.out_ann, + [4, ['Logic zero']]) + self.put(self.bits_pos[1][1], self.bits_pos[4][2], self.out_ann, + [4, ['Command', 'Cmd', 'C']]) + self.put(self.bits_pos[5][1], self.bits_pos[49][2], self.out_ann, + [4, ['Password', 'Passwd', 'Pass', 'P']]) + # Get command. + cmd = self.get_3_bits(1) + self.put(self.bits_pos[1][1], self.bits_pos[3][2], self.out_ann, + [5, [self.cmds[cmd]]]) + self.print_row_parity(1, 3) + + # Print data. + self.print_8bit_data(5) + self.print_row_parity(5, 8) + self.print_8bit_data(14) + self.print_row_parity(14, 8) + self.print_8bit_data(23) + self.print_row_parity(23, 8) + self.print_8bit_data(32) + self.print_row_parity(32, 8) + self.print_col_parity(5) + if self.bits_pos[49][0] == 0: + self.put(self.bits_pos[49][1], self.bits_pos[49][2], self.out_ann, + [5, ['Stop bit', 'Stop', 'SB']]) + else: + self.put(self.bits_pos[49][1], self.bits_pos[49][2], self.out_ann, + [5, ['Stop bit error', 'Error']]) + + if cmd == 1: + password = self.get_32_bits(5) + self.put(self.bits_pos[12][1], self.bits_pos[46][2], self.out_ann, + [10, ['Login password: %X' % password]]) + + if self.bit_nr == 57: + self.put(self.bits_pos[0][1], self.bits_pos[0][2], self.out_ann, + [4, ['Logic zero', 'LZ']]) + self.put(self.bits_pos[1][1], self.bits_pos[4][2], self.out_ann, + [4, ['Command', 'Cmd', 'C']]) + self.put(self.bits_pos[5][1], self.bits_pos[11][2], self.out_ann, + [4, ['Address', 'Addr', 'A']]) + self.put(self.bits_pos[12][1], self.bits_pos[56][2], self.out_ann, + [4, ['Data', 'Da', 'D']]) + + # Get command. + cmd = self.get_3_bits(1) + self.put(self.bits_pos[1][1], self.bits_pos[3][2], self.out_ann, + [5, [self.cmds[cmd]]]) + self.print_row_parity(1, 3) + + # Get address. + addr = self.get_4_bits(5) + self.put(self.bits_pos[5][1], self.bits_pos[8][2], self.out_ann, + [9, ['Addr' + ': %d' % addr, '%d' % addr]]) + self.put(self.bits_pos[9][1], self.bits_pos[10][2], self.out_ann, + [5, ['Zero bits', 'ZB']]) + self.print_row_parity(5, 6) + # Print data. + self.print_8bit_data(12) + self.print_row_parity(12, 8) + self.print_8bit_data(21) + self.print_row_parity(21, 8) + self.print_8bit_data(30) + self.print_row_parity(30, 8) + self.print_8bit_data(39) + self.print_row_parity(39, 8) + self.print_col_parity(12) + if self.bits_pos[56][0] == 0: + self.put(self.bits_pos[56][1], self.bits_pos[56][2], self.out_ann, + [5, ['Stop bit', 'Stop', 'SB']]) + else: + self.put(self.bits_pos[56][1], self.bits_pos[56][2], self.out_ann, + [5, ['Stop bit error', 'Error']]) + + if addr == 4: + self.decode_config(12) + + if addr == 2: + password = self.get_32_bits(12) + self.put(self.bits_pos[12][1], self.bits_pos[46][2], self.out_ann, + [10, ['Write password: %X' % password]]) + + # If we are programming EM4100 data we can decode it halfway. + if addr == 5 and self.options['em4100_decode'] == 'on': + self.em4100_decode1(12) + if addr == 6 and self.options['em4100_decode'] == 'on': + self.em4100_decode2(12) + + self.bit_nr = 0 + + def add_bits_pos(self, bit, ss_bit, es_bit): + if self.bit_nr < 70: + self.bits_pos[self.bit_nr][0] = bit + self.bits_pos[self.bit_nr][1] = ss_bit + self.bits_pos[self.bit_nr][2] = es_bit + self.bit_nr += 1 + + def decode(self, ss, es, data): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + for (self.samplenum, (pin,)) in data: + # Ignore identical samples early on (for performance reasons). + if self.oldpin == pin: + continue + + if self.oldpin is None: + self.oldpin = pin + self.last_samplenum = self.samplenum + self.oldsamplenum = 0 + self.old_gap_end = 0 + self.gap_detected = 0 + self.bit_nr = 0 + continue + + if self.oldpin != pin: + pl = self.samplenum - self.oldsamplenum + pp = pin + samples = self.samplenum - self.last_samplenum + + if self.state == 'FFS_DETECTED': + if pl > self.writegap: + self.gap_detected = 1 + if (self.last_samplenum - self.old_gap_end) > self.nogap: + self.gap_detected = 0 + self.state = 'FFS_SEARCH' + self.put(self.old_gap_end, self.last_samplenum, + self.out_ann, [3, ['Write mode exit']]) + self.put_fields() + + if self.state == 'FFS_SEARCH': + if pl > self.ffs: + self.gap_detected = 1 + self.put(self.last_samplenum, self.samplenum, + self.out_ann, [1, ['First field stop', 'Field stop', 'FFS']]) + self.state = 'FFS_DETECTED' + + if self.gap_detected == 1: + self.gap_detected = 0 + if (self.last_samplenum - self.old_gap_end) > self.wzmin \ + and (self.last_samplenum - self.old_gap_end) < self.wzmax: + self.put(self.old_gap_end, self.samplenum, + self.out_ann, [0, ['0']]) + self.add_bits_pos(0, self.old_gap_end, self.samplenum) + if (self.last_samplenum - self.old_gap_end) > self.womax \ + and (self.last_samplenum-self.old_gap_end) < self.nogap: + # One or more 1 bits + one_bits = (int)((self.last_samplenum - self.old_gap_end) / self.womax) + for ox in range(0, one_bits): + bs = (int)(self.old_gap_end+ox*self.womax) + be = (int)(self.old_gap_end+ox*self.womax + self.womax) + self.put(bs, be, self.out_ann, [0, ['1']]) + self.add_bits_pos(1, bs, be) + if (self.samplenum - self.last_samplenum) > self.wzmin \ + and (self.samplenum - self.last_samplenum) < self.wzmax: + bs = (int)(self.old_gap_end+one_bits*self.womax) + self.put(bs, self.samplenum, self.out_ann, [0, ['0']]) + self.add_bits_pos(0, bs, self.samplenum) + + self.old_gap_end = self.samplenum + + if self.state == 'SKIP': + self.state = 'FFS_SEARCH' + + self.oldsamplenum = self.samplenum + self.last_samplenum = self.samplenum + self.oldpin = pin