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 CHANGED
@@ -1,8 +1,19 @@
1
1
  # Changelog
2
- ## [2.2.4] - 2026-007-06
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
- ![Static Badge](https://img.shields.io/badge/rpi--io-_2.2.x_-FF5500?style=flat) ![Static Badge](https://img.shields.io/badge/Nodejs-%3E_23-66cc33?logo=nodedotjs&logoColor=white) ![Static Badge](https://img.shields.io/badge/NPM-%3E_10-CC3534?logo=npm&logoColor=white) ![Static Badge](https://img.shields.io/badge/Raspberry_Pi-Zero2_4B_5B-C51A4A?logo=raspberrypi&logoColor=white) ![Static Badge](https://img.shields.io/badge/OS-Bookworm_Trixie-0D7AB9?style=flat)
2
+ ![Static Badge](https://img.shields.io/badge/rpi--io-_3.y.z_-FF5500?style=flat) ![Static Badge](https://img.shields.io/badge/Nodejs-%3E_23-66cc33?logo=nodedotjs&logoColor=white) ![Static Badge](https://img.shields.io/badge/NPM-%3E_10-CC3534?logo=npm&logoColor=white) ![Static Badge](https://img.shields.io/badge/Raspberry_Pi-Zero2_4B_5B-C51A4A?logo=raspberrypi&logoColor=white) ![Static Badge](https://img.shields.io/badge/OS-Bookworm_Trixie-0D7AB9?style=flat)
3
3
 
4
- Current version is 2.2.x Latest update includes new static utility functions. More details in [CHANGELOG.md](CHANGELOG.md).
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
- #### Example of LED control
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
- ##### Code example
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 v38 - 2025-11-19
3
- // source: claude.ai/chat/f3139163-e976-47a4-8e46-01fee65686f2
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);
@@ -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