rpi-io 2.2.4 → 3.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/CHANGELOG.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # Changelog
2
- ## [2.2.4] - 2026-007-06
2
+ ## [3.0.0] - 2026-07-18
3
3
  ### Added
4
- - Static function RIO.lineIsSupported(*line*)
5
- - Static function RIO.lineIsAvailable(*line*)
4
+ - methods **pulseStart**(*count, edge, options*) and **pulseStop**() for output lines
5
+
6
+ ## [2.2.4] - 2026-07-06
7
+ ### Added
8
+ - Static function **RIO.lineIsSupported**(*line*)
9
+ - Static function **RIO.lineIsAvailable**(*line*)
6
10
  ### Changed
7
11
  - Fixes in License
8
12
 
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.0.0. The latest update introduces pulse sequence management methods for output lines. See [CHANGELOG.md](CHANGELOG.md) and documentation folder.
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 beween 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.
@@ -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
 
package/esm/main.mjs CHANGED
@@ -104,7 +104,7 @@ export class RIO {
104
104
  throw new Error("This line is not setup as PWM (see README)")
105
105
 
106
106
  if (opt.period < 0.1 || opt.period > 1000000)
107
- throw new Error("PWM period is out of range (100ns - 1s)")
107
+ throw new RangeError("PWM period is out of range (100ns - 1s)")
108
108
 
109
109
  // Normalize PWM values in ns
110
110
  this.period = opt.period * 1000
@@ -190,7 +190,7 @@ export class RIO {
190
190
  throw new Error("Cannot write to this GPIO mode:", this.mode)
191
191
 
192
192
  if ([0, 1].indexOf(value) === -1)
193
- throw new Error("Value must be either 0 or 1")
193
+ throw new RangeError("Value must be either 0 or 1")
194
194
 
195
195
  ADDON.write(this.handle, value)
196
196
  }
@@ -306,12 +306,65 @@ export class RIO {
306
306
  throw new Error("Duty of PWM line " + this.line + " cannot be updated")
307
307
 
308
308
  if (percent < 0 || percent > 100 || typeof percent !== "number") {
309
- throw new Error("Duty value (%) of PWM line" + this.line + " is not valid")
309
+ throw new RangeError("Duty value (%) of PWM line" + this.line + " is not valid")
310
310
  }
311
311
 
312
312
  writeFileSync(this.pwmPathChannel + "duty_cycle", (this.dutyMin + ((percent / 100) * (this.dutyMax - this.dutyMin))).toString())
313
313
  }
314
314
 
315
+ /** --------------------------------------------------------------
316
+ * @method pulseStart
317
+ * @description Start a train of N pulses on an output GPIO line
318
+ * @param {Number} count - Number of pulses
319
+ * @param {"asc"|"desc"} edge - ascending/descending edge
320
+ * @param {Object} opt - See pulse properties belows
321
+ * @return {Promise<{elapsedMs: number, pulsesCompleted: number, stopped: boolean}>}
322
+ */
323
+ pulseStart(count, edge, opt) {
324
+
325
+ const edges = ["desc", "asc"]
326
+ const defopt = {
327
+ pulseWidth: 10, // Pulse width in µs
328
+ spaceWidth: 10, // Space width between pulses in µs
329
+ }
330
+ opt = {...defopt, ...opt}
331
+
332
+
333
+ if (this.closed)
334
+ throw new Error("GPIO handle has been closed")
335
+
336
+ if (this.mode !== "output")
337
+ throw new Error("Cannot write to this GPIO mode:", this.mode)
338
+
339
+ if (!Number.isInteger(count) || count <= 0) {
340
+ throw new RangeError("Count must be a positive integer")
341
+ }
342
+
343
+ if (edges.indexOf(edge) === -1) {
344
+ throw new RangeError("Invalid edge value")
345
+ }
346
+
347
+ if (!Number.isFinite(opt.pulseWidth) || opt.pulseWidth < 0) {
348
+ throw new RangeError("pulseWidth must be a non-negative number")
349
+ }
350
+ if (!Number.isFinite(opt.spaceWidth) || opt.spaceWidth < 0) {
351
+ throw new RangeError("spaceWidth must be a non-negative number")
352
+ }
353
+
354
+ return ADDON.pulse(this.handle, count, Math.round(opt.spaceWidth), Math.round(opt.spaceWidth), edge === edges[0] ? 0 : 1)
355
+ }
356
+
357
+ /** --------------------------------------------------------------
358
+ * @method pulseStop
359
+ * @description Immediately interrupts an ongoing pulse sequence if one is in progress.
360
+ * Synchronous and blocking (a few ms max in practice), the handle remains open and usable afterwards.
361
+ * Upon return, the line is guaranteed to be idle—no pulses remain in flight.
362
+ * @return {Boolean} true if a sequence was in progress and was stopped.
363
+ */
364
+ pulseStop() {
365
+ return ADDON.pulseStop(this.handle)
366
+ }
367
+
315
368
  // -------------------------------------------------------------------
316
369
  // STATIC FUNCTIONS
317
370
  /** ------------------------------------------------------------------
package/esm/step.mjs ADDED
@@ -0,0 +1,212 @@
1
+ // -------------------------------------------------------------------
2
+ // RPI-IO: STEP MOTOR EXTENSION ⚠️ Work in Progress
3
+ // -------------------------------------------------------------------
4
+ import {RIO} from "./main.mjs";
5
+ // -------------------------------------------------------------------
6
+ // UTILITY FUNCTIONS
7
+ /** ------------------------------------------------------------------
8
+ * @function tlog
9
+ * @description Timestamped console.log
10
+ */
11
+ export function tlog() {
12
+ const date = new Date()
13
+ const nowMs = date.toLocaleTimeString() + "." + date.getMilliseconds().toLocaleString('en', {
14
+ minimumIntegerDigits: 3,
15
+ minimumFractionDigits: 0,
16
+ useGrouping: false
17
+ })
18
+ console.log.apply(console, Array.prototype.concat.apply([nowMs, "🔎 "], arguments))
19
+ }
20
+
21
+ /** ------------------------------------------------------------------
22
+ * @function wait
23
+ * @description Wait ms as a promise and optional tlog
24
+ * @param {Number} ms
25
+ * @param {Boolean} logWait
26
+ */
27
+ export function wait(ms, logWait = true) {
28
+ logWait ? tlog("sleeping", ms, "ms") : false
29
+ return new Promise(resolve => setTimeout(resolve, ms));
30
+ }
31
+
32
+ export class STEP {
33
+
34
+ static STATUS_MSG = {
35
+ 0: "none",
36
+ 1: "Unknown driver name",
37
+ 11: "At least one coil port rpi-io instance definition is missing",
38
+ 12: "At least one coil port rpi-io instance is not valid",
39
+ 13: "Wrong delay parameter value (0 ≤ d ≤ 1000)",
40
+ 14: "Wrong ULN2003 sequence {'full','half'}",
41
+ 200: "ok"
42
+ }
43
+ static ULN2003_MODE = ["half", "full"]
44
+ static ULN2003_STEPS = {
45
+ half: [
46
+ [1, 0, 0, 0],
47
+ [1, 1, 0, 0],
48
+ [0, 1, 0, 0],
49
+ [0, 1, 1, 0],
50
+ [0, 0, 1, 0],
51
+ [0, 0, 1, 1],
52
+ [0, 0, 0, 1],
53
+ [1, 0, 0, 1]
54
+ ],
55
+ full: [
56
+ [1, 0, 0, 1],
57
+ [1, 1, 0, 0],
58
+ [0, 1, 1, 0],
59
+ [0, 0, 1, 1]
60
+ ]
61
+ }
62
+
63
+ // Private method: Update instance status
64
+ #setStatus(code, data) {
65
+ this.status.code = code
66
+ this.status.msg = STEP.STATUS_MSG[code]
67
+ this.status.data = data
68
+ }
69
+
70
+ /** ------------------------------------------------------------------
71
+ * @method constructor
72
+ * @param {String} driver // Supported driver name
73
+ * @param {Object} rios // rpi-io Instance container depending on driver
74
+ * @param {Object} opt // Options depending on driver
75
+ */
76
+ constructor(driver, rios, opt) {
77
+
78
+ const defopt = {
79
+ // Parameters for ULN2003
80
+ delay: 10,
81
+ sequence: "half"
82
+ }
83
+ opt = {...defopt, ...opt}
84
+
85
+ // Init instance construction status
86
+ this.status = {
87
+ code: 0,
88
+ msg: STEP.STATUS_MSG[0],
89
+ data: ""
90
+ }
91
+
92
+ // Test parameters depending on driver
93
+ switch (driver) {
94
+ case "uln2003":
95
+
96
+ // ERR11: At least one coil port rpi-io instance definition is missing
97
+ if (typeof rios.coilA === "undefined" || typeof rios.coilB === "undefined" || typeof rios.coilC === "undefined" || typeof rios.coilD === "undefined") {
98
+ this.#setStatus(11, "")
99
+ RIO.closeAll()
100
+ return
101
+ }
102
+
103
+ // ERR12: At least one coil port rpi-io instance is not valid
104
+ try {
105
+ rios.coilA.write(0)
106
+ rios.coilB.write(0)
107
+ rios.coilC.write(0)
108
+ rios.coilD.write(0)
109
+ } catch (err) {
110
+ this.#setStatus(12, "")
111
+ RIO.closeAll()
112
+ return
113
+ }
114
+
115
+ // ERR13: Wrong delay parameter value (0 ≤ d ≤ 1000)
116
+ if (typeof opt.delay !== "number" || opt.delay < 0 || opt.delay > 1000) {
117
+ this.#setStatus(13, opt.delay)
118
+ RIO.closeAll()
119
+ return
120
+ }
121
+
122
+ // ERR14: Wrong ULN2003 sequence {'full','half'}
123
+ if (STEP.ULN2003_MODE.indexOf(opt.sequence) === -1) {
124
+ this.#setStatus(14, opt.sequence)
125
+ RIO.closeAll()
126
+ return
127
+ }
128
+
129
+ // Test successfully completed => Init properties
130
+ this.driver = "uln2003"
131
+ this.props = {
132
+ a: rios.coilA,
133
+ b: rios.coilB,
134
+ c: rios.coilC,
135
+ d: rios.coilD,
136
+ sequence: STEP.ULN2003_STEPS[opt.sequence],
137
+ len: STEP.ULN2003_STEPS[opt.sequence].length,
138
+ delay: opt.delay,
139
+ inc: 0,
140
+ index: 0
141
+ }
142
+ this.#setStatus(200, "")
143
+ break
144
+ default:
145
+ this.#setStatus(1, driver)
146
+ }
147
+ }
148
+
149
+ /** ------------------------------------------------------------------
150
+ * @method rotate
151
+ * @description Rotate the motor a certain number of steps forward or backward.
152
+ * A full rotation consists of 200 steps.
153
+ * @param {Number} steps
154
+ * @param {"forward"|"backward"} direction
155
+ * @param {Boolean} showStep
156
+ */
157
+ async rotate(steps, direction, showStep = false) {
158
+
159
+ switch (this.driver) {
160
+ case "uln2003":
161
+ this.props.inc = direction === "forward" ? 1 : -1
162
+
163
+ tlog("starting rotation:", steps, "steps", direction)
164
+
165
+ // Rotate
166
+ for (let i = 0; i < steps; i++) {
167
+ const [a, b, c, d] = this.props.sequence[this.props.index]
168
+ if (showStep) {
169
+ tlog("rotation step:", i, [a, b, c, d])
170
+ }
171
+ this.props.a.write(a)
172
+ this.props.b.write(b)
173
+ this.props.c.write(c)
174
+ this.props.d.write(d)
175
+
176
+ this.props.index = ((this.props.index + this.props.inc) % this.props.len + this.props.len) % this.props.len
177
+ await wait(this.props.delay, false)
178
+ }
179
+
180
+ // Disable coils
181
+ this.props.a.write(0)
182
+ this.props.b.write(0)
183
+ this.props.c.write(0)
184
+ this.props.d.write(0)
185
+ }
186
+ }
187
+
188
+ /** ------------------------------------------------------------------
189
+ * @method close
190
+ * @description Disable coils and close rpi-io instances used for step motor controller
191
+ */
192
+ close() {
193
+
194
+ switch (this.driver) {
195
+ case "uln2003":
196
+ this.props.a.write(0)
197
+ this.props.b.write(0)
198
+ this.props.c.write(0)
199
+ this.props.d.write(0)
200
+ this.props.a.close()
201
+ this.props.b.close()
202
+ this.props.c.close()
203
+ this.props.d.close()
204
+ tlog("ULN2003 controller closed")
205
+ }
206
+ }
207
+ }
208
+
209
+
210
+ // -------------------------------------------------------------------
211
+ // EoF
212
+ // -------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rpi-io",
3
- "version": "2.2.4",
3
+ "version": "3.0.0",
4
4
  "description": "Nodejs module to control Raspberry Pi GPIO",
5
5
  "keywords": [
6
6
  "raspberry",
package/test/pulse.js ADDED
@@ -0,0 +1,43 @@
1
+ // -------------------------------------------------------------------
2
+ // TEST - Simple slow and fast pulse trains
3
+ // -------------------------------------------------------------------
4
+ import {RIO, traceCfg, log, sleep, ctrlC, lineNumber} from "../esm/main.mjs"
5
+
6
+ (async () => {
7
+ traceCfg(2)
8
+
9
+ // Read line number from command line
10
+ const line = lineNumber(2)
11
+ if (line < 0) return
12
+
13
+ // Init output line and ctrl+c to close it
14
+ const led = new RIO(line, "output", {value: 0})
15
+ log("led:", led)
16
+ ctrlC(() => {
17
+ led.close()
18
+ })
19
+
20
+ // Slow pulse x10, ascending edge, w = 0.5 s, s = 0.5 s
21
+ const pulseStatus = await led.pulseStart(10, "asc", {
22
+ pulseWidth: 500000,
23
+ spaceWidth: 500000
24
+ })
25
+ log("pulse status stopped when completed:", pulseStatus)
26
+
27
+ // Fast pulse x1000, descending edge, w = 5µs, s = 5µs
28
+ led.write(1) // Init line according to edge
29
+ led.pulseStart(1000, "desc", {
30
+ pulseWidth: 5,
31
+ spaceWidth: 5
32
+ }).then(pulseStatus => {
33
+ log("pulse status stopped when stopped:", pulseStatus)
34
+ led.write(0) // Reset line
35
+ led.close()
36
+ })
37
+ await sleep(5)
38
+ led.pulseStop() // Stop pulse train after 5ms
39
+
40
+ })()
41
+ // -------------------------------------------------------------------
42
+ // EoF
43
+ // -------------------------------------------------------------------
package/test/read.js CHANGED
@@ -5,6 +5,8 @@ import {RIO, traceCfg, log, sleep, ctrlC, lineNumber} from "../esm/main.mjs"
5
5
 
6
6
  (async () => {
7
7
  traceCfg(2)
8
+
9
+ // Read line number from command line
8
10
  const line = lineNumber(2)
9
11
  if (line < 0) return
10
12
 
@@ -0,0 +1,59 @@
1
+ // -------------------------------------------------------------------
2
+ // RPI-IO: STEP MOTOR TEST SCRIPT ⚠️ Work in Progress
3
+ // -------------------------------------------------------------------
4
+ import {RIO} from "../esm/main.mjs";
5
+ import {STEP, tlog} from "../esm/step.mjs"
6
+
7
+ // Close any open rpi-io instance to prevent error
8
+ RIO.closeAll()
9
+
10
+ // Open step controller in half step mode => Half turn forward and backward => Close
11
+ const motor28BYJH = new STEP("uln2003", {
12
+ coilA: new RIO(21, "output"),
13
+ coilB: new RIO(22, "output"),
14
+ coilC: new RIO(24, "output"),
15
+ coilD: new RIO(25, "output")
16
+ }, {
17
+ delay: 0,
18
+ sequence: "half"
19
+ })
20
+ tlog("motor28BYJH creation status:", motor28BYJH.status)
21
+ await motor28BYJH.rotate(2048, "forward", false)
22
+ await motor28BYJH.rotate(2048, "backward", false)
23
+ motor28BYJH.close()
24
+
25
+ // Open step controller in full step mode => Half turn forward and backward => Close
26
+ const motor28BYJF = new STEP("uln2003", {
27
+ coilA: new RIO(21, "output"),
28
+ coilB: new RIO(22, "output"),
29
+ coilC: new RIO(24, "output"),
30
+ coilD: new RIO(25, "output")
31
+ }, {
32
+ delay: 4,
33
+ sequence: "full"
34
+ })
35
+ tlog("motor28BYJF creation status:", motor28BYJF.status)
36
+ await motor28BYJF.rotate(1024, "forward", false)
37
+ await motor28BYJF.rotate(1024, "backward", false)
38
+ motor28BYJF.close()
39
+
40
+ // A4988 drivers require one line per parameter as in the example below
41
+ const a4988 = {
42
+ pulse: new RIO(5, "output"),
43
+ direction: new RIO(6, "output"),
44
+ enable: new RIO(26, "output")
45
+ }
46
+
47
+ // DM452 drivers require one line per parameter as in the example below
48
+ const dm452 = {
49
+ pulse: new RIO(16, "output"),
50
+ direction: new RIO(17, "output"),
51
+ polarity: new RIO(27, "output")
52
+ }
53
+
54
+ // When done close all rpi-io instances
55
+ RIO.closeAll()
56
+
57
+ // -------------------------------------------------------------------
58
+ // EoF
59
+ // -------------------------------------------------------------------
package/test/write.js CHANGED
@@ -6,6 +6,7 @@ import {RIO, traceCfg, log, sleep, ctrlC, lineNumber} from "../esm/main.mjs"
6
6
  (async () => {
7
7
  traceCfg(2)
8
8
 
9
+ // Read line number from command line
9
10
  const line = lineNumber(2)
10
11
  if (line < 0) return
11
12
 
package/esm/uln2003.mjs DELETED
@@ -1,115 +0,0 @@
1
- // -------------------------------------------------------------------
2
- // RPI-IO: ULN2003 STEP MOTORS CONTROL
3
- // -------------------------------------------------------------------
4
-
5
- import {RIO} from "./main.mjs";
6
-
7
- export class ULN2003 {
8
-
9
- static RPi_GPIO_STD = [5, 6, 16, 17, 20, 21, 22, 23, 24, 25, 26, 27]
10
- static RPi_GPIO_PWM = [12, 13, 18, 19]
11
-
12
- /** ------------------------------------------------------------------
13
- * @function isValidGpio
14
- * @description Check if pin a valid multipurpose GPIO
15
- * @param {Number} pin
16
- * @return {Boolean}
17
- */
18
- static static isValidGpio(pin) {
19
- return [...ULN2003.RPi_GPIO_STD, ...ULN2003.RPi_GPIO_PWM].GPIOS.includes(pin);
20
- }
21
-
22
- static MODE = ["half", "full"]
23
- static STEPS = {
24
- half: [
25
- [1, 0, 0, 0],
26
- [1, 1, 0, 0],
27
- [0, 1, 0, 0],
28
- [0, 1, 1, 0],
29
- [0, 0, 1, 0],
30
- [0, 0, 1, 1],
31
- [0, 0, 0, 1],
32
- [1, 0, 0, 1]
33
- ],
34
- full: [
35
- [1, 0, 0, 1],
36
- [1, 1, 0, 0],
37
- [0, 1, 1, 0],
38
- [0, 0, 1, 1]
39
- ]
40
- }
41
-
42
-
43
- /** ------------------------------------------------------------------
44
- * @method constructor
45
- * @param {Array} pins - Array of 4 GPIO BCM port number e.g. [21,22,24,25]
46
- * @param {Object} opt - Delay between steps and sequencing mode. See below
47
- */
48
- constructor(pins, opt) {
49
-
50
- // Init options with default values and check validity
51
- const defopt = {
52
- // Delay between two steps in 1 ≤ ms ≤ 1000
53
- delayMs: 10,
54
- // Sequencing mode: 'full' or 'half' (default)
55
- seqMode: ULN2003.MODE[0]
56
- }
57
- opt = {...defopt, ...opt}
58
- opt.seqMode = opt.seqMode.toLowerCase()
59
-
60
- // Check if pins
61
- pins.forEach(pin => {
62
- if (!ULN2003.isValidGpio(pin)) {
63
- throw new Error("Invalid pin number: " + pin)
64
- }
65
- })
66
-
67
-
68
- rpiIoInstances.forEach(instance => {
69
- if (!instance instanceof RIO) throw new Error("Invalid instance")
70
- if (instance.mode !== "output") throw new Error("Invalid instance mode")
71
- })
72
-
73
- // Reset option values if not valid
74
- if (ULN2003.MODE.indexOf(opt.seqMode) === -1) opt.seqMode = defopt.seqMode
75
- if (typeof opt.delayMs !== "number" || opt.delayMs < 1 || opt.delayMs > 1000) opt.delayMs = defopt.delayMs
76
-
77
-
78
- // Throw error if list of rpi-io instances is not valid: wrong number, wrong instance, wrong mode
79
- if (!Array.isArray(rpiIoInstances) || rpiIoInstances.length !== 4) throw new Error("Invalid instance list.")
80
- rpiIoInstances.forEach(instance => {
81
- if (!instance instanceof RIO) throw new Error("Invalid instance")
82
- if (instance.mode !== "output") throw new Error("Invalid instance mode")
83
- })
84
-
85
- // No option error here
86
-
87
- this.coils = rpiIoInstances
88
- this.sequence = ULN2003.STEPS[opt.seqMode]
89
- this.len = this.sequence.length
90
- this.delay = opt.delayMs
91
- this.A = rpiIoInstances[0]
92
- this.B = rpiIoInstances[1]
93
- this.C = rpiIoInstances[2]
94
- this.D = rpiIoInstances[0]
95
- this.inc = 0
96
- this.index = 0
97
- }
98
- }
99
-
100
- const toto = new ULN2003()
101
-
102
-
103
- /** ------------------------------------------------------------------
104
- * @method uln2003
105
- * @description To control step motors based on ULN20003-like drivers.
106
- * This function used 4 rpi-io output instances to control
107
- * step motors back and forth.
108
- * @param {Object} pins
109
- * @param {Number} steps - Nombre de pas (entier > 0)
110
- * @param {'fwd'|'rev'} dir - Direction : 'fwd' = avant, 'rev' = arrière
111
- */
112
-
113
- // -------------------------------------------------------------------
114
- // EoF
115
- // -------------------------------------------------------------------
@@ -1,103 +0,0 @@
1
- // -------------------------------------------------------------------
2
- // TEST - ULN 2003
3
- // -------------------------------------------------------------------
4
- import {RIO, traceCfg, log, sleep, ctrlC, lineNumber} from "../esm/main.mjs"
5
-
6
- (async () => {
7
- traceCfg(2)
8
-
9
- // Init outputs
10
- const pins = {
11
- A: new RIO(21, 'output', {value: 0}),
12
- B: new RIO(22, 'output', {value: 0}),
13
- C: new RIO(24, 'output', {value: 0}),
14
- D: new RIO(25, 'output', {value: 0}),
15
- }
16
-
17
- const HALF_STEP = [
18
- [1, 0, 0, 0],
19
- [1, 1, 0, 0],
20
- [0, 1, 0, 0],
21
- [0, 1, 1, 0],
22
- [0, 0, 1, 0],
23
- [0, 0, 1, 1],
24
- [0, 0, 0, 1],
25
- [1, 0, 0, 1],
26
- ]
27
-
28
- const FULL_STEP = [
29
- [1, 0, 0, 1],
30
- [1, 1, 0, 0],
31
- [0, 1, 1, 0],
32
- [0, 0, 1, 1],
33
- ]
34
-
35
- /** ------------------------------------------------------------------
36
- * @function write
37
- * @param {Number} port
38
- * @param {Number} value
39
- */
40
- const write = (port, value) => {
41
- pins[port].write(value)
42
- }
43
-
44
- const reset = () => {
45
- for (const [port, instance] of Object.entries(pins)) {
46
- instance.write(0)
47
- }
48
- }
49
-
50
- const test = async () => {
51
- for (const [port, instance] of Object.entries(pins)) {
52
- instance.write(1)
53
- await sleep(500)
54
- instance.write(0)
55
- await sleep(500)
56
- }
57
- }
58
-
59
- // Close ports on ctrl+c
60
- ctrlC(() => {
61
- reset()
62
- RIO.closeAll();
63
- })
64
-
65
- /** --------------------------------------------------------------
66
- * @function stepMotor
67
- * @param {Object} opts
68
- * @param {Number} opts.steps - Nombre de pas (entier > 0)
69
- * @param {'fwd'|'rev'} opts.dir - Direction : 'fwd' = avant, 'rev' = arrière
70
- * @param {number} opts.delayMs - Délai entre deux pas en millisecondes (≥ 1)
71
- * @param {'half'|'full'} [opts.mode='half'] - Mode de séquençage
72
- * @returns {Promise<void>}
73
- */
74
- async function stepMotor({steps, dir = 'fwd', delayMs = 10, mode = 'half'}) {
75
- const sequence = mode === 'half' ? HALF_STEP : FULL_STEP;
76
- const len = sequence.length;
77
- const inc = dir === 'fwd' ? 1 : -1;
78
- let index = 0;
79
-
80
- for (let i = 0; i < steps; i++) {
81
- const [a, b, c, d] = sequence[index];
82
- write('A', a);
83
- write('B', b);
84
- write('C', c);
85
- write('D', d);
86
- index = ((index + inc) % len + len) % len;
87
- await sleep(delayMs); // sleep() natif rpi-io
88
- }
89
-
90
- // Désactivation des bobines
91
- write('A', 0);
92
- write('B', 0);
93
- write('C', 0);
94
- write('D', 0);
95
- }
96
-
97
-
98
- await stepMotor({ steps: 1600, dir: 'rev', delayMs: 8 })
99
-
100
- })()
101
- // -------------------------------------------------------------------
102
- // EoF
103
- // -------------------------------------------------------------------