summaryrefslogtreecommitdiff
path: root/irmp/irmp-main-sharedlib.c
blob: 4d02460014baa4687429bc8cd7a79ad0261c8247 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/*
 * irmp-main-sharedlib.c
 *
 * Copyright (c) 2009-2019 Frank Meyer - frank(at)fli4l.de
 * Copyright (c) 2009-2019 René Staffen - r.staffen(at)gmx.de
 * Copyright (c) 2020-2021 Gerhard Sittig <gerhard.sittig@gmx.net>
 *
 * 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.
 */

/*
 * Declare the library's public API first. Prove it's consistent and
 * complete as a standalone header file.
 */
#include "irmp-main-sharedlib.h"

#include <errno.h>
#include <glib.h>
#include <Python.h>
#include <stdlib.h>
#include <string.h>

/*
 * Include the IRMP core logic. This approach is required because of
 * static variables which hold internal state. The core logic started
 * as an MCU project where resources are severely constrained.
 *
 * This libsigrokdecode incarnation of IRMP will always be used in the
 * UNIX_OR_WINDOWS configuration. But libtool(1) breaks the upstream
 * logic's platform detection. Check reliably available conditions here
 * and provide expected symbols to the library, to reduce changes to the
 * upstream project.
 */
#if defined _WIN32
#  if !defined WIN32
#    define WIN32
#  endif
#else
#  if !defined unix
#    define unix
#  endif
#endif
#include "irmp.h"
#include "irmp.c"

/*
 * The remaining source code implements the PC library, which accepts
 * sample data from API callers, and provides detector results as they
 * become available after seeing input data.
 *
 * TODO items, known constraints
 * - Counters in the IRMP core logic and the library wrapper are 32bit
 *   only. In the strictest sense they only need to cover the span of
 *   an IR frame. In the PC side library case they need to cover "a
 *   detection phase", which happens to be under calling applications'
 *   control. The library shall not mess with the core's internal state,
 *   and may even not be able to reliably tell whether detection of a
 *   frame started in the core. Fortunately the 32bit counters only roll
 *   over after some 2.5 days at the highest available sample rate. So
 *   this limitation is not a blocker.
 * - The IRMP core keeps internal state in global variables. Which is
 *   appropriate for MCU configurations. For the PC library use case
 *   this constraint prevents concurrency, only a single data stream
 *   can get processed at any time. This limitation can get addressed
 *   later, making the flexible and featureful IRMP detection available
 *   in the first place is considered highly desirable, and is a great
 *   improvement in itself.
 * - The detection of IR frames from buffered data is both limited and
 *   complicated at the same time. The routine re-uses the caller's
 *   buffer _and_ internal state across multiple calls. Thus windowed
 *   operation over a larger set of input data is not available. The
 *   API lacks a flag for failed detection, thus applications need to
 *   guess from always returned payload data.
 * - Is it worth adding a "detection in progress" query to the API? Is
 *   the information available to the library wrapper, and reliable?
 *   Shall applications be able to "poll" the started, and completed
 *   state for streamed operation including periodic state resets which
 *   won't interfere with pending detection? (It's assumed that this
 *   is only required when feeding single values in individual calls is
 *   found to be rather expensive.
 * - Some of the result data reflects the core's internal presentation
 *   while there is no declaration in the library's API. This violates
 *   API layers, and needs to get addressed properly.
 * - The IRMP core logic (strictly speaking the specific details of
 *   preprocessor symbol arrangements in the current implementation)
 *   appears to assume either to run on an MCU and capture IR signals
 *   from hardware pins, falling back to AVR if no other platform got
 *   detected. Or assumes to run on a (desktop) PC, and automatically
 *   enables ANALYZE mode, which results in lots of stdio traffic that
 *   is undesirable for application code which uses the shared library
 *   for strict detection purposes but no further analysis or research.
 *   It's a pity that turning off ANALYZE switches to MCU mode, and that
 *   keeping ANALYZE enabled but silencing the output is rather messy
 *   and touches the innards of the core logic (the irmp.c source file
 *   and its dependency header files).
 */

#ifndef ARRAY_SIZE
#  define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#endif

static int irmp_lib_initialized;
static size_t irmp_lib_client_id;
static GMutex irmp_lib_mutex;

struct irmp_instance {
	size_t client_id;
	GMutex *mutex;
};

static void irmp_lib_autoinit(void)
{
	if (irmp_lib_initialized)
		return;

	irmp_lib_client_id = 0;
	g_mutex_init(&irmp_lib_mutex);

	irmp_lib_initialized = 1;
}

static size_t irmp_next_client_id(void)
{
	size_t id;

	do {
		id = ++irmp_lib_client_id;
	} while (!id);

	return id;
}

IRMP_DLLEXPORT struct irmp_instance *irmp_instance_alloc(void)
{
	struct irmp_instance *inst;

	irmp_lib_autoinit();

	inst = g_malloc0(sizeof(*inst));
	if (!inst)
		return NULL;

	inst->client_id = irmp_next_client_id();
	inst->mutex = &irmp_lib_mutex;

	return inst;
}

IRMP_DLLEXPORT void irmp_instance_free(struct irmp_instance *state)
{

	irmp_lib_autoinit();

	if (!state)
		return;

	g_free(state);
}

IRMP_DLLEXPORT size_t irmp_instance_id(struct irmp_instance *state)
{

	irmp_lib_autoinit();

	return state ? state->client_id : 0;
}

IRMP_DLLEXPORT int irmp_instance_lock(struct irmp_instance *state, int wait)
{
	int rc;
	PyGILState_STATE pyst;

	irmp_lib_autoinit();

	if (!state || !state->mutex)
		return -EINVAL;

	pyst = PyGILState_Ensure();
	Py_BEGIN_ALLOW_THREADS
	if (wait) {
		g_mutex_lock(state->mutex);
		rc = 0;
	} else {
		rc = g_mutex_trylock(state->mutex);
	}
	Py_END_ALLOW_THREADS
	PyGILState_Release(pyst);
	if (rc != 0)
		return rc;

	return 0;
}

IRMP_DLLEXPORT void irmp_instance_unlock(struct irmp_instance *state)
{

	irmp_lib_autoinit();

	if (!state || !state->mutex)
		return;

	g_mutex_unlock(state->mutex);
}

static uint32_t s_end_sample;

IRMP_DLLEXPORT uint32_t irmp_get_sample_rate(void)
{
	return F_INTERRUPTS;
}

IRMP_DLLEXPORT void irmp_reset_state(void)
{
	size_t i;
	IRMP_DATA data;

	/*
	 * Provide the equivalent of 1s idle input signal level. Then
	 * drain any potentially accumulated result data. This clears
	 * the internal decoder state.
	 */
	IRMP_PIN = 0xff;
	i = F_INTERRUPTS;
	while (i-- > 0) {
		(void)irmp_ISR();
	}
	(void)irmp_get_data(&data);

	time_counter = 0;
	s_startBitSample = 0;
	s_curSample = 0;
	s_end_sample = 0;

	/*
	 * TODO This is not the most appropriate location to control the
	 * core logic's verbosity. But out of the public set of library
	 * routines this call is closest to some initialization routine.
	 * The query for compile time parameter values is optional, the
	 * state reset is not. Multiple verbosity setup activities in
	 * the same program lifetime won't harm. This HACK is clearly
	 * preferrable over more fiddling with core logic innards, or
	 * the introduction of yet another DLL routine.
	 */
	silent = 1;
	verbose = 0;
}

IRMP_DLLEXPORT int irmp_add_one_sample(int sample)
{
	int ret;

	IRMP_PIN = sample ? 0xff : 0x00;
	ret = irmp_ISR() ? 1 : 0;
	s_end_sample = s_curSample++;
	return ret;
}

IRMP_DLLEXPORT int irmp_get_result_data(struct irmp_result_data *data)
{
	IRMP_DATA d;

	if (!irmp_get_data(&d))
		return 0;

	data->address = d.address;
	data->command = d.command;
	data->protocol = d.protocol;
	data->protocol_name = irmp_get_protocol_name(d.protocol);
	data->flags = d.flags;
	data->start_sample = s_startBitSample;
	data->end_sample = s_end_sample;
	return 1;
}

#if WITH_IRMP_DETECT_BUFFER
IRMP_DLLEXPORT struct irmp_result_data irmp_detect_buffer(const uint8_t *buff, size_t len)
{
	struct irmp_result_data ret;

	memset(&ret, 0, sizeof(ret));
	while (s_curSample < len) {
		if (irmp_add_one_sample(buff[s_curSample])) {
			irmp_get_result_data(&ret);
			return ret;
		}
	}
	return ret;
}
#endif

IRMP_DLLEXPORT const char *irmp_get_protocol_name(uint32_t protocol)
{
	const char *name;

	if (protocol >= ARRAY_SIZE(irmp_protocol_names))
		return "unknown";
	name = irmp_protocol_names[protocol];
	if (!name || !*name)
		return "unknown";
	return name;
}

static __attribute__((constructor)) void init(void)
{
	irmp_lib_autoinit();
}