From: Uwe Hermann Date: Thu, 6 Feb 2014 18:49:06 +0000 (+0100) Subject: Add initial RC-5 IR protocol decoder. X-Git-Tag: libsigrokdecode-0.3.0~68 X-Git-Url: https://sigrok.org/gitweb/?p=libsigrokdecode.git;a=commitdiff_plain;h=914f0b99d1f07c351742eaa5c8e397d601996846 Add initial RC-5 IR protocol decoder. --- diff --git a/decoders/ir_rc5/__init__.py b/decoders/ir_rc5/__init__.py new file mode 100644 index 0000000..085082f --- /dev/null +++ b/decoders/ir_rc5/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## 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 +## + +''' +RC-5 is a biphase/manchester based infrared remote control protocol. +''' + +from .pd import * + diff --git a/decoders/ir_rc5/lists.py b/decoders/ir_rc5/lists.py new file mode 100644 index 0000000..8a81ef7 --- /dev/null +++ b/decoders/ir_rc5/lists.py @@ -0,0 +1,95 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## 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 +## + +# Systems/addresses (0..31). Items that are not listed are reserved/unknown. +system = { + 0: ['TV receiver 1', 'TV1'], + 1: ['TV receiver 2', 'TV2'], + 2: ['Teletext', 'Txt'], + 3: ['Extension to TV1 and TV2', 'Ext TV1/TV2'], + 4: ['LaserVision player', 'LV'], + 5: ['Video cassette recorder 1', 'VCR1'], + 6: ['Video cassette recorder 2', 'VCR2'], + 7: ['Experimental', 'Exp'], + 8: ['Satellite TV receiver 1', 'Sat1'], + 9: ['Extension to VCR1 and VCR2', 'Ext VCR1/VCR2'], + 10: ['Satellite TV receiver 2', 'Sat2'], + 12: ['Compact disc video player', 'CD-Video'], + 13: ['Camcorder', 'Cam'], + 14: ['Photo on compact disc player', 'CD-Photo'], + 16: ['Audio preamplifier 1', 'Preamp1'], + 17: ['Radio tuner', 'Tuner'], + 18: ['Analog cassette recoder 1', 'Rec1'], + 19: ['Audio preamplifier 2', 'Preamp2'], + 20: ['Compact disc player', 'CD'], + 21: ['Audio stack or record player', 'Combi'], + 22: ['Audio satellite', 'Sat'], + 23: ['Analog cassette recoder 2', 'Rec2'], + 26: ['Compact disc recorder', 'CD-R'], + 29: ['Lighting 1', 'Light1'], + 30: ['Lighting 2', 'Light2'], + 31: ['Telephone', 'Phone'], +} + +digits = { + 0: ['0', '0'], + 1: ['1', '1'], + 2: ['2', '2'], + 3: ['3', '3'], + 4: ['4', '4'], + 5: ['5', '5'], + 6: ['6', '6'], + 7: ['7', '7'], + 8: ['8', '8'], + 9: ['9', '9'], +} + +# Commands (0..63 for RC-5, and 0..127 for Extended RC-5). +# Items that are not listed are reserved/unknown. +command = { + 'TV': dict(list(digits.items()) + list({ + 10: ['-/--', '-/--'], + 11: ['Channel/program', 'Ch/P'], + 12: ['Standby', 'StBy'], + 13: ['Mute', 'M'], + 14: ['Personal preferences', 'PP'], + 14: ['Display', 'Disp'], + 16: ['Volume up', 'Vol+'], + 17: ['Volume down', 'Vol-'], + 18: ['Brightness up', 'Br+'], + 19: ['Brightness down', 'Br-'], + 20: ['Saturation up', 'S+'], + 21: ['Saturation down', 'S-'], + 32: ['Program up', 'P+'], + 33: ['Program down', 'P-'], + }.items())), + 'VCR': dict(list(digits.items()) + list({ + 10: ['-/--', '-/--'], + 12: ['Standby', 'StBy'], + 32: ['Program up', 'P+'], + 33: ['Program down', 'P-'], + 50: ['Fast rewind', 'FRW'], + 52: ['Fast forward', 'FFW'], + 53: ['Play', 'Pl'], + 54: ['Stop', 'St'], + 55: ['Recording', 'Rec'], + }.items())), +} + diff --git a/decoders/ir_rc5/pd.py b/decoders/ir_rc5/pd.py new file mode 100644 index 0000000..1abba82 --- /dev/null +++ b/decoders/ir_rc5/pd.py @@ -0,0 +1,175 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## 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 +from .lists import * + +class Decoder(srd.Decoder): + api_version = 1 + id = 'ir_rc5' + name = 'IR RC-5' + longname = 'IR RC-5' + desc = 'RC-5 infrared remote control protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['ir_rc5'] + probes = [ + {'id': 'ir', 'name': 'IR', 'desc': 'IR data line'}, + ] + optional_probes = [] + options = { + 'polarity': ['Polarity', 'active-low'], + 'protocol': ['Protocol type', 'standard'], + } + annotations = [ + ['bit', 'Bit'], + ['startbit1', 'Startbit 1'], + ['startbit2', 'Startbit 2'], + ['togglebit-0', 'Toggle bit 0'], + ['togglebit-1', 'Toggle bit 1'], + ['address', 'Address'], + ['command', 'Command'], + ] + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('fields', 'Fields', (1, 2, 3, 4, 5, 6)), + ) + + def __init__(self, **kwargs): + self.samplerate = None + self.samplenum = None + self.edges, self.bits, self.bits_ss_es = [], [], [] + self.state = 'IDLE' + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.old_ir = 1 if self.options['polarity'] == 'active-low' else 0 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + # One bit: 1.78ms (one half low, one half high). + self.halfbit = int((self.samplerate * 0.00178) / 2.0) + + def putb(self, bit1, bit2, data): + ss, es = self.bits_ss_es[bit1][0], self.bits_ss_es[bit2][1] + self.put(ss, es, self.out_ann, data) + + def handle_bits(self): + a, c, b = 0, 0, self.bits + # Individual raw bits. + for i in range(14): + if i == 0: + ss = max(0, self.bits[0][0] - self.halfbit) + else: + ss = self.bits_ss_es[i - 1][1] + es = self.bits[i][0] + self.halfbit + self.bits_ss_es.append([ss, es]) + self.putb(i, i, [0, ['%d' % self.bits[i][1]]]) + # Bits[0:0]: Startbit 1 + s = ['Startbit1: %d' % b[0][1], 'SB1: %d' % b[0][1], 'SB1', 'S1', 'S'] + self.putb(0, 0, [1, s]) + # Bits[1:1]: Startbit 2 + ann_idx = 2 + s = ['Startbit2: %d' % b[1][1], 'SB2: %d' % b[1][1], 'SB2', 'S2', 'S'] + if self.options['protocol'] == 'extended': + s = ['CMD[6]#: %d' % b[1][1], 'C6#: %d' % b[1][1], 'C6#', 'C#', 'C'] + ann_idx = 6 + self.putb(1, 1, [ann_idx, s]) + # Bits[2:2]: Toggle bit + s = ['Togglebit: %d' % b[2][1], 'Toggle: %d' % b[2][1], + 'TB: %d' % b[2][1], 'TB', 'T'] + self.putb(2, 2, [3 if b[2][1] == 0 else 4, s]) + # Bits[3:7]: Address (MSB-first) + for i in range(5): + a |= (b[3 + i][1] << (4 - i)) + x = system.get(a, ['Unknown', 'Unk']) + s = ['Address: %d (%s)' % (a, x[0]), 'Addr: %d (%s)' % (a, x[1]), + 'Addr: %d' % a, 'A: %d' % a, 'A'] + self.putb(3, 7, [5, s]) + # Bits[8:13]: Command (MSB-first) + for i in range(6): + c |= (b[8 + i][1] << (5 - i)) + if self.options['protocol'] == 'extended': + inverted_bit6 = 1 if b[1][1] == 0 else 0 + c |= (inverted_bit6 << 6) + cmd_type = 'VCR' if x[1] in ('VCR1', 'VCR2') else 'TV' + x = command[cmd_type].get(c, ['Unknown', 'Unk']) + s = ['Command: %d (%s)' % (c, x[0]), 'Cmd: %d (%s)' % (c, x[1]), + 'Cmd: %d' % c, 'C: %d' % c, 'C'] + self.putb(8, 13, [6, s]) + + def edge_type(self): + # Categorize according to distance from last edge (short/long). + distance = self.samplenum - self.edges[-1] + s, l, margin = self.halfbit, self.halfbit * 2, int(self.halfbit / 2) + if distance in range(l - margin, l + margin + 1): + return 'l' + elif distance in range(s - margin, s + margin + 1): + return 's' + else: + raise Exception('Invalid edge distance: %d' % distance) + + def decode(self, ss, es, data): + if self.samplerate is None: + raise Exception("Cannot decode without samplerate.") + for (self.samplenum, pins) in data: + + self.ir = pins[0] + + # Wait for any edge (rising or falling). + if self.old_ir == self.ir: + continue + + # State machine. + if self.state == 'IDLE': + self.edges.append(self.samplenum) + self.bits.append([self.samplenum, 1]) + self.state = 'MID1' + self.old_ir = self.ir + continue + if self.state == 'MID1': + self.state = 'START1' if self.edge_type() == 's' else 'MID0' + bit = None if self.edge_type() == 's' else 0 + elif self.state == 'MID0': + self.state = 'START0' if self.edge_type() == 's' else 'MID1' + bit = None if self.edge_type() == 's' else 1 + elif self.state == 'START1': + if self.edge_type() == 's': + self.state = 'MID1' + bit = 1 if self.edge_type() == 's' else None + elif self.state == 'START0': + if self.edge_type() == 's': + self.state = 'MID0' + bit = 0 if self.edge_type() == 's' else None + else: + raise Exception('Invalid state: %s' % self.state) + + self.edges.append(self.samplenum) + if bit != None: + self.bits.append([self.samplenum, bit]) + + if len(self.bits) == 14 + 1: + self.handle_bits() + self.edges, self.bits, self.bits_ss_es = [], [], [] + self.state = 'IDLE' + + self.old_ir = self.ir +