From 25dfcc83132fc2704ae839f1866ef95603ec00fb Mon Sep 17 00:00:00 2001 From: Abdelhak Bougouffa Date: Tue, 2 Feb 2021 10:20:21 +0100 Subject: [PATCH] Add 'IR IRMP' decoder from libsigrokdecode codebase --- .../decoders/ir_irmp/__init__.py | 25 ++++ .../decoders/ir_irmp/irmp_library.py | 137 ++++++++++++++++++ libsigrokdecode4DSL/decoders/ir_irmp/pd.py | 137 ++++++++++++++++++ 3 files changed, 299 insertions(+) create mode 100644 libsigrokdecode4DSL/decoders/ir_irmp/__init__.py create mode 100644 libsigrokdecode4DSL/decoders/ir_irmp/irmp_library.py create mode 100644 libsigrokdecode4DSL/decoders/ir_irmp/pd.py diff --git a/libsigrokdecode4DSL/decoders/ir_irmp/__init__.py b/libsigrokdecode4DSL/decoders/ir_irmp/__init__.py new file mode 100644 index 00000000..b6bbff60 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_irmp/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Rene Staffen +## +## 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 . +## + +''' +IRMP is a multi protocol infrared remote protocol decoder. See +https://www.mikrocontroller.net/articles/IRMP for details. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ir_irmp/irmp_library.py b/libsigrokdecode4DSL/decoders/ir_irmp/irmp_library.py new file mode 100644 index 00000000..5ec65222 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_irmp/irmp_library.py @@ -0,0 +1,137 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Rene Staffen +## +## 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 . +## + +''' +Python binding for the IRMP library. +''' + +import ctypes +import platform + +class IrmpLibrary: + ''' + Library instance for an infrared protocol detector. + ''' + + __usable_instance = None + + class ResultData(ctypes.Structure): + _fields_ = [ + ( 'protocol', ctypes.c_uint32, ), + ( 'protocol_name', ctypes.c_char_p, ), + ( 'address', ctypes.c_uint32, ), + ( 'command', ctypes.c_uint32, ), + ( 'flags', ctypes.c_uint32, ), + ( 'start_sample', ctypes.c_uint32, ), + ( 'end_sample', ctypes.c_uint32, ), + ] + + FLAG_REPETITION = 1 << 0 + FLAG_RELEASE = 1 << 1 + + def _library_filename(self): + ''' + Determine the library filename depending on the platform. + ''' + + if platform.uname()[0] == 'Linux': + return 'libirmp.so' + if platform.uname()[0] == 'Darwin': + return 'libirmp.dylib' + return 'irmp.dll' + + def _library_setup_api(self): + ''' + Lookup the C library's API routines. Declare their prototypes. + ''' + + if not self._lib: + return False + + self._lib.irmp_get_sample_rate.restype = ctypes.c_uint32 + self._lib.irmp_get_sample_rate.argtypes = [] + + self._lib.irmp_reset_state.restype = None + self._lib.irmp_reset_state.argtypes = [] + + self._lib.irmp_add_one_sample.restype = ctypes.c_int + self._lib.irmp_add_one_sample.argtypes = [ ctypes.c_int, ] + + if False: + self._lib.irmp_detect_buffer.restype = self.ResultData + self._lib.irmp_detect_buffer.argtypes = [ ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t, ] + + self._lib.irmp_get_result_data.restype = ctypes.c_int + self._lib.irmp_get_result_data.argtypes = [ ctypes.POINTER(self.ResultData), ] + + self._lib.irmp_get_protocol_name.restype = ctypes.c_char_p + self._lib.irmp_get_protocol_name.argtypes = [ ctypes.c_uint32, ] + + # Create a result buffer that's local to the library instance. + self._data = self.ResultData() + + return True + + def __init__(self): + ''' + Create a library instance. + ''' + + # Only create a working instance for the first invocation. + # Degrade all other instances, make them fail "late" during + # execution, so that users will see the errors. + self._lib = None + self._data = None + if IrmpLibrary.__usable_instance is None: + filename = self._library_filename() + self._lib = ctypes.cdll.LoadLibrary(filename) + self._library_setup_api() + IrmpLibrary.__usable_instance = self + + def get_sample_rate(self): + if not self._lib: + return None + return self._lib.irmp_get_sample_rate() + + def reset_state(self): + if not self._lib: + return None + self._lib.irmp_reset_state() + + def add_one_sample(self, level): + if not self._lib: + raise Exception("IRMP library limited to a single instance.") + if not self._lib.irmp_add_one_sample(int(level)): + return False + self._lib.irmp_get_result_data(ctypes.byref(self._data)) + return True + + def get_result_data(self): + if not self._data: + return None + return { + 'proto_nr': self._data.protocol, + 'proto_name': self._data.protocol_name.decode('UTF-8', 'ignore'), + 'address': self._data.address, + 'command': self._data.command, + 'repeat': bool(self._data.flags & self.FLAG_REPETITION), + 'release': bool(self._data.flags & self.FLAG_RELEASE), + 'start': self._data.start_sample, + 'end': self._data.end_sample, + } diff --git a/libsigrokdecode4DSL/decoders/ir_irmp/pd.py b/libsigrokdecode4DSL/decoders/ir_irmp/pd.py new file mode 100644 index 00000000..979c1e01 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_irmp/pd.py @@ -0,0 +1,137 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Gump Yang +## Copyright (C) 2019 Rene Staffen +## +## 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 . +## + +from . import irmp_library +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class LibraryError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ir_irmp' + name = 'IR IRMP' + longname = 'IR IRMP' + desc = 'IRMP infrared remote control multi protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IR'] + channels = ( + {'id': 'ir', 'name': 'IR', 'desc': 'Data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'active-low', + 'values': ('active-low', 'active-high')}, + ) + annotations = ( + ('packet', 'Packet'), + ) + annotation_rows = ( + ('packets', 'IR Packets', (0,)), + ) + + def putframe(self, data): + '''Emit annotation for an IR frame.''' + + # Cache result data fields in local variables. Get the ss/es + # timestamps, scaled to sample numbers. + nr = data['proto_nr'] + name = data['proto_name'] + addr = data['address'] + cmd = data['command'] + repeat = data['repeat'] + release = data['release'] + ss = data['start'] * self.rate_factor + es = data['end'] * self.rate_factor + + # Prepare display texts for several zoom levels. + # Implementor's note: Keep list lengths for flags aligned during + # maintenance. Make sure there are as many flags text variants + # as are referenced by annotation text variants. Differing list + # lengths or dynamic refs will severely complicate the logic. + rep_txts = ['repeat', 'rep', 'r'] + rel_txts = ['release', 'rel', 'R'] + flag_txts = [None,] * len(rep_txts) + for zoom in range(len(flag_txts)): + flag_txts[zoom] = [] + if repeat: + flag_txts[zoom].append(rep_txts[zoom]) + if release: + flag_txts[zoom].append(rel_txts[zoom]) + flag_txts = [' '.join(t) or '-' for t in flag_txts] + flg = flag_txts # Short name for .format() references. + txts = [ + 'Protocol: {name} ({nr}), Address 0x{addr:04x}, Command: 0x{cmd:04x}, Flags: {flg[0]}'.format(**locals()), + 'P: {name} ({nr}), Addr: 0x{addr:x}, Cmd: 0x{cmd:x}, Flg: {flg[1]}'.format(**locals()), + 'P: {nr} A: 0x{addr:x} C: 0x{cmd:x} F: {flg[1]}'.format(**locals()), + 'C:{cmd:x} A:{addr:x} {flg[2]}'.format(**locals()), + 'C:{cmd:x}'.format(**locals()), + ] + + # Emit the annotation from details which were constructed above. + self.put(ss, es, self.out_ann, [0, txts]) + + def __init__(self): + self.irmp = None + self.reset() + + def reset(self): + self.want_reset = True + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def decode(self): + if not self.irmp: + try: + self.irmp = irmp_library.IrmpLibrary() + except Exception as e: + txt = e.args[0] + raise LibraryError(txt) + if self.irmp: + self.lib_rate = self.irmp.get_sample_rate() + if not self.irmp or not self.lib_rate: + raise LibraryError('Cannot access IRMP library. One instance limit exceeded?') + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + if self.samplerate % self.lib_rate: + raise SamplerateError('Capture samplerate must be multiple of library samplerate ({})'.format(self.lib_rate)) + self.rate_factor = int(self.samplerate / self.lib_rate) + if self.want_reset: + self.irmp.reset_state() + self.want_reset = False + + self.active = 0 if self.options['polarity'] == 'active-low' else 1 + ir, = self.wait() + while True: + if self.active == 1: + ir = 1 - ir + if self.irmp.add_one_sample(ir): + data = self.irmp.get_result_data() + self.putframe(data) + ir, = self.wait([{'skip': self.rate_factor}])