rpi-io 1.2.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +428 -252
- package/addon/gpio.c +875 -0
- package/binding.gyp +33 -0
- package/esm/_util.mjs +122 -0
- package/esm/main.mjs +307 -0
- package/history.md +5 -4
- package/package.json +54 -7
- package/script/check-env.sh +192 -0
- package/script/detect-gpiod-version.sh +21 -0
- package/test/benchmark-pwm.js +44 -0
- package/test/benchmark-read.js +37 -0
- package/test/benchmark-write.js +38 -0
- package/test/close-all.js +27 -0
- package/test/duplicate-error.js +29 -0
- package/test/line-configuration.js +16 -0
- package/test/pwm-led.js +36 -0
- package/test/pwm-motor.js +40 -0
- package/test/read.js +30 -0
- package/test/write.js +29 -0
- package/esm/dev.mjs +0 -59
- package/esm/rio.mjs +0 -675
- package/esm/utl.mjs +0 -100
- package/scr/test.js +0 -171
package/addon/gpio.c
ADDED
|
@@ -0,0 +1,875 @@
|
|
|
1
|
+
// -------------------------------------------------------------------
|
|
2
|
+
// RPI-IO: - gpio.c v38 - 2025-11-19
|
|
3
|
+
// source: claude.ai/chat/f3139163-e976-47a4-8e46-01fee65686f2
|
|
4
|
+
// -------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
#include <node_api.h>
|
|
7
|
+
#include <gpiod.h>
|
|
8
|
+
#include <string.h>
|
|
9
|
+
#include <stdio.h>
|
|
10
|
+
#include <stdlib.h>
|
|
11
|
+
#include <pthread.h>
|
|
12
|
+
#include <unistd.h>
|
|
13
|
+
#include <errno.h>
|
|
14
|
+
#include <time.h>
|
|
15
|
+
|
|
16
|
+
// La version de libgpiod est détectée par binding.gyp et passée comme define
|
|
17
|
+
// LIBGPIOD_V2 ou LIBGPIOD_V1
|
|
18
|
+
#if defined(LIBGPIOD_V2)
|
|
19
|
+
// #pragma message "Compiling with libgpiod v2.x API"
|
|
20
|
+
#elif defined(LIBGPIOD_V1)
|
|
21
|
+
// #pragma message "Compiling with libgpiod v1.x API"
|
|
22
|
+
#else
|
|
23
|
+
#error "Cannot detect libgpiod version. Please ensure detect-gpiod-version.sh is executable."
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
// Structure pour stocker les lignes GPIO ouvertes
|
|
27
|
+
typedef struct {
|
|
28
|
+
#ifdef LIBGPIOD_V2
|
|
29
|
+
struct gpiod_chip *chip;
|
|
30
|
+
struct gpiod_line_request *request;
|
|
31
|
+
struct gpiod_line_settings *line_settings;
|
|
32
|
+
struct gpiod_line_config *line_cfg;
|
|
33
|
+
struct gpiod_request_config *req_cfg;
|
|
34
|
+
unsigned int offset;
|
|
35
|
+
#else
|
|
36
|
+
struct gpiod_chip *chip;
|
|
37
|
+
struct gpiod_line *line;
|
|
38
|
+
#endif
|
|
39
|
+
int line_num;
|
|
40
|
+
int is_output;
|
|
41
|
+
int is_closed;
|
|
42
|
+
|
|
43
|
+
// Pour le monitoring
|
|
44
|
+
int is_monitoring;
|
|
45
|
+
pthread_t monitor_thread;
|
|
46
|
+
napi_threadsafe_function tsfn;
|
|
47
|
+
napi_ref callback_ref;
|
|
48
|
+
} gpio_context_t;
|
|
49
|
+
|
|
50
|
+
// Libérer les ressources GPIO
|
|
51
|
+
static void finalize_gpio(napi_env env, void* finalize_data, void* finalize_hint) {
|
|
52
|
+
gpio_context_t *ctx = (gpio_context_t*)finalize_data;
|
|
53
|
+
if (ctx) {
|
|
54
|
+
// Arrêter le monitoring si actif
|
|
55
|
+
if (ctx->is_monitoring) {
|
|
56
|
+
ctx->is_monitoring = 0;
|
|
57
|
+
if (ctx->monitor_thread) {
|
|
58
|
+
pthread_join(ctx->monitor_thread, NULL);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (ctx->tsfn) {
|
|
63
|
+
napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_abort);
|
|
64
|
+
ctx->tsfn = NULL;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Ne pas utiliser callback_ref ici car nous n'avons plus d'environnement valide
|
|
68
|
+
ctx->callback_ref = NULL;
|
|
69
|
+
|
|
70
|
+
// Ne libérer que si pas déjà fermé
|
|
71
|
+
if (!ctx->is_closed) {
|
|
72
|
+
#ifdef LIBGPIOD_V2
|
|
73
|
+
if (ctx->request) {
|
|
74
|
+
gpiod_line_request_release(ctx->request);
|
|
75
|
+
ctx->request = NULL;
|
|
76
|
+
}
|
|
77
|
+
if (ctx->line_settings) {
|
|
78
|
+
gpiod_line_settings_free(ctx->line_settings);
|
|
79
|
+
ctx->line_settings = NULL;
|
|
80
|
+
}
|
|
81
|
+
if (ctx->line_cfg) {
|
|
82
|
+
gpiod_line_config_free(ctx->line_cfg);
|
|
83
|
+
ctx->line_cfg = NULL;
|
|
84
|
+
}
|
|
85
|
+
if (ctx->req_cfg) {
|
|
86
|
+
gpiod_request_config_free(ctx->req_cfg);
|
|
87
|
+
ctx->req_cfg = NULL;
|
|
88
|
+
}
|
|
89
|
+
if (ctx->chip) {
|
|
90
|
+
gpiod_chip_close(ctx->chip);
|
|
91
|
+
ctx->chip = NULL;
|
|
92
|
+
}
|
|
93
|
+
#else
|
|
94
|
+
if (ctx->line) {
|
|
95
|
+
gpiod_line_release(ctx->line);
|
|
96
|
+
ctx->line = NULL;
|
|
97
|
+
}
|
|
98
|
+
if (ctx->chip) {
|
|
99
|
+
gpiod_chip_close(ctx->chip);
|
|
100
|
+
ctx->chip = NULL;
|
|
101
|
+
}
|
|
102
|
+
#endif
|
|
103
|
+
ctx->is_closed = 1;
|
|
104
|
+
}
|
|
105
|
+
free(ctx);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Fonction: GetVersion() - Retourne la version de libgpiod utilisée
|
|
110
|
+
static napi_value GetVersion(napi_env env, napi_callback_info info) {
|
|
111
|
+
napi_value result;
|
|
112
|
+
const char* version;
|
|
113
|
+
|
|
114
|
+
#ifdef LIBGPIOD_V2
|
|
115
|
+
version = gpiod_api_version();
|
|
116
|
+
#else
|
|
117
|
+
version = gpiod_version_string();
|
|
118
|
+
#endif
|
|
119
|
+
|
|
120
|
+
if (version == NULL) {
|
|
121
|
+
version = "unknown";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
napi_status status = napi_create_string_utf8(env, version, NAPI_AUTO_LENGTH, &result);
|
|
125
|
+
if (status != napi_ok) {
|
|
126
|
+
napi_throw_error(env, NULL, "Failed to create version string");
|
|
127
|
+
return NULL;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Fonction: openOutput(chipName, lineNumber, initialValue, bias)
|
|
134
|
+
static napi_value OpenOutput(napi_env env, napi_callback_info info) {
|
|
135
|
+
napi_status status;
|
|
136
|
+
size_t argc = 4;
|
|
137
|
+
napi_value args[4];
|
|
138
|
+
char chip_name[256];
|
|
139
|
+
size_t chip_name_len;
|
|
140
|
+
int line_num, initial_value;
|
|
141
|
+
char bias_str[32] = "disable";
|
|
142
|
+
|
|
143
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
144
|
+
if (status != napi_ok || argc < 2) {
|
|
145
|
+
napi_throw_error(env, NULL, "Expected chipName and lineNumber arguments");
|
|
146
|
+
return NULL;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
status = napi_get_value_string_utf8(env, args[0], chip_name, sizeof(chip_name), &chip_name_len);
|
|
150
|
+
if (status != napi_ok) {
|
|
151
|
+
napi_throw_error(env, NULL, "Invalid chip name");
|
|
152
|
+
return NULL;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
status = napi_get_value_int32(env, args[1], &line_num);
|
|
156
|
+
if (status != napi_ok) {
|
|
157
|
+
napi_throw_error(env, NULL, "Invalid line number");
|
|
158
|
+
return NULL;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (argc >= 3) {
|
|
162
|
+
napi_valuetype valuetype;
|
|
163
|
+
status = napi_typeof(env, args[2], &valuetype);
|
|
164
|
+
if (status == napi_ok && valuetype == napi_number) {
|
|
165
|
+
napi_get_value_int32(env, args[2], &initial_value);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (argc >= 4) {
|
|
170
|
+
napi_valuetype valuetype;
|
|
171
|
+
status = napi_typeof(env, args[3], &valuetype);
|
|
172
|
+
if (status == napi_ok && valuetype == napi_string) {
|
|
173
|
+
napi_get_value_string_utf8(env, args[3], bias_str, sizeof(bias_str), NULL);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
gpio_context_t *ctx = (gpio_context_t*)malloc(sizeof(gpio_context_t));
|
|
178
|
+
if (!ctx) {
|
|
179
|
+
napi_throw_error(env, NULL, "Memory allocation failed");
|
|
180
|
+
return NULL;
|
|
181
|
+
}
|
|
182
|
+
memset(ctx, 0, sizeof(gpio_context_t));
|
|
183
|
+
|
|
184
|
+
ctx->line_num = line_num;
|
|
185
|
+
ctx->is_output = 1;
|
|
186
|
+
ctx->is_closed = 0;
|
|
187
|
+
ctx->is_monitoring = 0;
|
|
188
|
+
ctx->monitor_thread = 0;
|
|
189
|
+
ctx->tsfn = NULL;
|
|
190
|
+
ctx->callback_ref = NULL;
|
|
191
|
+
|
|
192
|
+
#ifdef LIBGPIOD_V2
|
|
193
|
+
ctx->chip = gpiod_chip_open(chip_name);
|
|
194
|
+
if (!ctx->chip) {
|
|
195
|
+
free(ctx);
|
|
196
|
+
napi_throw_error(env, NULL, "Failed to open GPIO chip (v2)");
|
|
197
|
+
return NULL;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
ctx->offset = (unsigned int)line_num;
|
|
201
|
+
|
|
202
|
+
// Créer les structures de configuration
|
|
203
|
+
ctx->line_settings = gpiod_line_settings_new();
|
|
204
|
+
ctx->line_cfg = gpiod_line_config_new();
|
|
205
|
+
ctx->req_cfg = gpiod_request_config_new();
|
|
206
|
+
|
|
207
|
+
if (!ctx->line_settings || !ctx->line_cfg || !ctx->req_cfg) {
|
|
208
|
+
if (ctx->line_settings) gpiod_line_settings_free(ctx->line_settings);
|
|
209
|
+
if (ctx->line_cfg) gpiod_line_config_free(ctx->line_cfg);
|
|
210
|
+
if (ctx->req_cfg) gpiod_request_config_free(ctx->req_cfg);
|
|
211
|
+
gpiod_chip_close(ctx->chip);
|
|
212
|
+
free(ctx);
|
|
213
|
+
napi_throw_error(env, NULL, "Failed to create config structures");
|
|
214
|
+
return NULL;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Configurer les line_settings pour une sortie
|
|
218
|
+
gpiod_line_settings_set_direction(ctx->line_settings, GPIOD_LINE_DIRECTION_OUTPUT);
|
|
219
|
+
gpiod_line_settings_set_output_value(ctx->line_settings,
|
|
220
|
+
initial_value ? GPIOD_LINE_VALUE_ACTIVE : GPIOD_LINE_VALUE_INACTIVE);
|
|
221
|
+
|
|
222
|
+
// Ajouter les settings à la config pour cette ligne spécifique
|
|
223
|
+
int ret = gpiod_line_config_add_line_settings(ctx->line_cfg, &ctx->offset, 1, ctx->line_settings);
|
|
224
|
+
if (ret < 0) {
|
|
225
|
+
gpiod_line_settings_free(ctx->line_settings);
|
|
226
|
+
gpiod_line_config_free(ctx->line_cfg);
|
|
227
|
+
gpiod_request_config_free(ctx->req_cfg);
|
|
228
|
+
gpiod_chip_close(ctx->chip);
|
|
229
|
+
free(ctx);
|
|
230
|
+
napi_throw_error(env, NULL, "Failed to add line settings");
|
|
231
|
+
return NULL;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Configurer le consumer
|
|
235
|
+
gpiod_request_config_set_consumer(ctx->req_cfg, "nodejs-gpio");
|
|
236
|
+
|
|
237
|
+
// Demander la ligne
|
|
238
|
+
ctx->request = gpiod_chip_request_lines(ctx->chip, ctx->req_cfg, ctx->line_cfg);
|
|
239
|
+
if (!ctx->request) {
|
|
240
|
+
gpiod_line_settings_free(ctx->line_settings);
|
|
241
|
+
gpiod_line_config_free(ctx->line_cfg);
|
|
242
|
+
gpiod_request_config_free(ctx->req_cfg);
|
|
243
|
+
gpiod_chip_close(ctx->chip);
|
|
244
|
+
free(ctx);
|
|
245
|
+
napi_throw_error(env, NULL, "Failed to request line as output (v2)");
|
|
246
|
+
return NULL;
|
|
247
|
+
}
|
|
248
|
+
#else
|
|
249
|
+
ctx->chip = gpiod_chip_open(chip_name);
|
|
250
|
+
if (!ctx->chip) {
|
|
251
|
+
free(ctx);
|
|
252
|
+
napi_throw_error(env, NULL, "Failed to open GPIO chip (v1)");
|
|
253
|
+
return NULL;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
ctx->line = gpiod_chip_get_line(ctx->chip, line_num);
|
|
257
|
+
if (!ctx->line) {
|
|
258
|
+
gpiod_chip_close(ctx->chip);
|
|
259
|
+
free(ctx);
|
|
260
|
+
napi_throw_error(env, NULL, "Failed to get GPIO line");
|
|
261
|
+
return NULL;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
int ret = gpiod_line_request_output(ctx->line, "nodejs-gpio", initial_value);
|
|
265
|
+
if (ret < 0) {
|
|
266
|
+
gpiod_chip_close(ctx->chip);
|
|
267
|
+
free(ctx);
|
|
268
|
+
napi_throw_error(env, NULL, "Failed to request line as output (v1)");
|
|
269
|
+
return NULL;
|
|
270
|
+
}
|
|
271
|
+
#endif
|
|
272
|
+
|
|
273
|
+
napi_value external;
|
|
274
|
+
status = napi_create_external(env, ctx, finalize_gpio, NULL, &external);
|
|
275
|
+
if (status != napi_ok) {
|
|
276
|
+
finalize_gpio(env, ctx, NULL);
|
|
277
|
+
napi_throw_error(env, NULL, "Failed to create external");
|
|
278
|
+
return NULL;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return external;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Fonction: openInput(chipName, lineNumber, bias)
|
|
285
|
+
static napi_value OpenInput(napi_env env, napi_callback_info info) {
|
|
286
|
+
napi_status status;
|
|
287
|
+
size_t argc = 3;
|
|
288
|
+
napi_value args[3];
|
|
289
|
+
char chip_name[256];
|
|
290
|
+
size_t chip_name_len;
|
|
291
|
+
int line_num;
|
|
292
|
+
char bias_str[32] = "disable";
|
|
293
|
+
|
|
294
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
295
|
+
if (status != napi_ok || argc < 2) {
|
|
296
|
+
napi_throw_error(env, NULL, "Expected chipName and lineNumber arguments");
|
|
297
|
+
return NULL;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
status = napi_get_value_string_utf8(env, args[0], chip_name, sizeof(chip_name), &chip_name_len);
|
|
301
|
+
if (status != napi_ok) {
|
|
302
|
+
napi_throw_error(env, NULL, "Invalid chip name");
|
|
303
|
+
return NULL;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
status = napi_get_value_int32(env, args[1], &line_num);
|
|
307
|
+
if (status != napi_ok) {
|
|
308
|
+
napi_throw_error(env, NULL, "Invalid line number");
|
|
309
|
+
return NULL;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (argc >= 3) {
|
|
313
|
+
napi_valuetype valuetype;
|
|
314
|
+
status = napi_typeof(env, args[2], &valuetype);
|
|
315
|
+
if (status == napi_ok && valuetype == napi_string) {
|
|
316
|
+
napi_get_value_string_utf8(env, args[2], bias_str, sizeof(bias_str), NULL);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
gpio_context_t *ctx = (gpio_context_t*)malloc(sizeof(gpio_context_t));
|
|
321
|
+
if (!ctx) {
|
|
322
|
+
napi_throw_error(env, NULL, "Memory allocation failed");
|
|
323
|
+
return NULL;
|
|
324
|
+
}
|
|
325
|
+
memset(ctx, 0, sizeof(gpio_context_t));
|
|
326
|
+
|
|
327
|
+
ctx->line_num = line_num;
|
|
328
|
+
ctx->is_output = 0;
|
|
329
|
+
ctx->is_closed = 0;
|
|
330
|
+
ctx->is_monitoring = 0;
|
|
331
|
+
ctx->monitor_thread = 0;
|
|
332
|
+
ctx->tsfn = NULL;
|
|
333
|
+
ctx->callback_ref = NULL;
|
|
334
|
+
|
|
335
|
+
#ifdef LIBGPIOD_V2
|
|
336
|
+
ctx->chip = gpiod_chip_open(chip_name);
|
|
337
|
+
if (!ctx->chip) {
|
|
338
|
+
free(ctx);
|
|
339
|
+
napi_throw_error(env, NULL, "Failed to open GPIO chip (v2)");
|
|
340
|
+
return NULL;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
ctx->offset = (unsigned int)line_num;
|
|
344
|
+
|
|
345
|
+
// Créer les structures de configuration
|
|
346
|
+
ctx->line_settings = gpiod_line_settings_new();
|
|
347
|
+
ctx->line_cfg = gpiod_line_config_new();
|
|
348
|
+
ctx->req_cfg = gpiod_request_config_new();
|
|
349
|
+
|
|
350
|
+
if (!ctx->line_settings || !ctx->line_cfg || !ctx->req_cfg) {
|
|
351
|
+
if (ctx->line_settings) gpiod_line_settings_free(ctx->line_settings);
|
|
352
|
+
if (ctx->line_cfg) gpiod_line_config_free(ctx->line_cfg);
|
|
353
|
+
if (ctx->req_cfg) gpiod_request_config_free(ctx->req_cfg);
|
|
354
|
+
gpiod_chip_close(ctx->chip);
|
|
355
|
+
free(ctx);
|
|
356
|
+
napi_throw_error(env, NULL, "Failed to create config structures");
|
|
357
|
+
return NULL;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Configurer les line_settings pour une entrée
|
|
361
|
+
gpiod_line_settings_set_direction(ctx->line_settings, GPIOD_LINE_DIRECTION_INPUT);
|
|
362
|
+
|
|
363
|
+
// Configurer le bias
|
|
364
|
+
if (strcmp(bias_str, "pull-up") == 0) {
|
|
365
|
+
gpiod_line_settings_set_bias(ctx->line_settings, GPIOD_LINE_BIAS_PULL_UP);
|
|
366
|
+
} else if (strcmp(bias_str, "pull-down") == 0) {
|
|
367
|
+
gpiod_line_settings_set_bias(ctx->line_settings, GPIOD_LINE_BIAS_PULL_DOWN);
|
|
368
|
+
} else {
|
|
369
|
+
gpiod_line_settings_set_bias(ctx->line_settings, GPIOD_LINE_BIAS_DISABLED);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Configurer la détection d'événements (both edges)
|
|
373
|
+
gpiod_line_settings_set_edge_detection(ctx->line_settings, GPIOD_LINE_EDGE_BOTH);
|
|
374
|
+
|
|
375
|
+
// Ajouter les settings à la config pour cette ligne spécifique
|
|
376
|
+
int ret = gpiod_line_config_add_line_settings(ctx->line_cfg, &ctx->offset, 1, ctx->line_settings);
|
|
377
|
+
if (ret < 0) {
|
|
378
|
+
gpiod_line_settings_free(ctx->line_settings);
|
|
379
|
+
gpiod_line_config_free(ctx->line_cfg);
|
|
380
|
+
gpiod_request_config_free(ctx->req_cfg);
|
|
381
|
+
gpiod_chip_close(ctx->chip);
|
|
382
|
+
free(ctx);
|
|
383
|
+
napi_throw_error(env, NULL, "Failed to add line settings");
|
|
384
|
+
return NULL;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Configurer le consumer
|
|
388
|
+
gpiod_request_config_set_consumer(ctx->req_cfg, "nodejs-gpio");
|
|
389
|
+
|
|
390
|
+
// Demander la ligne
|
|
391
|
+
ctx->request = gpiod_chip_request_lines(ctx->chip, ctx->req_cfg, ctx->line_cfg);
|
|
392
|
+
if (!ctx->request) {
|
|
393
|
+
gpiod_line_settings_free(ctx->line_settings);
|
|
394
|
+
gpiod_line_config_free(ctx->line_cfg);
|
|
395
|
+
gpiod_request_config_free(ctx->req_cfg);
|
|
396
|
+
gpiod_chip_close(ctx->chip);
|
|
397
|
+
free(ctx);
|
|
398
|
+
napi_throw_error(env, NULL, "Failed to request line as input (v2)");
|
|
399
|
+
return NULL;
|
|
400
|
+
}
|
|
401
|
+
#else
|
|
402
|
+
ctx->chip = gpiod_chip_open(chip_name);
|
|
403
|
+
if (!ctx->chip) {
|
|
404
|
+
free(ctx);
|
|
405
|
+
napi_throw_error(env, NULL, "Failed to open GPIO chip (v1)");
|
|
406
|
+
return NULL;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
ctx->line = gpiod_chip_get_line(ctx->chip, line_num);
|
|
410
|
+
if (!ctx->line) {
|
|
411
|
+
gpiod_chip_close(ctx->chip);
|
|
412
|
+
free(ctx);
|
|
413
|
+
napi_throw_error(env, NULL, "Failed to get GPIO line");
|
|
414
|
+
return NULL;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Configurer les flags pour libgpiod 1.x
|
|
418
|
+
int flags = 0;
|
|
419
|
+
if (strcmp(bias_str, "pull-up") == 0) {
|
|
420
|
+
flags = GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
|
|
421
|
+
} else if (strcmp(bias_str, "pull-down") == 0) {
|
|
422
|
+
flags = GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
|
|
423
|
+
} else {
|
|
424
|
+
flags = GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Requête avec événements (both edges)
|
|
428
|
+
int ret = gpiod_line_request_both_edges_events_flags(ctx->line, "nodejs-gpio", flags);
|
|
429
|
+
if (ret < 0) {
|
|
430
|
+
// Si gpiod_line_request_both_edges_events_flags n'existe pas (libgpiod < 1.5),
|
|
431
|
+
// essayer sans flags
|
|
432
|
+
ret = gpiod_line_request_both_edges_events(ctx->line, "nodejs-gpio");
|
|
433
|
+
if (ret < 0) {
|
|
434
|
+
gpiod_chip_close(ctx->chip);
|
|
435
|
+
free(ctx);
|
|
436
|
+
napi_throw_error(env, NULL, "Failed to request line as input with events (v1)");
|
|
437
|
+
return NULL;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
#endif
|
|
441
|
+
|
|
442
|
+
napi_value external;
|
|
443
|
+
status = napi_create_external(env, ctx, finalize_gpio, NULL, &external);
|
|
444
|
+
if (status != napi_ok) {
|
|
445
|
+
finalize_gpio(env, ctx, NULL);
|
|
446
|
+
napi_throw_error(env, NULL, "Failed to create external");
|
|
447
|
+
return NULL;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return external;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Thread de monitoring des événements
|
|
454
|
+
static void* monitor_thread_func(void* arg) {
|
|
455
|
+
gpio_context_t *ctx = (gpio_context_t*)arg;
|
|
456
|
+
|
|
457
|
+
#ifdef LIBGPIOD_V2
|
|
458
|
+
struct gpiod_edge_event_buffer *event_buffer = gpiod_edge_event_buffer_new(1);
|
|
459
|
+
if (!event_buffer) return NULL;
|
|
460
|
+
|
|
461
|
+
while (ctx->is_monitoring && !ctx->is_closed) {
|
|
462
|
+
// Timeout de 100ms (100000000 nanosecondes)
|
|
463
|
+
int ret = gpiod_line_request_wait_edge_events(ctx->request, 100000000);
|
|
464
|
+
if (ret > 0) {
|
|
465
|
+
ret = gpiod_line_request_read_edge_events(ctx->request, event_buffer, 1);
|
|
466
|
+
if (ret > 0) {
|
|
467
|
+
struct gpiod_edge_event *event = gpiod_edge_event_buffer_get_event(event_buffer, 0);
|
|
468
|
+
if (event) {
|
|
469
|
+
enum gpiod_edge_event_type edge_type = gpiod_edge_event_get_event_type(event);
|
|
470
|
+
|
|
471
|
+
// Créer les données à passer au callback JavaScript
|
|
472
|
+
int *data = (int*)malloc(sizeof(int));
|
|
473
|
+
if (data) {
|
|
474
|
+
*data = (edge_type == GPIOD_EDGE_EVENT_RISING_EDGE) ? 1 : 0;
|
|
475
|
+
napi_call_threadsafe_function(ctx->tsfn, data, napi_tsfn_blocking);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
} else if (ret < 0 && ret != -ETIMEDOUT) {
|
|
480
|
+
// Erreur autre que timeout
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
gpiod_edge_event_buffer_free(event_buffer);
|
|
486
|
+
#else
|
|
487
|
+
struct gpiod_line_event event;
|
|
488
|
+
struct timespec timeout;
|
|
489
|
+
timeout.tv_sec = 0;
|
|
490
|
+
timeout.tv_nsec = 100000000; // 100ms
|
|
491
|
+
|
|
492
|
+
while (ctx->is_monitoring && !ctx->is_closed) {
|
|
493
|
+
int ret = gpiod_line_event_wait(ctx->line, &timeout);
|
|
494
|
+
if (ret > 0) {
|
|
495
|
+
ret = gpiod_line_event_read(ctx->line, &event);
|
|
496
|
+
if (ret == 0) {
|
|
497
|
+
int *data = (int*)malloc(sizeof(int));
|
|
498
|
+
if (data) {
|
|
499
|
+
*data = (event.event_type == GPIOD_LINE_EVENT_RISING_EDGE) ? 1 : 0;
|
|
500
|
+
napi_call_threadsafe_function(ctx->tsfn, data, napi_tsfn_blocking);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
#endif
|
|
506
|
+
|
|
507
|
+
return NULL;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Callback appelé depuis le thread JavaScript
|
|
511
|
+
static void call_js_callback(napi_env env, napi_value js_callback, void* context, void* data) {
|
|
512
|
+
if (data == NULL) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
int *edge = (int*)data;
|
|
517
|
+
|
|
518
|
+
if (env != NULL && js_callback != NULL) {
|
|
519
|
+
napi_value argv[1];
|
|
520
|
+
napi_status status = napi_create_int32(env, *edge, &argv[0]);
|
|
521
|
+
|
|
522
|
+
if (status == napi_ok) {
|
|
523
|
+
napi_value global;
|
|
524
|
+
status = napi_get_global(env, &global);
|
|
525
|
+
|
|
526
|
+
if (status == napi_ok) {
|
|
527
|
+
napi_value result;
|
|
528
|
+
napi_call_function(env, global, js_callback, 1, argv, &result);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
free(data);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Fonction: startMonitoring(handle, callback)
|
|
537
|
+
static napi_value StartMonitoring(napi_env env, napi_callback_info info) {
|
|
538
|
+
napi_status status;
|
|
539
|
+
size_t argc = 2;
|
|
540
|
+
napi_value args[2];
|
|
541
|
+
gpio_context_t *ctx = NULL;
|
|
542
|
+
|
|
543
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
544
|
+
if (status != napi_ok || argc < 2) {
|
|
545
|
+
napi_throw_error(env, NULL, "Expected handle and callback arguments");
|
|
546
|
+
return NULL;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
status = napi_get_value_external(env, args[0], (void**)&ctx);
|
|
550
|
+
if (status != napi_ok || ctx == NULL) {
|
|
551
|
+
napi_throw_error(env, NULL, "Invalid GPIO handle");
|
|
552
|
+
return NULL;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (ctx->is_closed) {
|
|
556
|
+
napi_throw_error(env, NULL, "GPIO handle has been closed");
|
|
557
|
+
return NULL;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (ctx->is_output) {
|
|
561
|
+
napi_throw_error(env, NULL, "Cannot monitor output GPIO");
|
|
562
|
+
return NULL;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (ctx->is_monitoring) {
|
|
566
|
+
napi_throw_error(env, NULL, "Monitoring already started");
|
|
567
|
+
return NULL;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Créer une threadsafe function
|
|
571
|
+
napi_value async_resource_name;
|
|
572
|
+
napi_create_string_utf8(env, "GPIOMonitor", NAPI_AUTO_LENGTH, &async_resource_name);
|
|
573
|
+
|
|
574
|
+
status = napi_create_threadsafe_function(
|
|
575
|
+
env,
|
|
576
|
+
args[1],
|
|
577
|
+
NULL,
|
|
578
|
+
async_resource_name,
|
|
579
|
+
0,
|
|
580
|
+
1,
|
|
581
|
+
NULL,
|
|
582
|
+
NULL,
|
|
583
|
+
ctx,
|
|
584
|
+
call_js_callback,
|
|
585
|
+
&ctx->tsfn
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
if (status != napi_ok) {
|
|
589
|
+
napi_throw_error(env, NULL, "Failed to create threadsafe function");
|
|
590
|
+
return NULL;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Démarrer le thread de monitoring
|
|
594
|
+
ctx->is_monitoring = 1;
|
|
595
|
+
if (pthread_create(&ctx->monitor_thread, NULL, monitor_thread_func, ctx) != 0) {
|
|
596
|
+
ctx->is_monitoring = 0;
|
|
597
|
+
napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_release);
|
|
598
|
+
ctx->tsfn = NULL;
|
|
599
|
+
napi_throw_error(env, NULL, "Failed to create monitor thread");
|
|
600
|
+
return NULL;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
napi_value result;
|
|
604
|
+
napi_get_undefined(env, &result);
|
|
605
|
+
return result;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Fonction: stopMonitoring(handle)
|
|
609
|
+
static napi_value StopMonitoring(napi_env env, napi_callback_info info) {
|
|
610
|
+
napi_status status;
|
|
611
|
+
size_t argc = 1;
|
|
612
|
+
napi_value args[1];
|
|
613
|
+
gpio_context_t *ctx = NULL;
|
|
614
|
+
|
|
615
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
616
|
+
if (status != napi_ok || argc < 1) {
|
|
617
|
+
napi_throw_error(env, NULL, "Expected handle argument");
|
|
618
|
+
return NULL;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
status = napi_get_value_external(env, args[0], (void**)&ctx);
|
|
622
|
+
if (status != napi_ok || ctx == NULL) {
|
|
623
|
+
napi_value result;
|
|
624
|
+
napi_get_undefined(env, &result);
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (ctx->is_monitoring) {
|
|
629
|
+
ctx->is_monitoring = 0;
|
|
630
|
+
if (ctx->monitor_thread) {
|
|
631
|
+
pthread_join(ctx->monitor_thread, NULL);
|
|
632
|
+
ctx->monitor_thread = 0;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (ctx->tsfn) {
|
|
636
|
+
napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_abort);
|
|
637
|
+
ctx->tsfn = NULL;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
napi_value result;
|
|
642
|
+
napi_get_undefined(env, &result);
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Fonction: write(handle, value)
|
|
647
|
+
static napi_value Write(napi_env env, napi_callback_info info) {
|
|
648
|
+
napi_status status;
|
|
649
|
+
size_t argc = 2;
|
|
650
|
+
napi_value args[2];
|
|
651
|
+
gpio_context_t *ctx;
|
|
652
|
+
int value;
|
|
653
|
+
|
|
654
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
655
|
+
if (status != napi_ok || argc < 2) {
|
|
656
|
+
napi_throw_error(env, NULL, "Expected handle and value arguments");
|
|
657
|
+
return NULL;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
status = napi_get_value_external(env, args[0], (void**)&ctx);
|
|
661
|
+
if (status != napi_ok || !ctx) {
|
|
662
|
+
napi_throw_error(env, NULL, "Invalid GPIO handle");
|
|
663
|
+
return NULL;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (ctx->is_closed) {
|
|
667
|
+
napi_throw_error(env, NULL, "GPIO handle has been closed");
|
|
668
|
+
return NULL;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (!ctx->is_output) {
|
|
672
|
+
napi_throw_error(env, NULL, "GPIO line is not configured as output");
|
|
673
|
+
return NULL;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
status = napi_get_value_int32(env, args[1], &value);
|
|
677
|
+
if (status != napi_ok) {
|
|
678
|
+
napi_throw_error(env, NULL, "Invalid value");
|
|
679
|
+
return NULL;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
#ifdef LIBGPIOD_V2
|
|
683
|
+
enum gpiod_line_value gpio_value = value ? GPIOD_LINE_VALUE_ACTIVE : GPIOD_LINE_VALUE_INACTIVE;
|
|
684
|
+
int ret = gpiod_line_request_set_value(ctx->request, ctx->offset, gpio_value);
|
|
685
|
+
#else
|
|
686
|
+
int ret = gpiod_line_set_value(ctx->line, value ? 1 : 0);
|
|
687
|
+
#endif
|
|
688
|
+
|
|
689
|
+
if (ret < 0) {
|
|
690
|
+
napi_throw_error(env, NULL, "Failed to set GPIO value");
|
|
691
|
+
return NULL;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
napi_value result;
|
|
695
|
+
napi_get_undefined(env, &result);
|
|
696
|
+
return result;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Fonction: read(handle)
|
|
700
|
+
static napi_value Read(napi_env env, napi_callback_info info) {
|
|
701
|
+
napi_status status;
|
|
702
|
+
size_t argc = 1;
|
|
703
|
+
napi_value args[1];
|
|
704
|
+
gpio_context_t *ctx;
|
|
705
|
+
|
|
706
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
707
|
+
if (status != napi_ok || argc < 1) {
|
|
708
|
+
napi_throw_error(env, NULL, "Expected handle argument");
|
|
709
|
+
return NULL;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
status = napi_get_value_external(env, args[0], (void**)&ctx);
|
|
713
|
+
if (status != napi_ok || !ctx) {
|
|
714
|
+
napi_throw_error(env, NULL, "Invalid GPIO handle");
|
|
715
|
+
return NULL;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (ctx->is_closed) {
|
|
719
|
+
napi_throw_error(env, NULL, "GPIO handle has been closed");
|
|
720
|
+
return NULL;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
#ifdef LIBGPIOD_V2
|
|
724
|
+
enum gpiod_line_value gpio_value = gpiod_line_request_get_value(ctx->request, ctx->offset);
|
|
725
|
+
if (gpio_value == GPIOD_LINE_VALUE_ERROR) {
|
|
726
|
+
napi_throw_error(env, NULL, "Failed to read GPIO value (v2)");
|
|
727
|
+
return NULL;
|
|
728
|
+
}
|
|
729
|
+
int value = (gpio_value == GPIOD_LINE_VALUE_ACTIVE) ? 1 : 0;
|
|
730
|
+
#else
|
|
731
|
+
int value = gpiod_line_get_value(ctx->line);
|
|
732
|
+
if (value < 0) {
|
|
733
|
+
napi_throw_error(env, NULL, "Failed to read GPIO value (v1)");
|
|
734
|
+
return NULL;
|
|
735
|
+
}
|
|
736
|
+
#endif
|
|
737
|
+
|
|
738
|
+
napi_value result;
|
|
739
|
+
status = napi_create_int32(env, value, &result);
|
|
740
|
+
if (status != napi_ok) {
|
|
741
|
+
napi_throw_error(env, NULL, "Failed to create return value");
|
|
742
|
+
return NULL;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return result;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Fonction: close(handle)
|
|
749
|
+
static napi_value Close(napi_env env, napi_callback_info info) {
|
|
750
|
+
napi_status status;
|
|
751
|
+
size_t argc = 1;
|
|
752
|
+
napi_value args[1];
|
|
753
|
+
gpio_context_t *ctx = NULL;
|
|
754
|
+
|
|
755
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
756
|
+
if (status != napi_ok || argc < 1) {
|
|
757
|
+
napi_throw_error(env, NULL, "Expected handle argument");
|
|
758
|
+
return NULL;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
status = napi_get_value_external(env, args[0], (void**)&ctx);
|
|
762
|
+
if (status != napi_ok || ctx == NULL) {
|
|
763
|
+
napi_value result;
|
|
764
|
+
napi_get_undefined(env, &result);
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (ctx->is_closed) {
|
|
769
|
+
napi_value result;
|
|
770
|
+
napi_get_undefined(env, &result);
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Arrêter le monitoring si actif
|
|
775
|
+
if (ctx->is_monitoring) {
|
|
776
|
+
ctx->is_monitoring = 0;
|
|
777
|
+
if (ctx->monitor_thread) {
|
|
778
|
+
pthread_join(ctx->monitor_thread, NULL);
|
|
779
|
+
ctx->monitor_thread = 0;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (ctx->tsfn) {
|
|
783
|
+
napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_abort);
|
|
784
|
+
ctx->tsfn = NULL;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
#ifdef LIBGPIOD_V2
|
|
789
|
+
if (ctx->request) {
|
|
790
|
+
gpiod_line_request_release(ctx->request);
|
|
791
|
+
ctx->request = NULL;
|
|
792
|
+
}
|
|
793
|
+
if (ctx->line_settings) {
|
|
794
|
+
gpiod_line_settings_free(ctx->line_settings);
|
|
795
|
+
ctx->line_settings = NULL;
|
|
796
|
+
}
|
|
797
|
+
if (ctx->line_cfg) {
|
|
798
|
+
gpiod_line_config_free(ctx->line_cfg);
|
|
799
|
+
ctx->line_cfg = NULL;
|
|
800
|
+
}
|
|
801
|
+
if (ctx->req_cfg) {
|
|
802
|
+
gpiod_request_config_free(ctx->req_cfg);
|
|
803
|
+
ctx->req_cfg = NULL;
|
|
804
|
+
}
|
|
805
|
+
if (ctx->chip) {
|
|
806
|
+
gpiod_chip_close(ctx->chip);
|
|
807
|
+
ctx->chip = NULL;
|
|
808
|
+
}
|
|
809
|
+
#else
|
|
810
|
+
if (ctx->line) {
|
|
811
|
+
gpiod_line_release(ctx->line);
|
|
812
|
+
ctx->line = NULL;
|
|
813
|
+
}
|
|
814
|
+
if (ctx->chip) {
|
|
815
|
+
gpiod_chip_close(ctx->chip);
|
|
816
|
+
ctx->chip = NULL;
|
|
817
|
+
}
|
|
818
|
+
#endif
|
|
819
|
+
|
|
820
|
+
ctx->is_closed = 1;
|
|
821
|
+
|
|
822
|
+
napi_value result;
|
|
823
|
+
napi_get_undefined(env, &result);
|
|
824
|
+
return result;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Initialisation du module
|
|
828
|
+
static napi_value Init(napi_env env, napi_value exports) {
|
|
829
|
+
napi_status status;
|
|
830
|
+
napi_value fn;
|
|
831
|
+
|
|
832
|
+
status = napi_create_function(env, NULL, 0, GetVersion, NULL, &fn);
|
|
833
|
+
if (status == napi_ok) {
|
|
834
|
+
napi_set_named_property(env, exports, "getVersion", fn);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
status = napi_create_function(env, NULL, 0, OpenOutput, NULL, &fn);
|
|
838
|
+
if (status == napi_ok) {
|
|
839
|
+
napi_set_named_property(env, exports, "openOutput", fn);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
status = napi_create_function(env, NULL, 0, OpenInput, NULL, &fn);
|
|
843
|
+
if (status == napi_ok) {
|
|
844
|
+
napi_set_named_property(env, exports, "openInput", fn);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
status = napi_create_function(env, NULL, 0, Write, NULL, &fn);
|
|
848
|
+
if (status == napi_ok) {
|
|
849
|
+
napi_set_named_property(env, exports, "write", fn);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
status = napi_create_function(env, NULL, 0, Read, NULL, &fn);
|
|
853
|
+
if (status == napi_ok) {
|
|
854
|
+
napi_set_named_property(env, exports, "read", fn);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
status = napi_create_function(env, NULL, 0, StartMonitoring, NULL, &fn);
|
|
858
|
+
if (status == napi_ok) {
|
|
859
|
+
napi_set_named_property(env, exports, "startMonitoring", fn);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
status = napi_create_function(env, NULL, 0, StopMonitoring, NULL, &fn);
|
|
863
|
+
if (status == napi_ok) {
|
|
864
|
+
napi_set_named_property(env, exports, "stopMonitoring", fn);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
status = napi_create_function(env, NULL, 0, Close, NULL, &fn);
|
|
868
|
+
if (status == napi_ok) {
|
|
869
|
+
napi_set_named_property(env, exports, "close", fn);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
return exports;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|