@technoculture/safeserial 0.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/CMakeLists.txt +66 -0
- package/README.md +77 -0
- package/deps/include/safeserial/config.hpp +69 -0
- package/deps/include/safeserial/protocol/crc32.hpp +19 -0
- package/deps/include/safeserial/protocol/packet.hpp +175 -0
- package/deps/include/safeserial/protocol/reassembler.hpp +80 -0
- package/deps/include/safeserial/resilient_bridge.hpp +106 -0
- package/deps/include/safeserial/safeserial.hpp +87 -0
- package/deps/include/safeserial/transport/factory.hpp +0 -0
- package/deps/include/safeserial/transport/iserial_port.hpp +15 -0
- package/deps/include/safeserial/transport/serial_port.hpp +28 -0
- package/deps/src/CMakeLists.txt +21 -0
- package/deps/src/protocol/packet.cpp +1 -0
- package/deps/src/resilient_bridge.cpp +338 -0
- package/deps/src/safeserial.cpp +246 -0
- package/deps/src/transport/platform/linux/linux_serial.cpp +53 -0
- package/deps/src/transport/platform/windows/windows_serial.cpp +51 -0
- package/dist/index.d.mts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +202 -0
- package/dist/index.mjs +170 -0
- package/lib/index.ts +23 -0
- package/lib/native.ts +108 -0
- package/lib/reliable.ts +94 -0
- package/lib/resilient.ts +122 -0
- package/package.json +78 -0
- package/prebuilds/darwin-arm64/Release/data_bridge_node.node +0 -0
- package/prebuilds/darwin-arm64/Release/safeserial_node.node +0 -0
- package/scripts/copy_deps.js +44 -0
- package/scripts/receiver.js +37 -0
- package/scripts/sender.js +48 -0
- package/src/addon.cpp +807 -0
- package/src/serial_wrapper.cpp +8 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#include <safeserial/transport/serial_port.hpp>
|
|
2
|
+
#include <windows.h>
|
|
3
|
+
|
|
4
|
+
struct SerialPort::Impl {
|
|
5
|
+
HANDLE hSerial = INVALID_HANDLE_VALUE;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
SerialPort::SerialPort() : pimpl(std::make_unique<Impl>()) {}
|
|
9
|
+
SerialPort::~SerialPort() { close(); }
|
|
10
|
+
|
|
11
|
+
bool SerialPort::open(const std::string& port, int baud) {
|
|
12
|
+
pimpl->hSerial = CreateFileA(port.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
|
13
|
+
if (pimpl->hSerial == INVALID_HANDLE_VALUE) return false;
|
|
14
|
+
|
|
15
|
+
DCB dcb = {0};
|
|
16
|
+
dcb.DCBlength = sizeof(dcb);
|
|
17
|
+
GetCommState(pimpl->hSerial, &dcb);
|
|
18
|
+
dcb.BaudRate = CBR_115200;
|
|
19
|
+
dcb.ByteSize = 8;
|
|
20
|
+
dcb.StopBits = ONESTOPBIT;
|
|
21
|
+
dcb.Parity = NOPARITY;
|
|
22
|
+
SetCommState(pimpl->hSerial, &dcb);
|
|
23
|
+
|
|
24
|
+
// TIMEOUTS - Prevents the app from freezing
|
|
25
|
+
COMMTIMEOUTS timeouts = { 0 };
|
|
26
|
+
timeouts.ReadIntervalTimeout = 50;
|
|
27
|
+
timeouts.ReadTotalTimeoutConstant = 50;
|
|
28
|
+
timeouts.ReadTotalTimeoutMultiplier = 10;
|
|
29
|
+
SetCommTimeouts(pimpl->hSerial, &timeouts);
|
|
30
|
+
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
int SerialPort::write(const std::vector<uint8_t>& data) {
|
|
35
|
+
DWORD written;
|
|
36
|
+
WriteFile(pimpl->hSerial, data.data(), (DWORD)data.size(), &written, NULL);
|
|
37
|
+
return (int)written;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
int SerialPort::read(uint8_t* buffer, size_t size) {
|
|
41
|
+
DWORD read;
|
|
42
|
+
if (!ReadFile(pimpl->hSerial, buffer, (DWORD)size, &read, NULL)) return -1;
|
|
43
|
+
return (int)read;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
void SerialPort::close() {
|
|
47
|
+
if (pimpl->hSerial != INVALID_HANDLE_VALUE) {
|
|
48
|
+
CloseHandle(pimpl->hSerial);
|
|
49
|
+
pimpl->hSerial = INVALID_HANDLE_VALUE;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
|
|
3
|
+
interface DataBridgeOptions {
|
|
4
|
+
baudRate?: number;
|
|
5
|
+
maxRetries?: number;
|
|
6
|
+
ackTimeoutMs?: number;
|
|
7
|
+
fragmentSize?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* ReliableDataBridge
|
|
11
|
+
*
|
|
12
|
+
* Thin wrapper around the native C++ DataBridge implementation.
|
|
13
|
+
*/
|
|
14
|
+
declare class ReliableDataBridge extends EventEmitter {
|
|
15
|
+
private bridge;
|
|
16
|
+
private isOpen_;
|
|
17
|
+
private options;
|
|
18
|
+
constructor(options?: DataBridgeOptions);
|
|
19
|
+
static open(port: string, baudOrOptions?: number | DataBridgeOptions, cb?: (data: Buffer) => void): Promise<ReliableDataBridge>;
|
|
20
|
+
open(port: string, baud?: number): Promise<boolean>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
get isOpen(): boolean;
|
|
23
|
+
send(data: string | Buffer): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SerialPort {
|
|
27
|
+
open(port: string, baud: number, callback: (data: Buffer) => void): boolean;
|
|
28
|
+
write(data: Buffer): number;
|
|
29
|
+
close(): boolean;
|
|
30
|
+
isOpen(): boolean;
|
|
31
|
+
}
|
|
32
|
+
declare const SerialPort: {
|
|
33
|
+
new (): SerialPort;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* ResilientDataBridge - Connection-resilient wrapper
|
|
38
|
+
*
|
|
39
|
+
* Thin wrapper around the native C++ ResilientDataBridge implementation.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
interface ResilientOptions extends DataBridgeOptions {
|
|
43
|
+
/** Enable auto-reconnection (default: true) */
|
|
44
|
+
reconnect?: boolean;
|
|
45
|
+
/** Initial reconnect delay in ms (default: 1000) */
|
|
46
|
+
reconnectDelay?: number;
|
|
47
|
+
/** Maximum reconnect delay in ms (default: 30000) */
|
|
48
|
+
maxReconnectDelay?: number;
|
|
49
|
+
/** Maximum queue size before dropping old messages (default: 1000) */
|
|
50
|
+
maxQueueSize?: number;
|
|
51
|
+
}
|
|
52
|
+
interface ResilientEvents {
|
|
53
|
+
data: (data: Buffer) => void;
|
|
54
|
+
error: (error: Error) => void;
|
|
55
|
+
close: () => void;
|
|
56
|
+
disconnect: () => void;
|
|
57
|
+
reconnecting: (attempt: number, delay: number) => void;
|
|
58
|
+
reconnected: () => void;
|
|
59
|
+
}
|
|
60
|
+
declare class ResilientDataBridge extends EventEmitter {
|
|
61
|
+
private bridge;
|
|
62
|
+
private port;
|
|
63
|
+
private options;
|
|
64
|
+
private constructor();
|
|
65
|
+
/**
|
|
66
|
+
* Open a resilient serial connection.
|
|
67
|
+
*
|
|
68
|
+
* @param port - Serial port path
|
|
69
|
+
* @param options - Configuration including reconnection settings
|
|
70
|
+
*/
|
|
71
|
+
static open(port: string, options?: ResilientOptions): Promise<ResilientDataBridge>;
|
|
72
|
+
open(): Promise<boolean>;
|
|
73
|
+
/**
|
|
74
|
+
* Send data with guaranteed delivery.
|
|
75
|
+
*
|
|
76
|
+
* @param data - Data to send
|
|
77
|
+
* @returns Promise that resolves when data is acknowledged
|
|
78
|
+
*/
|
|
79
|
+
send(data: Buffer | string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Close the connection permanently.
|
|
82
|
+
*/
|
|
83
|
+
close(): Promise<void>;
|
|
84
|
+
/** Current connection state */
|
|
85
|
+
get connected(): boolean;
|
|
86
|
+
/** Number of messages waiting in queue */
|
|
87
|
+
get queueLength(): number;
|
|
88
|
+
on<K extends keyof ResilientEvents>(event: K, listener: ResilientEvents[K]): this;
|
|
89
|
+
once<K extends keyof ResilientEvents>(event: K, listener: ResilientEvents[K]): this;
|
|
90
|
+
emit<K extends keyof ResilientEvents>(event: K, ...args: Parameters<ResilientEvents[K]>): boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { ReliableDataBridge as DataBridge, type DataBridgeOptions, SerialPort as RawSerialPort, ResilientDataBridge, type ResilientEvents, type ResilientOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
|
|
3
|
+
interface DataBridgeOptions {
|
|
4
|
+
baudRate?: number;
|
|
5
|
+
maxRetries?: number;
|
|
6
|
+
ackTimeoutMs?: number;
|
|
7
|
+
fragmentSize?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* ReliableDataBridge
|
|
11
|
+
*
|
|
12
|
+
* Thin wrapper around the native C++ DataBridge implementation.
|
|
13
|
+
*/
|
|
14
|
+
declare class ReliableDataBridge extends EventEmitter {
|
|
15
|
+
private bridge;
|
|
16
|
+
private isOpen_;
|
|
17
|
+
private options;
|
|
18
|
+
constructor(options?: DataBridgeOptions);
|
|
19
|
+
static open(port: string, baudOrOptions?: number | DataBridgeOptions, cb?: (data: Buffer) => void): Promise<ReliableDataBridge>;
|
|
20
|
+
open(port: string, baud?: number): Promise<boolean>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
get isOpen(): boolean;
|
|
23
|
+
send(data: string | Buffer): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SerialPort {
|
|
27
|
+
open(port: string, baud: number, callback: (data: Buffer) => void): boolean;
|
|
28
|
+
write(data: Buffer): number;
|
|
29
|
+
close(): boolean;
|
|
30
|
+
isOpen(): boolean;
|
|
31
|
+
}
|
|
32
|
+
declare const SerialPort: {
|
|
33
|
+
new (): SerialPort;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* ResilientDataBridge - Connection-resilient wrapper
|
|
38
|
+
*
|
|
39
|
+
* Thin wrapper around the native C++ ResilientDataBridge implementation.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
interface ResilientOptions extends DataBridgeOptions {
|
|
43
|
+
/** Enable auto-reconnection (default: true) */
|
|
44
|
+
reconnect?: boolean;
|
|
45
|
+
/** Initial reconnect delay in ms (default: 1000) */
|
|
46
|
+
reconnectDelay?: number;
|
|
47
|
+
/** Maximum reconnect delay in ms (default: 30000) */
|
|
48
|
+
maxReconnectDelay?: number;
|
|
49
|
+
/** Maximum queue size before dropping old messages (default: 1000) */
|
|
50
|
+
maxQueueSize?: number;
|
|
51
|
+
}
|
|
52
|
+
interface ResilientEvents {
|
|
53
|
+
data: (data: Buffer) => void;
|
|
54
|
+
error: (error: Error) => void;
|
|
55
|
+
close: () => void;
|
|
56
|
+
disconnect: () => void;
|
|
57
|
+
reconnecting: (attempt: number, delay: number) => void;
|
|
58
|
+
reconnected: () => void;
|
|
59
|
+
}
|
|
60
|
+
declare class ResilientDataBridge extends EventEmitter {
|
|
61
|
+
private bridge;
|
|
62
|
+
private port;
|
|
63
|
+
private options;
|
|
64
|
+
private constructor();
|
|
65
|
+
/**
|
|
66
|
+
* Open a resilient serial connection.
|
|
67
|
+
*
|
|
68
|
+
* @param port - Serial port path
|
|
69
|
+
* @param options - Configuration including reconnection settings
|
|
70
|
+
*/
|
|
71
|
+
static open(port: string, options?: ResilientOptions): Promise<ResilientDataBridge>;
|
|
72
|
+
open(): Promise<boolean>;
|
|
73
|
+
/**
|
|
74
|
+
* Send data with guaranteed delivery.
|
|
75
|
+
*
|
|
76
|
+
* @param data - Data to send
|
|
77
|
+
* @returns Promise that resolves when data is acknowledged
|
|
78
|
+
*/
|
|
79
|
+
send(data: Buffer | string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Close the connection permanently.
|
|
82
|
+
*/
|
|
83
|
+
close(): Promise<void>;
|
|
84
|
+
/** Current connection state */
|
|
85
|
+
get connected(): boolean;
|
|
86
|
+
/** Number of messages waiting in queue */
|
|
87
|
+
get queueLength(): number;
|
|
88
|
+
on<K extends keyof ResilientEvents>(event: K, listener: ResilientEvents[K]): this;
|
|
89
|
+
once<K extends keyof ResilientEvents>(event: K, listener: ResilientEvents[K]): this;
|
|
90
|
+
emit<K extends keyof ResilientEvents>(event: K, ...args: Parameters<ResilientEvents[K]>): boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { ReliableDataBridge as DataBridge, type DataBridgeOptions, SerialPort as RawSerialPort, ResilientDataBridge, type ResilientEvents, type ResilientOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// lib/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
DataBridge: () => ReliableDataBridge,
|
|
34
|
+
RawSerialPort: () => SerialPort,
|
|
35
|
+
ResilientDataBridge: () => ResilientDataBridge
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// lib/reliable.ts
|
|
40
|
+
var import_events = require("events");
|
|
41
|
+
|
|
42
|
+
// lib/native.ts
|
|
43
|
+
var import_path = __toESM(require("path"));
|
|
44
|
+
function loadAddon() {
|
|
45
|
+
const possiblePaths = [
|
|
46
|
+
`../prebuilds/${process.platform}-${process.arch}/safeserial_node.node`,
|
|
47
|
+
"../build/Release/safeserial_node.node"
|
|
48
|
+
];
|
|
49
|
+
for (const p of possiblePaths) {
|
|
50
|
+
try {
|
|
51
|
+
return require(import_path.default.join(__dirname, p));
|
|
52
|
+
} catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
throw new Error("Failed to load native addon. Run `npm run build` first.");
|
|
57
|
+
}
|
|
58
|
+
var addon = loadAddon();
|
|
59
|
+
var SerialPort = addon.SerialPort;
|
|
60
|
+
var NativeDataBridge = addon.DataBridge;
|
|
61
|
+
var NativeResilientDataBridge = addon.ResilientDataBridge;
|
|
62
|
+
var Packet = addon.Packet;
|
|
63
|
+
var Reassembler = addon.Reassembler;
|
|
64
|
+
|
|
65
|
+
// lib/reliable.ts
|
|
66
|
+
var ReliableDataBridge = class _ReliableDataBridge extends import_events.EventEmitter {
|
|
67
|
+
bridge;
|
|
68
|
+
isOpen_ = false;
|
|
69
|
+
// Config
|
|
70
|
+
options;
|
|
71
|
+
constructor(options = {}) {
|
|
72
|
+
super();
|
|
73
|
+
this.bridge = new NativeDataBridge();
|
|
74
|
+
this.options = {
|
|
75
|
+
baudRate: options.baudRate ?? 115200,
|
|
76
|
+
maxRetries: options.maxRetries ?? 10,
|
|
77
|
+
ackTimeoutMs: options.ackTimeoutMs ?? 500,
|
|
78
|
+
fragmentSize: options.fragmentSize ?? 200
|
|
79
|
+
};
|
|
80
|
+
this.bridge.onData((data) => {
|
|
81
|
+
this.emit("data", data);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
static async open(port, baudOrOptions, cb) {
|
|
85
|
+
let opts = {};
|
|
86
|
+
if (typeof baudOrOptions === "number") {
|
|
87
|
+
opts.baudRate = baudOrOptions;
|
|
88
|
+
} else if (baudOrOptions) {
|
|
89
|
+
opts = baudOrOptions;
|
|
90
|
+
}
|
|
91
|
+
const bridge = new _ReliableDataBridge(opts);
|
|
92
|
+
if (cb) bridge.on("data", cb);
|
|
93
|
+
await bridge.open(port);
|
|
94
|
+
return bridge;
|
|
95
|
+
}
|
|
96
|
+
async open(port, baud) {
|
|
97
|
+
if (this.isOpen_) return true;
|
|
98
|
+
const baudRate = baud ?? this.options.baudRate;
|
|
99
|
+
const success = this.bridge.open(port, baudRate);
|
|
100
|
+
if (!success) {
|
|
101
|
+
throw new Error(`Failed to open ${port}`);
|
|
102
|
+
}
|
|
103
|
+
this.isOpen_ = true;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
async close() {
|
|
107
|
+
if (this.isOpen_) {
|
|
108
|
+
this.bridge.close();
|
|
109
|
+
this.isOpen_ = false;
|
|
110
|
+
this.emit("close");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
get isOpen() {
|
|
114
|
+
return this.isOpen_;
|
|
115
|
+
}
|
|
116
|
+
async send(data) {
|
|
117
|
+
if (!this.isOpen_) throw new Error("Port not open");
|
|
118
|
+
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
119
|
+
this.bridge.send(
|
|
120
|
+
buf,
|
|
121
|
+
this.options.ackTimeoutMs,
|
|
122
|
+
this.options.maxRetries,
|
|
123
|
+
this.options.fragmentSize
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// lib/resilient.ts
|
|
129
|
+
var import_events2 = require("events");
|
|
130
|
+
var ResilientDataBridge = class _ResilientDataBridge extends import_events2.EventEmitter {
|
|
131
|
+
bridge;
|
|
132
|
+
port;
|
|
133
|
+
options;
|
|
134
|
+
constructor(port, options) {
|
|
135
|
+
super();
|
|
136
|
+
this.port = port;
|
|
137
|
+
this.options = options;
|
|
138
|
+
this.bridge = new NativeResilientDataBridge(options);
|
|
139
|
+
this.bridge.onData((data) => this.emit("data", data));
|
|
140
|
+
this.bridge.onError((message) => this.emit("error", new Error(message)));
|
|
141
|
+
this.bridge.onDisconnect(() => this.emit("disconnect"));
|
|
142
|
+
this.bridge.onReconnecting(
|
|
143
|
+
(attempt, delay) => this.emit("reconnecting", attempt, delay)
|
|
144
|
+
);
|
|
145
|
+
this.bridge.onReconnected(() => this.emit("reconnected"));
|
|
146
|
+
this.bridge.onClose(() => this.emit("close"));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Open a resilient serial connection.
|
|
150
|
+
*
|
|
151
|
+
* @param port - Serial port path
|
|
152
|
+
* @param options - Configuration including reconnection settings
|
|
153
|
+
*/
|
|
154
|
+
static async open(port, options = {}) {
|
|
155
|
+
const instance = new _ResilientDataBridge(port, options);
|
|
156
|
+
await instance.open();
|
|
157
|
+
return instance;
|
|
158
|
+
}
|
|
159
|
+
async open() {
|
|
160
|
+
return this.bridge.open(this.port);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Send data with guaranteed delivery.
|
|
164
|
+
*
|
|
165
|
+
* @param data - Data to send
|
|
166
|
+
* @returns Promise that resolves when data is acknowledged
|
|
167
|
+
*/
|
|
168
|
+
async send(data) {
|
|
169
|
+
const buffer = typeof data === "string" ? Buffer.from(data) : data;
|
|
170
|
+
this.bridge.send(buffer);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Close the connection permanently.
|
|
174
|
+
*/
|
|
175
|
+
async close() {
|
|
176
|
+
this.bridge.close();
|
|
177
|
+
}
|
|
178
|
+
/** Current connection state */
|
|
179
|
+
get connected() {
|
|
180
|
+
return this.bridge.isConnected();
|
|
181
|
+
}
|
|
182
|
+
/** Number of messages waiting in queue */
|
|
183
|
+
get queueLength() {
|
|
184
|
+
return this.bridge.queueLength();
|
|
185
|
+
}
|
|
186
|
+
// Type-safe event emitter
|
|
187
|
+
on(event, listener) {
|
|
188
|
+
return super.on(event, listener);
|
|
189
|
+
}
|
|
190
|
+
once(event, listener) {
|
|
191
|
+
return super.once(event, listener);
|
|
192
|
+
}
|
|
193
|
+
emit(event, ...args) {
|
|
194
|
+
return super.emit(event, ...args);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
198
|
+
0 && (module.exports = {
|
|
199
|
+
DataBridge,
|
|
200
|
+
RawSerialPort,
|
|
201
|
+
ResilientDataBridge
|
|
202
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// lib/reliable.ts
|
|
9
|
+
import { EventEmitter } from "events";
|
|
10
|
+
|
|
11
|
+
// lib/native.ts
|
|
12
|
+
import path from "path";
|
|
13
|
+
function loadAddon() {
|
|
14
|
+
const possiblePaths = [
|
|
15
|
+
`../prebuilds/${process.platform}-${process.arch}/safeserial_node.node`,
|
|
16
|
+
"../build/Release/safeserial_node.node"
|
|
17
|
+
];
|
|
18
|
+
for (const p of possiblePaths) {
|
|
19
|
+
try {
|
|
20
|
+
return __require(path.join(__dirname, p));
|
|
21
|
+
} catch {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
throw new Error("Failed to load native addon. Run `npm run build` first.");
|
|
26
|
+
}
|
|
27
|
+
var addon = loadAddon();
|
|
28
|
+
var SerialPort = addon.SerialPort;
|
|
29
|
+
var NativeDataBridge = addon.DataBridge;
|
|
30
|
+
var NativeResilientDataBridge = addon.ResilientDataBridge;
|
|
31
|
+
var Packet = addon.Packet;
|
|
32
|
+
var Reassembler = addon.Reassembler;
|
|
33
|
+
|
|
34
|
+
// lib/reliable.ts
|
|
35
|
+
var ReliableDataBridge = class _ReliableDataBridge extends EventEmitter {
|
|
36
|
+
bridge;
|
|
37
|
+
isOpen_ = false;
|
|
38
|
+
// Config
|
|
39
|
+
options;
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
super();
|
|
42
|
+
this.bridge = new NativeDataBridge();
|
|
43
|
+
this.options = {
|
|
44
|
+
baudRate: options.baudRate ?? 115200,
|
|
45
|
+
maxRetries: options.maxRetries ?? 10,
|
|
46
|
+
ackTimeoutMs: options.ackTimeoutMs ?? 500,
|
|
47
|
+
fragmentSize: options.fragmentSize ?? 200
|
|
48
|
+
};
|
|
49
|
+
this.bridge.onData((data) => {
|
|
50
|
+
this.emit("data", data);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
static async open(port, baudOrOptions, cb) {
|
|
54
|
+
let opts = {};
|
|
55
|
+
if (typeof baudOrOptions === "number") {
|
|
56
|
+
opts.baudRate = baudOrOptions;
|
|
57
|
+
} else if (baudOrOptions) {
|
|
58
|
+
opts = baudOrOptions;
|
|
59
|
+
}
|
|
60
|
+
const bridge = new _ReliableDataBridge(opts);
|
|
61
|
+
if (cb) bridge.on("data", cb);
|
|
62
|
+
await bridge.open(port);
|
|
63
|
+
return bridge;
|
|
64
|
+
}
|
|
65
|
+
async open(port, baud) {
|
|
66
|
+
if (this.isOpen_) return true;
|
|
67
|
+
const baudRate = baud ?? this.options.baudRate;
|
|
68
|
+
const success = this.bridge.open(port, baudRate);
|
|
69
|
+
if (!success) {
|
|
70
|
+
throw new Error(`Failed to open ${port}`);
|
|
71
|
+
}
|
|
72
|
+
this.isOpen_ = true;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
async close() {
|
|
76
|
+
if (this.isOpen_) {
|
|
77
|
+
this.bridge.close();
|
|
78
|
+
this.isOpen_ = false;
|
|
79
|
+
this.emit("close");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
get isOpen() {
|
|
83
|
+
return this.isOpen_;
|
|
84
|
+
}
|
|
85
|
+
async send(data) {
|
|
86
|
+
if (!this.isOpen_) throw new Error("Port not open");
|
|
87
|
+
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
88
|
+
this.bridge.send(
|
|
89
|
+
buf,
|
|
90
|
+
this.options.ackTimeoutMs,
|
|
91
|
+
this.options.maxRetries,
|
|
92
|
+
this.options.fragmentSize
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// lib/resilient.ts
|
|
98
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
99
|
+
var ResilientDataBridge = class _ResilientDataBridge extends EventEmitter2 {
|
|
100
|
+
bridge;
|
|
101
|
+
port;
|
|
102
|
+
options;
|
|
103
|
+
constructor(port, options) {
|
|
104
|
+
super();
|
|
105
|
+
this.port = port;
|
|
106
|
+
this.options = options;
|
|
107
|
+
this.bridge = new NativeResilientDataBridge(options);
|
|
108
|
+
this.bridge.onData((data) => this.emit("data", data));
|
|
109
|
+
this.bridge.onError((message) => this.emit("error", new Error(message)));
|
|
110
|
+
this.bridge.onDisconnect(() => this.emit("disconnect"));
|
|
111
|
+
this.bridge.onReconnecting(
|
|
112
|
+
(attempt, delay) => this.emit("reconnecting", attempt, delay)
|
|
113
|
+
);
|
|
114
|
+
this.bridge.onReconnected(() => this.emit("reconnected"));
|
|
115
|
+
this.bridge.onClose(() => this.emit("close"));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Open a resilient serial connection.
|
|
119
|
+
*
|
|
120
|
+
* @param port - Serial port path
|
|
121
|
+
* @param options - Configuration including reconnection settings
|
|
122
|
+
*/
|
|
123
|
+
static async open(port, options = {}) {
|
|
124
|
+
const instance = new _ResilientDataBridge(port, options);
|
|
125
|
+
await instance.open();
|
|
126
|
+
return instance;
|
|
127
|
+
}
|
|
128
|
+
async open() {
|
|
129
|
+
return this.bridge.open(this.port);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Send data with guaranteed delivery.
|
|
133
|
+
*
|
|
134
|
+
* @param data - Data to send
|
|
135
|
+
* @returns Promise that resolves when data is acknowledged
|
|
136
|
+
*/
|
|
137
|
+
async send(data) {
|
|
138
|
+
const buffer = typeof data === "string" ? Buffer.from(data) : data;
|
|
139
|
+
this.bridge.send(buffer);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Close the connection permanently.
|
|
143
|
+
*/
|
|
144
|
+
async close() {
|
|
145
|
+
this.bridge.close();
|
|
146
|
+
}
|
|
147
|
+
/** Current connection state */
|
|
148
|
+
get connected() {
|
|
149
|
+
return this.bridge.isConnected();
|
|
150
|
+
}
|
|
151
|
+
/** Number of messages waiting in queue */
|
|
152
|
+
get queueLength() {
|
|
153
|
+
return this.bridge.queueLength();
|
|
154
|
+
}
|
|
155
|
+
// Type-safe event emitter
|
|
156
|
+
on(event, listener) {
|
|
157
|
+
return super.on(event, listener);
|
|
158
|
+
}
|
|
159
|
+
once(event, listener) {
|
|
160
|
+
return super.once(event, listener);
|
|
161
|
+
}
|
|
162
|
+
emit(event, ...args) {
|
|
163
|
+
return super.emit(event, ...args);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
export {
|
|
167
|
+
ReliableDataBridge as DataBridge,
|
|
168
|
+
SerialPort as RawSerialPort,
|
|
169
|
+
ResilientDataBridge
|
|
170
|
+
};
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* SafeSerial - Guaranteed Reliable Serial Communication
|
|
4
|
+
*
|
|
5
|
+
* TypeScript wrapper for the native Node-API addon.
|
|
6
|
+
* Provides a clean, async-friendly API for Electron applications.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ReliableDataBridge, DataBridgeOptions } from './reliable';
|
|
10
|
+
import { SerialPort } from './native';
|
|
11
|
+
|
|
12
|
+
// Export the Reliable implementation as the default DataBridge
|
|
13
|
+
export { ReliableDataBridge as DataBridge };
|
|
14
|
+
export { DataBridgeOptions };
|
|
15
|
+
|
|
16
|
+
// Export Raw SerialPort for advanced users
|
|
17
|
+
export { SerialPort as RawSerialPort };
|
|
18
|
+
|
|
19
|
+
// Export Resilient wrapper (connection resilience)
|
|
20
|
+
// Note: ResilientDataBridge wraps DataBridge.
|
|
21
|
+
// If DataBridge is now Reliable, ResilientDataBridge will hold a ReliableDataBridge.
|
|
22
|
+
// This gives us Connection Resilience + Data Reliability.
|
|
23
|
+
export { ResilientDataBridge, ResilientOptions, ResilientEvents } from './resilient';
|