]> sigrok.org Git - libsigrokdecode.git/blob - irmp/irmp-main-sharedlib.c
ir_irmp: wrapper lib, add locking and Python threading support
[libsigrokdecode.git] / irmp / irmp-main-sharedlib.c
1 /*
2  * irmp-main-sharedlib.c
3  *
4  * Copyright (c) 2009-2019 Frank Meyer - frank(at)fli4l.de
5  * Copyright (c) 2009-2019 RenĂ© Staffen - r.staffen(at)gmx.de
6  * Copyright (c) 2020-2021 Gerhard Sittig <gerhard.sittig@gmx.net>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  */
13
14 /*
15  * Declare the library's public API first. Prove it's consistent and
16  * complete as a standalone header file.
17  */
18 #include "irmp-main-sharedlib.h"
19
20 #include <errno.h>
21 #include <glib.h>
22 #include <Python.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 /*
27  * Include the IRMP core logic. This approach is required because of
28  * static variables which hold internal state. The core logic started
29  * as an MCU project where resources are severely constrained.
30  *
31  * This libsigrokdecode incarnation of IRMP will always be used in the
32  * UNIX_OR_WINDOWS configuration. But libtool(1) breaks the upstream
33  * logic's platform detection. Check reliably available conditions here
34  * and provide expected symbols to the library, to reduce changes to the
35  * upstream project.
36  */
37 #if defined _WIN32
38 #  if !defined WIN32
39 #    define WIN32
40 #  endif
41 #else
42 #  if !defined unix
43 #    define unix
44 #  endif
45 #endif
46 #include "irmp.h"
47 #include "irmp.c"
48
49 /*
50  * The remaining source code implements the PC library, which accepts
51  * sample data from API callers, and provides detector results as they
52  * become available after seeing input data.
53  *
54  * TODO items, known constraints
55  * - Counters in the IRMP core logic and the library wrapper are 32bit
56  *   only. In the strictest sense they only need to cover the span of
57  *   an IR frame. In the PC side library case they need to cover "a
58  *   detection phase", which happens to be under calling applications'
59  *   control. The library shall not mess with the core's internal state,
60  *   and may even not be able to reliably tell whether detection of a
61  *   frame started in the core. Fortunately the 32bit counters only roll
62  *   over after some 2.5 days at the highest available sample rate. So
63  *   this limitation is not a blocker.
64  * - The IRMP core keeps internal state in global variables. Which is
65  *   appropriate for MCU configurations. For the PC library use case
66  *   this constraint prevents concurrency, only a single data stream
67  *   can get processed at any time. This limitation can get addressed
68  *   later, making the flexible and featureful IRMP detection available
69  *   in the first place is considered highly desirable, and is a great
70  *   improvement in itself.
71  * - The detection of IR frames from buffered data is both limited and
72  *   complicated at the same time. The routine re-uses the caller's
73  *   buffer _and_ internal state across multiple calls. Thus windowed
74  *   operation over a larger set of input data is not available. The
75  *   API lacks a flag for failed detection, thus applications need to
76  *   guess from always returned payload data.
77  * - Is it worth adding a "detection in progress" query to the API? Is
78  *   the information available to the library wrapper, and reliable?
79  *   Shall applications be able to "poll" the started, and completed
80  *   state for streamed operation including periodic state resets which
81  *   won't interfere with pending detection? (It's assumed that this
82  *   is only required when feeding single values in individual calls is
83  *   found to be rather expensive.
84  * - Some of the result data reflects the core's internal presentation
85  *   while there is no declaration in the library's API. This violates
86  *   API layers, and needs to get addressed properly.
87  * - The IRMP core logic (strictly speaking the specific details of
88  *   preprocessor symbol arrangements in the current implementation)
89  *   appears to assume either to run on an MCU and capture IR signals
90  *   from hardware pins, falling back to AVR if no other platform got
91  *   detected. Or assumes to run on a (desktop) PC, and automatically
92  *   enables ANALYZE mode, which results in lots of stdio traffic that
93  *   is undesirable for application code which uses the shared library
94  *   for strict detection purposes but no further analysis or research.
95  *   It's a pity that turning off ANALYZE switches to MCU mode, and that
96  *   keeping ANALYZE enabled but silencing the output is rather messy
97  *   and touches the innards of the core logic (the irmp.c source file
98  *   and its dependency header files).
99  */
100
101 #ifndef ARRAY_SIZE
102 #  define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
103 #endif
104
105 static int irmp_lib_initialized;
106 static size_t irmp_lib_client_id;
107 static GMutex irmp_lib_mutex;
108
109 struct irmp_instance {
110         size_t client_id;
111         GMutex *mutex;
112 };
113
114 static void irmp_lib_autoinit(void)
115 {
116         if (irmp_lib_initialized)
117                 return;
118
119         irmp_lib_client_id = 0;
120         g_mutex_init(&irmp_lib_mutex);
121
122         irmp_lib_initialized = 1;
123 }
124
125 static size_t irmp_next_client_id(void)
126 {
127         size_t id;
128
129         do {
130                 id = ++irmp_lib_client_id;
131         } while (!id);
132
133         return id;
134 }
135
136 IRMP_DLLEXPORT struct irmp_instance *irmp_instance_alloc(void)
137 {
138         struct irmp_instance *inst;
139
140         irmp_lib_autoinit();
141
142         inst = g_malloc0(sizeof(*inst));
143         if (!inst)
144                 return NULL;
145
146         inst->client_id = irmp_next_client_id();
147         inst->mutex = &irmp_lib_mutex;
148
149         return inst;
150 }
151
152 IRMP_DLLEXPORT void irmp_instance_free(struct irmp_instance *state)
153 {
154
155         irmp_lib_autoinit();
156
157         if (!state)
158                 return;
159
160         g_free(state);
161 }
162
163 IRMP_DLLEXPORT size_t irmp_instance_id(struct irmp_instance *state)
164 {
165
166         irmp_lib_autoinit();
167
168         return state ? state->client_id : 0;
169 }
170
171 IRMP_DLLEXPORT int irmp_instance_lock(struct irmp_instance *state, int wait)
172 {
173         int rc;
174         PyGILState_STATE pyst;
175
176         irmp_lib_autoinit();
177
178         if (!state || !state->mutex)
179                 return -EINVAL;
180
181         pyst = PyGILState_Ensure();
182         Py_BEGIN_ALLOW_THREADS
183         if (wait) {
184                 g_mutex_lock(state->mutex);
185                 rc = 0;
186         } else {
187                 rc = g_mutex_trylock(state->mutex);
188         }
189         Py_END_ALLOW_THREADS
190         PyGILState_Release(pyst);
191         if (rc != 0)
192                 return rc;
193
194         return 0;
195 }
196
197 IRMP_DLLEXPORT void irmp_instance_unlock(struct irmp_instance *state)
198 {
199
200         irmp_lib_autoinit();
201
202         if (!state || !state->mutex)
203                 return;
204
205         g_mutex_unlock(state->mutex);
206 }
207
208 static uint32_t s_end_sample;
209
210 IRMP_DLLEXPORT uint32_t irmp_get_sample_rate(void)
211 {
212         return F_INTERRUPTS;
213 }
214
215 IRMP_DLLEXPORT void irmp_reset_state(void)
216 {
217         size_t i;
218         IRMP_DATA data;
219
220         /*
221          * Provide the equivalent of 1s idle input signal level. Then
222          * drain any potentially accumulated result data. This clears
223          * the internal decoder state.
224          */
225         IRMP_PIN = 0xff;
226         i = F_INTERRUPTS;
227         while (i-- > 0) {
228                 (void)irmp_ISR();
229         }
230         (void)irmp_get_data(&data);
231
232         time_counter = 0;
233         s_startBitSample = 0;
234         s_curSample = 0;
235         s_end_sample = 0;
236
237         /*
238          * TODO This is not the most appropriate location to control the
239          * core logic's verbosity. But out of the public set of library
240          * routines this call is closest to some initialization routine.
241          * The query for compile time parameter values is optional, the
242          * state reset is not. Multiple verbosity setup activities in
243          * the same program lifetime won't harm. This HACK is clearly
244          * preferrable over more fiddling with core logic innards, or
245          * the introduction of yet another DLL routine.
246          */
247         silent = 1;
248         verbose = 0;
249 }
250
251 IRMP_DLLEXPORT int irmp_add_one_sample(int sample)
252 {
253         int ret;
254
255         IRMP_PIN = sample ? 0xff : 0x00;
256         ret = irmp_ISR() ? 1 : 0;
257         s_end_sample = s_curSample++;
258         return ret;
259 }
260
261 IRMP_DLLEXPORT int irmp_get_result_data(struct irmp_result_data *data)
262 {
263         IRMP_DATA d;
264
265         if (!irmp_get_data(&d))
266                 return 0;
267
268         data->address = d.address;
269         data->command = d.command;
270         data->protocol = d.protocol;
271         data->protocol_name = irmp_get_protocol_name(d.protocol);
272         data->flags = d.flags;
273         data->start_sample = s_startBitSample;
274         data->end_sample = s_end_sample;
275         return 1;
276 }
277
278 #if WITH_IRMP_DETECT_BUFFER
279 IRMP_DLLEXPORT struct irmp_result_data irmp_detect_buffer(const uint8_t *buff, size_t len)
280 {
281         struct irmp_result_data ret;
282
283         memset(&ret, 0, sizeof(ret));
284         while (s_curSample < len) {
285                 if (irmp_add_one_sample(buff[s_curSample])) {
286                         irmp_get_result_data(&ret);
287                         return ret;
288                 }
289         }
290         return ret;
291 }
292 #endif
293
294 IRMP_DLLEXPORT const char *irmp_get_protocol_name(uint32_t protocol)
295 {
296         const char *name;
297
298         if (protocol >= ARRAY_SIZE(irmp_protocol_names))
299                 return "unknown";
300         name = irmp_protocol_names[protocol];
301         if (!name || !*name)
302                 return "unknown";
303         return name;
304 }
305
306 static __attribute__((constructor)) void init(void)
307 {
308         irmp_lib_autoinit();
309 }