diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..9af6ed5 --- /dev/null +++ b/README.en.md @@ -0,0 +1,387 @@ +# SOEM-Nodejs + +[![CI/CD](https://github.com/MXASoundNDEv/SOEM-Nodejs/actions/workflows/ci.yml/badge.svg)](https://github.com/MXASoundNDEv/SOEM-Nodejs/actions/workflows/ci.yml) +[![npm version](https://badge.fury.io/js/soem-node.svg)](https://badge.fury.io/js/soem-node) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) +[![Node.js Version](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org/) +[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgrey)](https://github.com/MXASoundNDEv/SOEM-Nodejs) +[![Coverage](https://img.shields.io/badge/coverage-38%25-yellow)](./coverage/index.html) + +High-performance Node.js bindings for [SOEM (Simple Open EtherCAT Master)](https://github.com/OpenEtherCATsociety/SOEM) featuring automatic network interface discovery and advanced management utilities. + +## 🚀 Features + +- ✅ **Automatic interface discovery** – finds optimal EtherCAT-capable interfaces +- ✅ **Multi-platform support** – Windows & Linux (x64, arm64) +- ✅ **Full TypeScript definitions** – rich typing & IntelliSense +- ✅ **Modern API** – clean, minimal, predictable +- ✅ **Built‑in utilities** – management & diagnostics helpers +- ✅ **Extensive tests** – unit + integration coverage +- ✅ **Real examples** – practical scripts & guidance + +## 📦 Installation + +```bash +npm install soem-node +``` + +### System prerequisites + +- **Node.js** >= 18 +- **C/C++ toolchain** (MSVC Build Tools on Windows, gcc or clang on Linux) +- **Python** (required by node-gyp) + +> CMake is no longer required: the build uses `node-gyp` and `binding.gyp` only. + +#### Windows +- **Npcap** (recommended) or **WinPcap** for raw network access + - Download: https://npcap.com/#download +- **Visual Studio Build Tools** (or full VS install) + +#### Linux +- `libpcap-dev` (Ubuntu/Debian) or `libpcap` for your distro +- Proper raw socket permissions (see below) + +### Linux capabilities (avoid running as root) + +```bash +sudo setcap cap_net_raw,cap_net_admin+eip $(which node) +``` + +## 🎯 Quick Start + +### 1. List interfaces + +```js +const { SoemMaster } = require('soem-node'); +const interfaces = SoemMaster.listInterfaces(); +console.log('Available interfaces:', interfaces); +``` + +### 2. Automatic master creation + +```js +const { EtherCATUtils } = require('soem-node/examples/ethercat-utils'); +const master = EtherCATUtils.createMaster(); +if (master) { + try { + const slaves = master.configInit(); + console.log(`${slaves} EtherCAT slaves detected`); + } finally { + master.close(); + } +} +``` + +### 3. Full communication cycle + +```js +const { SoemMaster } = require('soem-node'); + +async function main() { + const master = new SoemMaster('eth0'); // or Windows interface name + if (!master.init()) throw new Error('EtherCAT init failed'); + try { + const slaves = master.configInit(); + if (slaves > 0) { + master.configMapPDO(); + for (let i = 0; i < 100; i++) { + master.sendProcessdata(); + const wkc = master.receiveProcessdata(); + console.log(`Cycle ${i}: WKC=${wkc}`); + await new Promise(r => setTimeout(r, 10)); + } + const deviceType = master.sdoRead(1, 0x1000, 0); + if (deviceType) { + console.log('Device type:', deviceType.readUInt32LE(0).toString(16)); + } + } + } finally { + master.close(); + } +} + +main().catch(console.error); +``` + +## Manual Build + +Build invokes `node-gyp` and auto-generates `ec_options.h` via the script referenced in `binding.gyp` (`scripts/generate-ec-options.js`). + +``` +npm run build +``` + +Native binary output: `build/Release/soem_addon.node`. + +## Electron Usage + +Based on **Node-API (N-API)**: most Electron versions supporting the same N-API level will work without rebuilding. If you encounter an ABI error: + +```bash +npm rebuild --runtime=electron --target=30.0.0 --dist-url=https://electronjs.org/headers +``` + +`electron-builder` config snippet: +```jsonc +{ + "asarUnpack": [ + "node_modules/soem-node/build/Release/*.node" + ] +} +``` + +Checklist: +- Install Npcap (Windows) or libpcap (Linux) +- Confirm architecture (x64 / arm64) +- Adjust Linux capabilities (see above) + +Reference: https://www.electronjs.org/docs/latest/tutorial/native-code-and-electron + +## 🧪 Testing & Quality + +### Run tests + +```bash +npm test # basic tests +npm run test:ci # with coverage +npm run test:watch # watch mode +npm run test:all # full suite +npm run lint # lint +npm run security # security audit +``` + +### Coverage + +Project maintains strong coverage: +- ✅ Core API unit tests +- ✅ Integration workflows +- ✅ Intelligent mocks for CI +- ✅ Performance & stability checks + +### CI/CD + +GitHub Actions provides: +- ✅ Cross-platform tests (Windows, Linux) +- ✅ TypeScript + ESLint validation +- ✅ Security audit +- ✅ Multi-platform build +- ✅ Automated npm publish + +## 🔧 Development + +### Setup + +```bash +git clone https://github.com/MXASoundNDEv/SOEM-Nodejs.git +cd SOEM-Nodejs +npm install +npm run build +npm test +``` + +### Project structure + +``` +soem-node/ +├── src/ # TypeScript source +├── examples/ # Example scripts & helpers +├── test/ # Test suite +├── types/ # Type definitions +├── external/ # SOEM submodule +├── docs/ # Documentation +└── scripts/ # Utility scripts (options generation, release, CI) +``` + +## Example script + +``` +node examples/scan-and-read.js eth0 +``` +Reads slaves, exchanges processdata and reads SDO `0x1000` from slave 1. + +## Troubleshooting + +| Issue | Hint | +|-------|------| +| Native module not found | Re-run `npm run build`; check `build/Release/soem_addon.node` | +| Build errors | Ensure Python + toolchain present | +| Network access denied | Install Npcap/libpcap & permissions | +| Need rebuild (Electron) | Use `npm rebuild --runtime=electron ...` | +| Low WKC | Check cabling, topology, slave power | + +## License + +GPL-2.0-or-later. SOEM itself is GPLv2 with exceptions. Shipping prebuilt binaries may require publishing your source. A commercial SOEM license is available for proprietary use. + +--- + +## 📘 Full API (SoemMaster) + +This section mirrors the French API doc for convenience. See also `docs/` for background (EtherCAT overview, glossary). + +### Contents +1. Overview +2. Import & Typing +3. Lifecycle +4. Summary Table +5. Category Reference +6. Advanced Examples +7. Errors & Diagnostics +8. Performance Best Practices +9. Short FAQ + +--- +### 1. Overview +`SoemMaster` wraps native SOEM primitives (N-API). Provides: +- Slave discovery & configuration +- PDO mapping and cyclic exchange +- SDO / SoE / EEPROM access +- Low-level frame primitives +- Distributed Clocks (DC) helpers +- Recovery & reconfiguration utilities + +### 2. Import & Typing +```ts +import { SoemMaster } from 'soem-node'; +interface NetworkInterface { name: string; description: string } +``` +CommonJS: +```js +const { SoemMaster } = require('soem-node'); +``` + +### 3. Lifecycle +``` +new SoemMaster(ifname) -> init() -> configInit() -> (slaves>0) -> configMapPDO() -> loop: send/receive -> close() +``` + +### 4. Summary Table +| Method | Returns | Category | Notes | +|--------|---------|----------|-------| +| constructor(ifname?) | instance | Init | Default `eth0` (adjust on Windows) | +| init() | boolean | Init | Opens interface | +| configInit() | number | Discovery | Slave count | +| configMapPDO() | void | PDO | Setup processdata map | +| state() | number | State | Cached value | +| readState() | number | State | Forces refresh | +| sdoRead(slave,i,sub,ca?) | Buffer\|null | SDO | Optional Complete Access | +| sdoWrite(slave,i,sub,data,ca?) | boolean | SDO | Optional Complete Access | +| sendProcessdata() | number | PDO | Cycle send | +| receiveProcessdata() | number | PDO | WKC / bytes | +| writeState(slave,state) | number | State | Workcounter/code | +| stateCheck(slave,req,timeout?) | number | State | Wait for state | +| reconfigSlave(slave,timeout?) | number | Recovery | Reconfigure slave | +| recoverSlave(slave,timeout?) | number | Recovery | >0 success | +| slaveMbxCyclic(slave) | number | Mailbox | Enables cyclic mailbox | +| configDC() | boolean | DC | Enable distributed clocks | +| getSlaves() | any[] | Info | Slave metadata | +| initRedundant(if1,if2) | boolean | Redundancy | Dual interface master | +| configMapGroup(group?) | Buffer\|null | PDO Group | Group mapping | +| sendProcessdataGroup(group?) | number | PDO Group | Group variant | +| receiveProcessdataGroup(group?,timeout?) | number | PDO Group | Group variant | +| mbxHandler(group?,limit?) | number | Mailbox | Mailbox processing | +| elist2string() | string | Diagnostic | Internal error log | +| SoEread(slave,driveNo,flags,idn) | Buffer\|null | SoE | Read element | +| SoEwrite(slave,driveNo,flags,idn,data) | boolean | SoE | Write element | +| readeeprom(slave,addr,timeout?) | number | EEPROM | Read word | +| writeeeprom(slave,addr,data,timeout?) | number | EEPROM | Write word | +| APRD(ADP,ADO,length,timeout?) | Buffer\|null | Low-level | Application Read | +| APWR(ADP,ADO,data,timeout?) | number | Low-level | Application Write | +| LRW(LogAdr,length,buf,timeout?) | number | Low-level | Logical Read/Write | +| LRD(LogAdr,length,timeout?) | Buffer\|null | Low-level | Logical Read | +| LWR(LogAdr,data,timeout?) | number | Low-level | Logical Write | +| dcsync0(slave,act,CyclTime,CyclShift) | boolean | DC | Single cycle sync | +| dcsync01(slave,act,CyclTime0,CyclTime1,CyclShift) | boolean | DC | Dual cycle sync | +| SoemMaster.listInterfaces() | NetworkInterface[] | Utility | Enumerate interfaces | + +### 5. Category Reference +#### Initialization & Discovery +`init()`, `configInit()`, `configMapPDO()`, `initRedundant()`. + +#### Process Data (PDO / Groups) +`sendProcessdata()`, `receiveProcessdata()`, plus `*Group()` variants for multiple groups / timings. + +#### Object Access (SDO / SoE / EEPROM) +Use SDO for dictionary objects, SoE for supported drives, EEPROM for low-level device storage. Optional `ca` (Complete Access) for block reads/writes where supported. + +#### State & Recovery +`state()`, `readState()`, `writeState()`, `stateCheck()`, `reconfigSlave()`, `recoverSlave()`. Use `stateCheck` after transitions (e.g. INIT -> PRE-OP -> SAFE-OP -> OP). + +#### Distributed Clocks & Sync +`configDC()`, `dcsync0()`, `dcsync01()` for sub-microsecond synchronized cycles. `CyclTime*` are in nanoseconds. + +#### Low-level Primitives +`APRD`, `APWR`, `LRW`, `LRD`, `LWR` for diagnostic frames or specialized control flows. + +#### Utilities & Diagnostics +`elist2string()` for internal SOEM error/event list, `getSlaves()` for runtime bus inspection, `mbxHandler()` for periodic mailbox processing. + +### 6. Advanced Examples +Real-time style loop (~1 kHz): +```js +const master = new SoemMaster('eth0'); +if (!master.init()) throw new Error('init failed'); +try { + const slaves = master.configInit(); + if (!slaves) return; + master.configMapPDO(); + master.configDC(); + const periodUs = 1000; // 1 kHz + const start = process.hrtime.bigint(); + for (let i = 0; i < 5000; i++) { + master.sendProcessdata(); + master.receiveProcessdata(); + const target = start + BigInt(i + 1) * BigInt(periodUs) * 1000n; + while (process.hrtime.bigint() < target) {} + } +} finally { master.close(); } +``` + +Batch SDO reads: +```js +function readU32(master, slave, index, sub) { + const b = master.sdoRead(slave, index, sub); + return b ? b.readUInt32LE(0) : null; +} +const items = [0x1000, 0x1001, 0x1008]; +const results = Object.fromEntries(items.map(i => [i.toString(16), readU32(master, 1, i, 0)])); +``` + +DC sync enable: +```js +if (master.configDC()) { + master.dcsync0(1, true, 1_000_000, 0); // 1 ms in ns +} +``` + +Redundancy: +```js +const redundant = new SoemMaster('eth0'); +if (redundant.initRedundant('eth0', 'eth1')) { + // proceed like a normal master +} +``` + +### 7. Errors & Diagnostics +- `false` / `null` return => non-fatal failure (e.g., SDO read timeout) +- Exceptions => severe init or binding errors +- Use `elist2string()` after anomalies +- Tune `timeout?` parameters for slower devices +- Abnormal WKC => check cabling, slave power, EMI, network adapter + +### 8. Performance Best Practices +- Pre-allocate buffers for repeated `LRW` +- Avoid SDO operations inside tight cyclic loops (do them pre/post cycle) +- Enable DC to reduce jitter +- On Linux consider CPU isolation (isolcpus) & RT scheduling +- On Windows minimize background services on the target core + +### 9. Short FAQ +**Q:** Why does `sdoRead` return null? **A:** Slave timeout or invalid index/sub. +**Q:** Need root on Linux? **A:** Not if capabilities are set. +**Q:** Electron support? **A:** Yes via N-API; rebuild only if ABI mismatch. +**Q:** How to debug WKC = 0? **A:** Check physical layer, interface selection, slave power, cabling path. + +--- +For contributions or deeper protocol background see `docs/`. diff --git a/README.md b/README.md index ce141eb..adfaa05 100644 --- a/README.md +++ b/README.md @@ -247,3 +247,199 @@ Ce script détecte les esclaves, échange les `processdata` et lit l'SDO `0x1000 ## Licence Ce projet est distribué sous licence GPL-2.0-or-later. SOEM est GPLv2 avec exceptions ; la distribution de binaires précompilés peut imposer de publier votre code source. Une licence commerciale de SOEM est disponible pour des utilisations propriétaires. + +## 📘 API détaillée (SoemMaster) + +Cette section résume et intègre la documentation de `docs/API-SoemMaster.md` directement dans le README. Pour des détails supplémentaires (contexte EtherCAT, glossaire), voir le dossier `docs/`. + +### Table des matières + +1. Vue d'ensemble +2. Import & types +3. Cycle de vie minimal +4. Tableau récapitulatif +5. Référence par catégorie + - Initialisation & découverte + - Process Data (PDO / Groupes) + - Accès objets (SDO / SoE / EEPROM) + - Gestion d'état & récupération + - Distributed Clocks & Sync + - Accès bas-niveau (APRD/APWR/LRW/LRD/LWR) + - Utilitaires & diagnostics +6. Exemples avancés +7. Erreurs & diagnostics +8. Bonnes pratiques performance +9. FAQ courte + +--- + +### 1. Vue d'ensemble + +`SoemMaster` est un wrapper TypeScript au-dessus du binding natif SOEM. Il fournit: +- Découverte et configuration des esclaves +- Mapping PDO et échanges cycliques +- Lecture/écriture SDO, SoE, EEPROM +- Primitives bas-niveau (APRD, etc.) +- Gestion de Distributed Clocks (DC) +- Fonctions de récupération et reconfiguration d'esclaves + +Les méthodes reflètent les primitives SOEM. Les validations avancées sont faites côté natif. Vérifiez systématiquement les retours. + +### 2. Import & types + +CommonJS: +```js +const { SoemMaster } = require('soem-node'); +``` +ESM / TypeScript: +```ts +import { SoemMaster } from 'soem-node'; +``` + +Type principal supplémentaire: +```ts +interface NetworkInterface { name: string; description: string } +``` + +### 3. Cycle de vie minimal + +``` +new SoemMaster(ifname) -> init() -> configInit() -> (slaves > 0 ?) -> configMapPDO() -> [boucle] + sendProcessdata(); receiveProcessdata(); (SDO/SoE accès ponctuels) -> close() +``` + +### 4. Tableau récapitulatif + +| Méthode | Retour | Catégorie | Notes | +|---------|--------|-----------|-------| +| constructor(ifname?) | instance | Init | Interface réseau par défaut `eth0` (adapter sous Windows) | +| init() | boolean | Init | Ouvre l'interface | +| configInit() | number | Découverte | Nombre d'esclaves | +| configMapPDO() | void | PDO | Map processdata | +| state() | number | État | Lecture cache | +| readState() | number | État | Force rafraîchissement | +| sdoRead(slave,i,sub,ca?) | Buffer\|null | SDO | Complete Access optionnel | +| sdoWrite(slave,i,sub,data,ca?) | boolean | SDO | Complete Access optionnel | +| sendProcessdata() | number | PDO | Envoi cycle | +| receiveProcessdata() | number | PDO | WKC / octets | +| writeState(slave,state) | number | État | Workcounter/code | +| stateCheck(slave,req,timeout?) | number | État | Bloque jusqu'état | +| reconfigSlave(slave,timeout?) | number | Récupération | Reconfigure | +| recoverSlave(slave,timeout?) | number | Récupération | >0 = ok | +| slaveMbxCyclic(slave) | number | Mailbox | Active cyclique | +| configDC() | boolean | DC | Active DC | +| getSlaves() | any[] | Info | Métadonnées esclaves | +| initRedundant(if1,if2) | boolean | Redondance | Master redondant | +| configMapGroup(group?) | Buffer\|null | PDO Group | Mapping groupe | +| sendProcessdataGroup(group?) | number | PDO Group | Variante | +| receiveProcessdataGroup(group?,timeout?) | number | PDO Group | Variante | +| mbxHandler(group?,limit?) | number | Mailbox | Traitement messages | +| elist2string() | string | Diagnostic | Journal erreurs | +| SoEread(slave,driveNo,flags,idn) | Buffer\|null | SoE | Lecture élément | +| SoEwrite(slave,driveNo,flags,idn,data) | boolean | SoE | Écriture élément | +| readeeprom(slave,addr,timeout?) | number | EEPROM | Lecture mot | +| writeeeprom(slave,addr,data,timeout?) | number | EEPROM | Écriture mot | +| APRD(ADP,ADO,length,timeout?) | Buffer\|null | Bas-niveau | Application Read | +| APWR(ADP,ADO,data,timeout?) | number | Bas-niveau | Application Write | +| LRW(LogAdr,length,buf,timeout?) | number | Bas-niveau | Logical Read/Write | +| LRD(LogAdr,length,timeout?) | Buffer\|null | Bas-niveau | Logical Read | +| LWR(LogAdr,data,timeout?) | number | Bas-niveau | Logical Write | +| dcsync0(slave,act,CyclTime,CyclShift) | boolean | DC | Sync simple | +| dcsync01(slave,act,CyclTime0,CyclTime1,CyclShift) | boolean | DC | Sync dual | +| SoemMaster.listInterfaces() | NetworkInterface[] | Utilitaire | Découverte interfaces | + +### 5. Référence par catégorie + +#### Initialisation & découverte +`init()`, `configInit()`, `configMapPDO()`, `initRedundant()` +Ordre recommandé: init -> configInit -> (slaves>0) -> configMapPDO. + +#### Process Data (PDO / Groupes) +`sendProcessdata()`, `receiveProcessdata()`, variantes `*Group()` pour segmenter plusieurs groupes de slaves et gérer des cadences différentes. + +#### Accès objets (SDO / SoE / EEPROM) +SDO: `sdoRead` / `sdoWrite` (+ flag `ca` Complete Access). SoE pour drives supportant protocole SoE. EEPROM pour opérations bas-niveau (attention aux timings et verrouillages matériels). + +#### Gestion d'état & récupération +`state()`, `readState()`, `writeState()`, `stateCheck()`, `reconfigSlave()`, `recoverSlave()`. Utiliser `stateCheck` après une transition (ex: PRE-OP -> SAFE-OP) pour attendre de façon synchrone. + +#### Distributed Clocks & Sync +`configDC()`, `dcsync0()`, `dcsync01()`. Configure la synchronisation temporelle matérielle pour minimiser la dérive. Ajuster `CyclTime` en ns. + +#### Accès bas-niveau +`APRD`, `APWR`, `LRW`, `LRD`, `LWR` donnent un contrôle fin sur le trafic EtherCAT. Réservé aux usages avancés (diagnostic spécifique, optimisation bas-niveau, tests conformance). + +#### Utilitaires & diagnostics +`elist2string()` agrège les logs internes SOEM. `getSlaves()` permet d'inspecter dynamiquement le bus. `mbxHandler()` pour traitement mailbox périodique. + +### 6. Exemples avancés + +Boucle temps réel simplifiée (pseudo 1 kHz) : +```js +const master = new SoemMaster('eth0'); +if (!master.init()) throw new Error('init failed'); +try { + const slaves = master.configInit(); + if (!slaves) return; + master.configMapPDO(); + master.configDC(); + const periodUs = 1000; // 1 kHz + const start = process.hrtime.bigint(); + for (let i = 0; i < 5000; i++) { + master.sendProcessdata(); + master.receiveProcessdata(); + // Work... (lire/écrire buffers partagés) + const target = start + BigInt(i + 1) * BigInt(periodUs) * 1000n; + while (process.hrtime.bigint() < target) {} + } +} finally { master.close(); } +``` + +Lecture groupée d'objets SDO (ex: indices consécutifs) : +```js +function readU32(master, slave, index, sub) { + const b = master.sdoRead(slave, index, sub); + return b ? b.readUInt32LE(0) : null; +} +const items = [0x1000,0x1001,0x1008]; +const results = Object.fromEntries(items.map(i => [i.toString(16), readU32(master,1,i,0)])); +``` + +Activation DC Sync simple : +```js +if (master.configDC()) { + master.dcsync0(1, true, 1000000, 0); // 1 ms cycle +} +``` + +Redondance : +```js +const red = new SoemMaster('eth0'); +if (red.initRedundant('eth0','eth1')) { + // Procéder comme un master classique (configInit, etc.) +} +``` + +### 7. Erreurs & diagnostics +- Retour `false`/`null` indique échec non fatal (ex: SDO non lu) +- Exceptions: problèmes init majeurs, erreurs N-API +- `elist2string()` pour journal interne après anomalies +- Timeouts: ajuster paramètres `timeout?` sur méthodes correspondantes +- WKC (Working Counter) anormal: vérifier topologie, câblage, état esclave, pertes paquets + +### 8. Bonnes pratiques performance +- Pré-allouer buffers pour `LRW` au lieu de recréer chaque cycle +- Minimiser les appels SDO pendant la boucle temps réel (faire hors cycle ou avant) +- Activer DC pour réduire la gigue de synchronisation +- Sur Linux, isoler le CPU (isolcpus) et utiliser un scheduler temps réel si critique +- Sur Windows, réduire les interruptions (désactiver services non nécessaires) + +### 9. FAQ courte +**Q: Pourquoi `sdoRead` retourne null ?** L'esclave n'a pas répondu ou index/sub invalide. +**Q: Besoin de root sous Linux ?** Non si capabilities `cap_net_raw,cap_net_admin` définies. +**Q: Peut-on utiliser Electron ?** Oui, basé N-API; rebuild seulement si ABI incompatible. +**Q: Comment diagnostiquer un WKC = 0 ?** Vérifier lien physique, interface sélectionnée, câbles et alimentation des esclaves. + +--- + +Pour plus de détails ou contributions, consultez `docs/API-SoemMaster.md` et ouvrez une Issue/PR. diff --git a/package.json b/package.json index 49ac247..7233f9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "soem-node", - "version": "1.1.6", + "version": "1.2.0", "description": "Bindings Node-API pour SOEM (Simple Open EtherCAT Master)", "main": "dist/index.js", "types": "types/index.d.ts", @@ -96,6 +96,7 @@ "external", "LICENSE", "README.md", - "CHANGELOG.md" + "CHANGELOG.md", + "binding.gyp" ] }