From 4e7f2d702807d39bc5cd3835f02ff9d773b0cea1 Mon Sep 17 00:00:00 2001 From: Christoph Rackwitz Date: Mon, 6 Aug 2018 04:48:06 +0200 Subject: [PATCH] Miller encoding PD --- decoders/miller/__init__.py | 26 +++++ decoders/miller/pd.py | 183 ++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 decoders/miller/__init__.py create mode 100644 decoders/miller/pd.py diff --git a/decoders/miller/__init__.py b/decoders/miller/__init__.py new file mode 100644 index 0000000..409b178 --- /dev/null +++ b/decoders/miller/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Christoph Rackwitz +## +## 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, see . +## + +''' +The Miller protocol decoder supports (modified) Miller encoded data. + +Used in NFC communication at 106 kbaud. +''' + +from .pd import Decoder diff --git a/decoders/miller/pd.py b/decoders/miller/pd.py new file mode 100644 index 0000000..7ccd75e --- /dev/null +++ b/decoders/miller/pd.py @@ -0,0 +1,183 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Christoph Rackwitz +## +## 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, see . +## + +# http://www.gorferay.com/type-a-communications-interface/ +# https://resources.infosecinstitute.com/introduction-rfid-security/ +# https://www.radio-electronics.com/info/wireless/nfc/near-field-communications-modulation-rf-signal-interface.php +# https://www.researchgate.net/figure/Modified-Miller-Code_fig16_283498836 + +# Miller: either edge +# modified Miller: falling edge + +import sigrokdecode as srd + +def roundto(x, k=1.0): + return round(x / k) * k + +class Decoder(srd.Decoder): + api_version = 3 + id = 'miller' + name = 'Miller' + longname = 'Miller decoder for NFC' + desc = 'Decodes (modified) Miller encoding as used in NFC communication.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['miller'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data signal'}, + ) + options = ( + {'id': 'baudrate', 'desc': 'Baud rate', 'default': 106000}, + {'id': 'edge', 'desc': 'Edge', 'default': 'falling', 'values': ('rising', 'falling', 'either')}, + ) + annotations = ( + ('bit', 'Bit'), + ('bitstring', 'Bitstring'), + ) + annotation_rows = tuple((u, v, (i,)) for i, (u, v) in enumerate(annotations)) + + def __init__(self): + self.samplerate = None + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + + def decode_bits(self): + timeunit = self.samplerate / self.options['baudrate'] + edgetype = self.options['edge'][0] + + self.wait({0: edgetype}) # first symbol, beginning of unit + prevedge = self.samplenum + + # start of message: '0' + prevbit = 0 + yield (0, prevedge, prevedge + timeunit) + expectedstart = self.samplenum + timeunit + + # end of message: '0' followed by one idle symbol + + while True: + self.wait([{0: edgetype}, {'skip': int(3 * timeunit)}]) + got_timeout = self.matched[1] + sampledelta = (self.samplenum - prevedge) + prevedge = self.samplenum + timedelta = roundto(sampledelta / timeunit, 0.5) + + # a mark stands for a 1 bit + # a mark has an edge in the middle + + # a space stands for a 0 bit + # a space either has an edge at the beginning or no edge at all + # after a mark, a space is edge-less + # after a space, a space has an edge + + # we get 1.0, 1.5, 2.0 times between edges + + # end of transmission is always a space, either edged or edge-less + + if prevbit == 0: # space -> ??? + if timedelta == 1.0: # 1.0 units -> space + yield (0, self.samplenum, self.samplenum + timeunit) + prevbit = 0 + expectedstart = self.samplenum + timeunit + elif timedelta == 1.5: # 1.5 units -> mark + yield (1, expectedstart, self.samplenum + 0.5*timeunit) + prevbit = 1 + expectedstart = self.samplenum + timeunit*0.5 + elif timedelta >= 2.0: + # idle symbol (end of message) + yield None + else: + # assert timedelta >= 2.0 + yield (False, self.samplenum - sampledelta, self.samplenum) + break + else: # mark -> ??? + if timedelta <= 0.5: + yield (False, self.samplenum - sampledelta, self.samplenum) + break + if timedelta == 1.0: # 1.0 units -> mark again (1.5 from start) + yield (1, expectedstart, self.samplenum + 0.5*timeunit) + prevbit = 1 + expectedstart = self.samplenum + 0.5*timeunit + elif timedelta == 1.5: # 1.5 units -> space (no pulse) and space (pulse) + yield (0, expectedstart, self.samplenum) + yield (0, self.samplenum, self.samplenum + timeunit) + prevbit = 0 + expectedstart = self.samplenum + timeunit + elif timedelta == 2.0: # 2.0 units -> space (no pulse) and mark (pulse) + yield (0, expectedstart, expectedstart + timeunit) + yield (1, self.samplenum - 0.5*timeunit, self.samplenum + 0.5*timeunit) + prevbit = 1 + expectedstart = self.samplenum + timeunit*0.5 + else: # longer -> space and end of message + yield (0, expectedstart, expectedstart + timeunit) + yield None + break + + def decode_run(self): + numbits = 0 + bitvalue = 0 + bitstring = '' + stringstart = None + stringend = None + + for bit in self.decode_bits(): + if bit is None: + break + + (value, ss, es) = bit + + if value is False: + self.put(int(ss), int(es), self.out_ann, [1, ['ERROR']]) + else: + self.put(int(ss), int(es), self.out_ann, [0, ['{}'.format(value)]]) + + if value is False: + numbits = 0 + break + + if stringstart is None: + stringstart = ss + + stringend = es + + bitvalue |= value << numbits + numbits += 1 + + bitstring += '{}'.format(value) + if numbits % 4 == 0: + bitstring += ' ' + + if not numbits: + return + + self.put(int(stringstart), int(stringend), self.out_ann, [1, ['{}'.format(bitstring)]]) + + numbytes = numbits // 8 + (numbits % 8 > 0) + bytestring = bitvalue.to_bytes(numbytes, 'little') + self.put(int(stringstart), int(stringend), self.out_binary, [1, bytestring]) + + def decode(self): + while True: + self.decode_run() -- 2.30.2