rpi-io 2.2.4 → 3.1.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/CHANGELOG.md +14 -3
- package/README.md +21 -4
- package/addon/gpio.c +435 -4
- package/documentation/api.md +67 -1
- package/documentation/examples.md +3 -0
- package/documentation/stepper-motor.md +192 -0
- package/esm/log.mjs +30 -40
- package/esm/main.mjs +56 -3
- package/extensions/stepper-bipolar.mjs +131 -0
- package/extensions/stepper-unipolar.mjs +172 -0
- package/package.json +6 -2
- package/test/log-warn.js +41 -0
- package/test/pulse.js +43 -0
- package/test/read.js +2 -0
- package/test/stepper-motor-bipolar.js +36 -0
- package/test/stepper-motor-unipolar.js +24 -0
- package/test/write.js +1 -0
- package/esm/uln2003.mjs +0 -115
- package/test/exampleUln2003.js +0 -103
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
## [3.1.0] - 2026-07-22
|
|
4
|
+
### Changed
|
|
5
|
+
- log utility refactored
|
|
6
|
+
### Added
|
|
7
|
+
- extension classes for unipolar and bipolar stepper motors
|
|
8
|
+
|
|
9
|
+
## [3.0.0] - 2026-07-18
|
|
10
|
+
### Added
|
|
11
|
+
- methods **pulseStart**(*count, edge, options*) and **pulseStop**() for output lines
|
|
12
|
+
|
|
13
|
+
## [2.2.4] - 2026-07-06
|
|
3
14
|
### Added
|
|
4
|
-
- Static function RIO.lineIsSupported(*line*)
|
|
5
|
-
- Static function RIO.lineIsAvailable(*line*)
|
|
15
|
+
- Static function **RIO.lineIsSupported**(*line*)
|
|
16
|
+
- Static function **RIO.lineIsAvailable**(*line*)
|
|
6
17
|
### Changed
|
|
7
18
|
- Fixes in License
|
|
8
19
|
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# rpi-io
|
|
2
|
-
    
|
|
3
3
|
|
|
4
|
-
Current version is
|
|
4
|
+
Current version is 3.1.0. This version takes advantage of the *pulseStart()* and *pulseStop()* methods introduced in version 3.0.0 to propose extension classes for stepper motor control. See [extension](https://github.com/gdorbes/rpi-io/tree/master/extensions) and [documentation](https://github.com/gdorbes/rpi-io/tree/master/documentation) folders.
|
|
5
5
|
|
|
6
6
|
**rpi-io** is a lite [ESM](https://nodejs.org/api/esm.html#modules-ecmascript-modules) module for **Node.js** to control **Raspberry Pi** GPIO: access (in, out), input event detection and [PWM](https://en.wikipedia.org/wiki/Pulse-width_modulation) peripheral control.
|
|
7
7
|
|
|
@@ -133,7 +133,7 @@ PLEASE NOTE: In all this document, GPIO line numbers are the BCM ones as defined
|
|
|
133
133
|
|
|
134
134
|
### *OUT* operations
|
|
135
135
|
|
|
136
|
-
####
|
|
136
|
+
#### Examples of LED control
|
|
137
137
|
##### Diagram
|
|
138
138
|
```
|
|
139
139
|
GPIO Pin (Output)
|
|
@@ -150,7 +150,7 @@ GPIO Pin (Output)
|
|
|
150
150
|
─
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
#####
|
|
153
|
+
##### Simple write
|
|
154
154
|
|
|
155
155
|
```javascript
|
|
156
156
|
// Import rpi-io module
|
|
@@ -169,6 +169,23 @@ setTimeout(()=>{
|
|
|
169
169
|
}, 5000)
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
+
##### Pulse sequence
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
import {RIO} from "rpi-io"
|
|
176
|
+
const myOutput = new RIO(17, "output", {value:0})
|
|
177
|
+
|
|
178
|
+
// Slow pulse x10, ascending edge, w = 0.5 s, s = 0.5 s
|
|
179
|
+
myOutput.pulseStart(10, "asc", {
|
|
180
|
+
pulseWidth: 500000,
|
|
181
|
+
spaceWidth: 500000
|
|
182
|
+
}).then(pulseStatus => {
|
|
183
|
+
console.log("pulse status stopped when completed:", pulseStatus)
|
|
184
|
+
// { elapsedMs: 9500.113291, pulsesCompleted: 10, stopped: false }
|
|
185
|
+
myOutput.close()
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
172
189
|
|
|
173
190
|
|
|
174
191
|
### *IN* operations
|
package/addon/gpio.c
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// -------------------------------------------------------------------
|
|
2
|
-
// RPI-IO: - gpio.c
|
|
3
|
-
// source: claude.ai/chat/
|
|
4
|
-
//
|
|
5
|
-
|
|
2
|
+
// RPI-IO: - gpio.c v42 - 2026-07-16
|
|
3
|
+
// Private source grd: https://claude.ai/chat/cf72d4ca-335a-410f-8d0d-c18d0c5a9f12
|
|
4
|
+
// ------------------------------------------------------------------
|
|
6
5
|
#include <node_api.h>
|
|
7
6
|
#include <gpiod.h>
|
|
8
7
|
#include <string.h>
|
|
@@ -45,12 +44,25 @@ typedef struct {
|
|
|
45
44
|
pthread_t monitor_thread;
|
|
46
45
|
napi_threadsafe_function tsfn;
|
|
47
46
|
napi_ref callback_ref;
|
|
47
|
+
|
|
48
|
+
// Pour pulse() : empêche close()/finalize pendant un train en cours
|
|
49
|
+
// (opération exécutée dans le thread pool libuv via napi_async_work)
|
|
50
|
+
volatile int is_pulsing;
|
|
51
|
+
volatile int stop_requested; // positionné par close()/pulseStop() pour arrêter pulse() en cours
|
|
48
52
|
} gpio_context_t;
|
|
49
53
|
|
|
54
|
+
// Déclaration anticipée (définie plus loin, avec le reste du code de pulse()) :
|
|
55
|
+
// utilisée par finalize_gpio ci-dessous.
|
|
56
|
+
static int pulse_force_stop_and_wait(gpio_context_t *ctx, long timeout_ms);
|
|
57
|
+
|
|
50
58
|
// Libérer les ressources GPIO
|
|
51
59
|
static void finalize_gpio(napi_env env, void* finalize_data, void* finalize_hint) {
|
|
52
60
|
gpio_context_t *ctx = (gpio_context_t*)finalize_data;
|
|
53
61
|
if (ctx) {
|
|
62
|
+
// Si un pulse() tourne encore (handle GC sans close() explicite),
|
|
63
|
+
// on force son arrêt avant de libérer les ressources sous ses pieds.
|
|
64
|
+
pulse_force_stop_and_wait(ctx, 500);
|
|
65
|
+
|
|
54
66
|
// Arrêter le monitoring si actif
|
|
55
67
|
if (ctx->is_monitoring) {
|
|
56
68
|
ctx->is_monitoring = 0;
|
|
@@ -745,6 +757,411 @@ static napi_value Read(napi_env env, napi_callback_info info) {
|
|
|
745
757
|
return result;
|
|
746
758
|
}
|
|
747
759
|
|
|
760
|
+
// -------------------------------------------------------------------
|
|
761
|
+
// Fonction: pulse(handle, count, widthUs, spacingUs, polarity)
|
|
762
|
+
//
|
|
763
|
+
// Génère un train de N impulsions sur une ligne déjà ouverte en sortie.
|
|
764
|
+
// - count: nombre d'impulsions
|
|
765
|
+
// - widthUs: largeur d'impulsion en microsecondes
|
|
766
|
+
// - spacingUs: espacement entre impulsions en microsecondes
|
|
767
|
+
// - polarity: 1 = impulsion active à l'état HAUT (front montant)
|
|
768
|
+
// 0 = impulsion active à l'état BAS (front descendant)
|
|
769
|
+
//
|
|
770
|
+
// Exécutée en tâche async_work dans le thread pool libuv pour ne pas
|
|
771
|
+
// bloquer la boucle d'événements JS. Résout une Promise avec la durée
|
|
772
|
+
// réelle écoulée (ms).
|
|
773
|
+
// -------------------------------------------------------------------
|
|
774
|
+
|
|
775
|
+
typedef struct {
|
|
776
|
+
napi_async_work work;
|
|
777
|
+
napi_deferred deferred;
|
|
778
|
+
gpio_context_t *ctx;
|
|
779
|
+
|
|
780
|
+
unsigned int count;
|
|
781
|
+
int64_t width_ns;
|
|
782
|
+
int64_t spacing_ns;
|
|
783
|
+
int polarity; // 1 = actif haut, 0 = actif bas
|
|
784
|
+
|
|
785
|
+
double elapsed_ms;
|
|
786
|
+
unsigned int pulses_completed;
|
|
787
|
+
int stopped_early; // 1 si interrompu par close()
|
|
788
|
+
int error_code; // 0 = ok, sinon voir pulse_error_message()
|
|
789
|
+
} pulse_work_t;
|
|
790
|
+
|
|
791
|
+
// Plage acceptée pour widthUs/spacingUs, en microsecondes.
|
|
792
|
+
// Le plafond (1h) est purement une garde-fou pratique : int64_t ne
|
|
793
|
+
// déborde de toute façon pas avant des millénaires en nanosecondes,
|
|
794
|
+
// mais une valeur aussi grande relève très probablement d'une erreur
|
|
795
|
+
// de l'appelant (unité confondue ms/µs, notamment) plutôt qu'un vrai
|
|
796
|
+
// besoin, et monopoliserait un thread du pool libuv inutilement.
|
|
797
|
+
#define PULSE_MAX_US 3600000000LL // 1 heure en µs
|
|
798
|
+
|
|
799
|
+
static const char* pulse_error_message(int code) {
|
|
800
|
+
switch (code) {
|
|
801
|
+
case 1: return "GPIO handle has been closed";
|
|
802
|
+
case 2: return "GPIO line is not configured as output";
|
|
803
|
+
case 3: return "Failed to set GPIO value during pulse train";
|
|
804
|
+
default: return "Unknown error during pulse()";
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Attente active de haute précision basée sur CLOCK_MONOTONIC.
|
|
809
|
+
// Ne pas utiliser nanosleep()/usleep() ici : leur résolution/latence
|
|
810
|
+
// réelle sur Linux (souvent >50-100µs de jitter) est insuffisante
|
|
811
|
+
// pour des largeurs d'impulsion de quelques microsecondes.
|
|
812
|
+
//
|
|
813
|
+
// Découpée en tranches d'au plus PULSE_STOP_CHECK_NS pour que
|
|
814
|
+
// stop_requested soit détecté rapidement même sur un long espacement,
|
|
815
|
+
// sans quoi close() pourrait devoir attendre toute la durée restante.
|
|
816
|
+
// Retourne 1 si l'attente a été interrompue par une demande d'arrêt.
|
|
817
|
+
#define PULSE_STOP_CHECK_NS 2000000LL // 2 ms
|
|
818
|
+
|
|
819
|
+
static int pulse_busy_wait_ns(int64_t ns, volatile int *stop_requested) {
|
|
820
|
+
if (ns <= 0) return 0;
|
|
821
|
+
|
|
822
|
+
struct timespec start, now;
|
|
823
|
+
clock_gettime(CLOCK_MONOTONIC, &start);
|
|
824
|
+
|
|
825
|
+
long long remaining_ns = ns;
|
|
826
|
+
|
|
827
|
+
while (remaining_ns > 0) {
|
|
828
|
+
long long slice_ns = remaining_ns < PULSE_STOP_CHECK_NS ? remaining_ns : PULSE_STOP_CHECK_NS;
|
|
829
|
+
|
|
830
|
+
// Recalcule la cible de fin de tranche à partir de maintenant
|
|
831
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
832
|
+
long long slice_target_nsec = (long long)now.tv_nsec + slice_ns;
|
|
833
|
+
time_t slice_target_sec = now.tv_sec + (time_t)(slice_target_nsec / 1000000000LL);
|
|
834
|
+
slice_target_nsec %= 1000000000LL;
|
|
835
|
+
|
|
836
|
+
do {
|
|
837
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
838
|
+
} while (now.tv_sec < slice_target_sec ||
|
|
839
|
+
(now.tv_sec == slice_target_sec && now.tv_nsec < slice_target_nsec));
|
|
840
|
+
|
|
841
|
+
if (*stop_requested) {
|
|
842
|
+
return 1;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Temps réellement écoulé depuis le début de l'attente totale
|
|
846
|
+
long long elapsed_ns = (long long)(now.tv_sec - start.tv_sec) * 1000000000LL +
|
|
847
|
+
((long long)now.tv_nsec - (long long)start.tv_nsec);
|
|
848
|
+
remaining_ns = ns - elapsed_ns;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return 0;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Positionne la ligne à la valeur donnée. Retourne 0 si ok, <0 sinon.
|
|
855
|
+
// Réutilise directement le handle déjà ouvert par openOutput() -
|
|
856
|
+
// pas de réouverture de chip/ligne, pour une latence minimale.
|
|
857
|
+
static int pulse_set_value(gpio_context_t *ctx, int value) {
|
|
858
|
+
#ifdef LIBGPIOD_V2
|
|
859
|
+
enum gpiod_line_value gpio_value = value ? GPIOD_LINE_VALUE_ACTIVE : GPIOD_LINE_VALUE_INACTIVE;
|
|
860
|
+
return gpiod_line_request_set_value(ctx->request, ctx->offset, gpio_value);
|
|
861
|
+
#else
|
|
862
|
+
return gpiod_line_set_value(ctx->line, value ? 1 : 0);
|
|
863
|
+
#endif
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Demande l'arrêt d'un pulse() en cours et attend (bloquant, thread JS)
|
|
867
|
+
// sa terminaison effective dans le thread du pool libuv, bornée par
|
|
868
|
+
// timeout_ms par sécurité. Ne fait rien si aucun pulse() n'est en cours.
|
|
869
|
+
// Retourne 1 si un train était effectivement en cours et a été arrêté,
|
|
870
|
+
// 0 si rien n'était en cours.
|
|
871
|
+
static int pulse_force_stop_and_wait(gpio_context_t *ctx, long timeout_ms) {
|
|
872
|
+
if (!ctx->is_pulsing) {
|
|
873
|
+
return 0;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Vu depuis PulseExecute (thread pool libuv) toutes les 2 ms max
|
|
877
|
+
// pendant les attentes, et à chaque itération sinon.
|
|
878
|
+
ctx->stop_requested = 1;
|
|
879
|
+
|
|
880
|
+
struct timespec wait_start, now;
|
|
881
|
+
clock_gettime(CLOCK_MONOTONIC, &wait_start);
|
|
882
|
+
|
|
883
|
+
while (ctx->is_pulsing) {
|
|
884
|
+
usleep(200); // 0.2 ms - relâche le CPU, pas de busy-wait pur ici
|
|
885
|
+
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
886
|
+
long elapsed_ms = (now.tv_sec - wait_start.tv_sec) * 1000L +
|
|
887
|
+
(now.tv_nsec - wait_start.tv_nsec) / 1000000L;
|
|
888
|
+
if (elapsed_ms > timeout_ms) {
|
|
889
|
+
// Ne devrait jamais arriver (vérifications toutes les 2ms max
|
|
890
|
+
// côté PulseExecute). Filet de sécurité pour ne jamais bloquer
|
|
891
|
+
// indéfiniment l'appelant.
|
|
892
|
+
fprintf(stderr,
|
|
893
|
+
"rpi-io: warning - pulse() stop timed out after %ldms\n",
|
|
894
|
+
timeout_ms);
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
ctx->stop_requested = 0;
|
|
900
|
+
return 1;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Exécuté dans le thread pool libuv - jamais sur le thread JS principal
|
|
904
|
+
static void PulseExecute(napi_env env, void* data) {
|
|
905
|
+
pulse_work_t *w = (pulse_work_t*)data;
|
|
906
|
+
gpio_context_t *ctx = w->ctx;
|
|
907
|
+
|
|
908
|
+
if (ctx->is_closed) {
|
|
909
|
+
w->error_code = 1;
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (!ctx->is_output) {
|
|
913
|
+
w->error_code = 2;
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
int active_value = w->polarity ? 1 : 0;
|
|
918
|
+
int idle_value = w->polarity ? 0 : 1;
|
|
919
|
+
|
|
920
|
+
struct timespec t0, t1;
|
|
921
|
+
clock_gettime(CLOCK_MONOTONIC, &t0);
|
|
922
|
+
|
|
923
|
+
unsigned int i;
|
|
924
|
+
for (i = 0; i < w->count; i++) {
|
|
925
|
+
if (ctx->stop_requested) {
|
|
926
|
+
w->stopped_early = 1;
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (pulse_set_value(ctx, active_value) < 0) {
|
|
931
|
+
w->error_code = 3;
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (pulse_busy_wait_ns(w->width_ns, &ctx->stop_requested)) {
|
|
936
|
+
w->stopped_early = 1;
|
|
937
|
+
// On coupe immédiatement l'impulsion avant de sortir
|
|
938
|
+
pulse_set_value(ctx, idle_value);
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (pulse_set_value(ctx, idle_value) < 0) {
|
|
943
|
+
w->error_code = 3;
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (i < w->count - 1) {
|
|
948
|
+
if (pulse_busy_wait_ns(w->spacing_ns, &ctx->stop_requested)) {
|
|
949
|
+
w->stopped_early = 1;
|
|
950
|
+
break;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// Sécurité : quelle que soit la raison de sortie, s'assurer que la
|
|
956
|
+
// ligne est bien au repos avant de rendre la main.
|
|
957
|
+
if (w->error_code == 0) {
|
|
958
|
+
pulse_set_value(ctx, idle_value);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
w->pulses_completed = i < w->count ? i : w->count;
|
|
962
|
+
|
|
963
|
+
clock_gettime(CLOCK_MONOTONIC, &t1);
|
|
964
|
+
w->elapsed_ms = (double)(t1.tv_sec - t0.tv_sec) * 1000.0 +
|
|
965
|
+
(double)(t1.tv_nsec - t0.tv_nsec) / 1e6;
|
|
966
|
+
|
|
967
|
+
// IMPORTANT: is_pulsing doit être remis à 0 ICI, dans le thread du
|
|
968
|
+
// pool libuv, et non dans PulseComplete (qui s'exécute sur le thread
|
|
969
|
+
// JS principal). close()/pulseStop() bloquent volontairement le
|
|
970
|
+
// thread JS en attendant is_pulsing == 0 : si ce flag n'était remis
|
|
971
|
+
// à 0 que par PulseComplete, on aurait un interblocage (le thread JS
|
|
972
|
+
// bloqué empêcherait sa propre boucle d'événements de déclencher
|
|
973
|
+
// PulseComplete).
|
|
974
|
+
ctx->is_pulsing = 0;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Exécuté de retour sur le thread JS - résout ou rejette la Promise
|
|
978
|
+
static void PulseComplete(napi_env env, napi_status status, void* data) {
|
|
979
|
+
pulse_work_t *w = (pulse_work_t*)data;
|
|
980
|
+
|
|
981
|
+
// Note: ctx->is_pulsing a déjà été remis à 0 par PulseExecute
|
|
982
|
+
// (voir commentaire à la fin de PulseExecute pour la raison).
|
|
983
|
+
|
|
984
|
+
if (status != napi_ok || w->error_code != 0) {
|
|
985
|
+
napi_value err;
|
|
986
|
+
const char *msg = (status != napi_ok)
|
|
987
|
+
? "Async work failed"
|
|
988
|
+
: pulse_error_message(w->error_code);
|
|
989
|
+
napi_create_string_utf8(env, msg, NAPI_AUTO_LENGTH, &err);
|
|
990
|
+
napi_reject_deferred(env, w->deferred, err);
|
|
991
|
+
} else {
|
|
992
|
+
napi_value result, elapsed, completed, stopped;
|
|
993
|
+
napi_create_object(env, &result);
|
|
994
|
+
napi_create_double(env, w->elapsed_ms, &elapsed);
|
|
995
|
+
napi_create_uint32(env, w->pulses_completed, &completed);
|
|
996
|
+
napi_get_boolean(env, w->stopped_early, &stopped);
|
|
997
|
+
|
|
998
|
+
napi_set_named_property(env, result, "elapsedMs", elapsed);
|
|
999
|
+
napi_set_named_property(env, result, "pulsesCompleted", completed);
|
|
1000
|
+
napi_set_named_property(env, result, "stopped", stopped);
|
|
1001
|
+
|
|
1002
|
+
napi_resolve_deferred(env, w->deferred, result);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
napi_delete_async_work(env, w->work);
|
|
1006
|
+
free(w);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
static napi_value Pulse(napi_env env, napi_callback_info info) {
|
|
1010
|
+
napi_status status;
|
|
1011
|
+
size_t argc = 5;
|
|
1012
|
+
napi_value args[5];
|
|
1013
|
+
gpio_context_t *ctx = NULL;
|
|
1014
|
+
int32_t count, polarity;
|
|
1015
|
+
int64_t width_us, spacing_us;
|
|
1016
|
+
|
|
1017
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
1018
|
+
if (status != napi_ok || argc < 5) {
|
|
1019
|
+
napi_throw_error(env, NULL,
|
|
1020
|
+
"Expected (handle, count, widthUs, spacingUs, polarity) arguments");
|
|
1021
|
+
return NULL;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
status = napi_get_value_external(env, args[0], (void**)&ctx);
|
|
1025
|
+
if (status != napi_ok || !ctx) {
|
|
1026
|
+
napi_throw_error(env, NULL, "Invalid GPIO handle");
|
|
1027
|
+
return NULL;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (ctx->is_closed) {
|
|
1031
|
+
napi_throw_error(env, NULL, "GPIO handle has been closed");
|
|
1032
|
+
return NULL;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (!ctx->is_output) {
|
|
1036
|
+
napi_throw_error(env, NULL, "GPIO line is not configured as output");
|
|
1037
|
+
return NULL;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
if (ctx->is_pulsing) {
|
|
1041
|
+
napi_throw_error(env, NULL, "A pulse() train is already in progress on this handle");
|
|
1042
|
+
return NULL;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
if (napi_get_value_int32(env, args[1], &count) != napi_ok || count <= 0) {
|
|
1046
|
+
napi_throw_error(env, NULL, "Invalid count: must be a positive integer");
|
|
1047
|
+
return NULL;
|
|
1048
|
+
}
|
|
1049
|
+
if (napi_get_value_int64(env, args[2], &width_us) != napi_ok ||
|
|
1050
|
+
width_us < 0 || width_us > PULSE_MAX_US) {
|
|
1051
|
+
napi_throw_error(env, NULL,
|
|
1052
|
+
"Invalid widthUs: must be an integer in [0, 3600000000] (0 to 1h in microseconds)");
|
|
1053
|
+
return NULL;
|
|
1054
|
+
}
|
|
1055
|
+
if (napi_get_value_int64(env, args[3], &spacing_us) != napi_ok ||
|
|
1056
|
+
spacing_us < 0 || spacing_us > PULSE_MAX_US) {
|
|
1057
|
+
napi_throw_error(env, NULL,
|
|
1058
|
+
"Invalid spacingUs: must be an integer in [0, 3600000000] (0 to 1h in microseconds)");
|
|
1059
|
+
return NULL;
|
|
1060
|
+
}
|
|
1061
|
+
if (napi_get_value_int32(env, args[4], &polarity) != napi_ok) {
|
|
1062
|
+
napi_throw_error(env, NULL, "Invalid polarity: must be 0 or 1");
|
|
1063
|
+
return NULL;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
pulse_work_t *w = (pulse_work_t*)malloc(sizeof(pulse_work_t));
|
|
1067
|
+
if (!w) {
|
|
1068
|
+
napi_throw_error(env, NULL, "Memory allocation failed");
|
|
1069
|
+
return NULL;
|
|
1070
|
+
}
|
|
1071
|
+
memset(w, 0, sizeof(pulse_work_t));
|
|
1072
|
+
|
|
1073
|
+
w->ctx = ctx;
|
|
1074
|
+
w->count = (unsigned int)count;
|
|
1075
|
+
w->width_ns = width_us * 1000LL;
|
|
1076
|
+
w->spacing_ns = spacing_us * 1000LL;
|
|
1077
|
+
w->polarity = polarity ? 1 : 0;
|
|
1078
|
+
|
|
1079
|
+
napi_value promise;
|
|
1080
|
+
status = napi_create_promise(env, &w->deferred, &promise);
|
|
1081
|
+
if (status != napi_ok) {
|
|
1082
|
+
free(w);
|
|
1083
|
+
napi_throw_error(env, NULL, "Failed to create promise");
|
|
1084
|
+
return NULL;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
napi_value resource_name;
|
|
1088
|
+
napi_create_string_utf8(env, "GPIOPulse", NAPI_AUTO_LENGTH, &resource_name);
|
|
1089
|
+
|
|
1090
|
+
status = napi_create_async_work(env, NULL, resource_name,
|
|
1091
|
+
PulseExecute, PulseComplete, w, &w->work);
|
|
1092
|
+
if (status != napi_ok) {
|
|
1093
|
+
free(w);
|
|
1094
|
+
napi_throw_error(env, NULL, "Failed to create async work");
|
|
1095
|
+
return NULL;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
ctx->is_pulsing = 1;
|
|
1099
|
+
ctx->stop_requested = 0;
|
|
1100
|
+
|
|
1101
|
+
status = napi_queue_async_work(env, w->work);
|
|
1102
|
+
if (status != napi_ok) {
|
|
1103
|
+
ctx->is_pulsing = 0;
|
|
1104
|
+
napi_delete_async_work(env, w->work);
|
|
1105
|
+
free(w);
|
|
1106
|
+
napi_throw_error(env, NULL, "Failed to queue async work");
|
|
1107
|
+
return NULL;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
return promise;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// -------------------------------------------------------------------
|
|
1114
|
+
// Fonction: pulseStop(handle)
|
|
1115
|
+
//
|
|
1116
|
+
// Interrompt immédiatement un train d'impulsions en cours sur ce
|
|
1117
|
+
// handle, s'il y en a un. Contrairement à close(), le handle reste
|
|
1118
|
+
// ouvert et utilisable ensuite (write, read, ou un nouveau pulse()).
|
|
1119
|
+
//
|
|
1120
|
+
// Cas d'usage typique : butée de fin de course sur un moteur pas à
|
|
1121
|
+
// pas - un GPIO d'entrée en interruption (startMonitoring) appelle
|
|
1122
|
+
// pulseStop() sur le GPIO de pas pour arrêter net le mouvement.
|
|
1123
|
+
//
|
|
1124
|
+
// Fonction SYNCHRONE et BLOQUANTE (bornée à ~500ms de sécurité, mais
|
|
1125
|
+
// l'arrêt réel prend au maximum quelques millisecondes) : au retour,
|
|
1126
|
+
// la ligne est garantie au repos et plus aucune impulsion ne sera
|
|
1127
|
+
// émise. C'est volontaire : pour une butée de fin de course, le code
|
|
1128
|
+
// appelant a besoin de cette garantie avant de décider de la suite
|
|
1129
|
+
// (ex: inverser le sens de rotation) sans risque de course avec une
|
|
1130
|
+
// dernière impulsion encore en vol.
|
|
1131
|
+
//
|
|
1132
|
+
// Retourne un booléen : true si un train était en cours et a été
|
|
1133
|
+
// arrêté, false si rien n'était en cours (no-op).
|
|
1134
|
+
// -------------------------------------------------------------------
|
|
1135
|
+
static napi_value PulseStop(napi_env env, napi_callback_info info) {
|
|
1136
|
+
napi_status status;
|
|
1137
|
+
size_t argc = 1;
|
|
1138
|
+
napi_value args[1];
|
|
1139
|
+
gpio_context_t *ctx = NULL;
|
|
1140
|
+
|
|
1141
|
+
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
1142
|
+
if (status != napi_ok || argc < 1) {
|
|
1143
|
+
napi_throw_error(env, NULL, "Expected handle argument");
|
|
1144
|
+
return NULL;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
status = napi_get_value_external(env, args[0], (void**)&ctx);
|
|
1148
|
+
if (status != napi_ok || !ctx) {
|
|
1149
|
+
napi_throw_error(env, NULL, "Invalid GPIO handle");
|
|
1150
|
+
return NULL;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (ctx->is_closed) {
|
|
1154
|
+
napi_throw_error(env, NULL, "GPIO handle has been closed");
|
|
1155
|
+
return NULL;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
int was_stopped = pulse_force_stop_and_wait(ctx, 500);
|
|
1159
|
+
|
|
1160
|
+
napi_value result;
|
|
1161
|
+
napi_get_boolean(env, was_stopped, &result);
|
|
1162
|
+
return result;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
748
1165
|
// Fonction: close(handle)
|
|
749
1166
|
static napi_value Close(napi_env env, napi_callback_info info) {
|
|
750
1167
|
napi_status status;
|
|
@@ -771,6 +1188,10 @@ static napi_value Close(napi_env env, napi_callback_info info) {
|
|
|
771
1188
|
return result;
|
|
772
1189
|
}
|
|
773
1190
|
|
|
1191
|
+
// Force l'arrêt d'un pulse() en cours et attend sa terminaison réelle
|
|
1192
|
+
// avant de libérer les ressources GPIO sous ses pieds.
|
|
1193
|
+
pulse_force_stop_and_wait(ctx, 500);
|
|
1194
|
+
|
|
774
1195
|
// Arrêter le monitoring si actif
|
|
775
1196
|
if (ctx->is_monitoring) {
|
|
776
1197
|
ctx->is_monitoring = 0;
|
|
@@ -854,6 +1275,16 @@ static napi_value Init(napi_env env, napi_value exports) {
|
|
|
854
1275
|
napi_set_named_property(env, exports, "read", fn);
|
|
855
1276
|
}
|
|
856
1277
|
|
|
1278
|
+
status = napi_create_function(env, NULL, 0, Pulse, NULL, &fn);
|
|
1279
|
+
if (status == napi_ok) {
|
|
1280
|
+
napi_set_named_property(env, exports, "pulse", fn);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
status = napi_create_function(env, NULL, 0, PulseStop, NULL, &fn);
|
|
1284
|
+
if (status == napi_ok) {
|
|
1285
|
+
napi_set_named_property(env, exports, "pulseStop", fn);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
857
1288
|
status = napi_create_function(env, NULL, 0, StartMonitoring, NULL, &fn);
|
|
858
1289
|
if (status == napi_ok) {
|
|
859
1290
|
napi_set_named_property(env, exports, "startMonitoring", fn);
|
package/documentation/api.md
CHANGED
|
@@ -84,6 +84,73 @@ myOuput.write(1)
|
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
### pulseStart(count, edge, opt)
|
|
88
|
+
|
|
89
|
+
To start a pulse sequence to "output" instance.
|
|
90
|
+
|
|
91
|
+
#### Example
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
import {RIO} from "rpi-io"
|
|
95
|
+
const myOutput = new RIO(17, "output", {value:0})
|
|
96
|
+
|
|
97
|
+
// Slow pulse x10, ascending edge, w = 0.5 s, s = 0.5 s
|
|
98
|
+
myOutput.pulseStart(10, "asc", {
|
|
99
|
+
pulseWidth: 500000,
|
|
100
|
+
spaceWidth: 500000
|
|
101
|
+
}).then(pulseStatus => {
|
|
102
|
+
console.log("pulse status stopped when completed:", pulseStatus)
|
|
103
|
+
// { elapsedMs: 9500.113291, pulsesCompleted: 10, stopped: false }
|
|
104
|
+
myOutput.close()
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Parameter(s)
|
|
109
|
+
|
|
110
|
+
- **count** *{Number}*
|
|
111
|
+
- **edge** *{"asc'"|'"desc"}* Ascending or descendig edge
|
|
112
|
+
- **options** *{Object}* Pulse options. See default values below
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
{
|
|
116
|
+
pulseWidth: 10, // pulse width in µs
|
|
117
|
+
spaceWidth: 10 // space between pulses in µs
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### Return
|
|
122
|
+
|
|
123
|
+
*{Promise<{`elapsedMs`: number, `pulsesCompleted`: number, `stopped`: boolean}>}*
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
### pulseStop()
|
|
128
|
+
|
|
129
|
+
To stop a pulse sequence on an "output" instance. This function does not return an error is no pulse sequence is active.
|
|
130
|
+
|
|
131
|
+
#### Example
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
import {RIO, sleep} from "rpi-io"
|
|
135
|
+
const myOutput = new RIO(17, "output", {value:1})
|
|
136
|
+
|
|
137
|
+
// Fast pulse x1000, descending edge, w = 5µs, s = 5µs
|
|
138
|
+
myOutput.pulseStart(1000, "desc", {
|
|
139
|
+
pulseWidth: 5,
|
|
140
|
+
spaceWidth: 5
|
|
141
|
+
}).then(pulseStatus => {
|
|
142
|
+
console.log("pulse status stopped when completed or stopped:", pulseStatus)
|
|
143
|
+
// { elapsedMs: 5.283217, pulsesCompleted: 456, stopped: true }
|
|
144
|
+
myOutput.write(0) // Reset line
|
|
145
|
+
myOutput.close()
|
|
146
|
+
})
|
|
147
|
+
await sleep(5)
|
|
148
|
+
myOutput.pulseStop() // Stop pulse train after 5ms
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
####
|
|
153
|
+
|
|
87
154
|
### read()
|
|
88
155
|
|
|
89
156
|
To read value from "input" instance.
|
|
@@ -254,7 +321,6 @@ log("This is useful info")
|
|
|
254
321
|
```
|
|
255
322
|
|
|
256
323
|
|
|
257
|
-
|
|
258
324
|
### sleep(time)
|
|
259
325
|
|
|
260
326
|
Similar to `setTimeout` but in async mode.
|
|
@@ -5,6 +5,9 @@ To get familiar with the **rpi-io**, you might have a glance on examples and pla
|
|
|
5
5
|
# Simple write
|
|
6
6
|
node /your-project/node_modules/rpi-io/test/write.js
|
|
7
7
|
|
|
8
|
+
# Pulse sequences
|
|
9
|
+
node /your-project/node_modules/rpi-io/test/pulse.js
|
|
10
|
+
|
|
8
11
|
# Read and monitor
|
|
9
12
|
node /your-project/node_modules/rpi-io/test/read.js
|
|
10
13
|
|