@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
package/lib/native.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
// Load native addon
|
|
4
|
+
function loadAddon(): any {
|
|
5
|
+
const possiblePaths = [
|
|
6
|
+
`../prebuilds/${process.platform}-${process.arch}/safeserial_node.node`,
|
|
7
|
+
"../build/Release/safeserial_node.node",
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
for (const p of possiblePaths) {
|
|
11
|
+
try {
|
|
12
|
+
return require(path.join(__dirname, p));
|
|
13
|
+
} catch {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
throw new Error("Failed to load native addon. Run `npm run build` first.");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const addon = loadAddon();
|
|
21
|
+
|
|
22
|
+
export interface SerialPort {
|
|
23
|
+
open(port: string, baud: number, callback: (data: Buffer) => void): boolean;
|
|
24
|
+
write(data: Buffer): number;
|
|
25
|
+
close(): boolean;
|
|
26
|
+
isOpen(): boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const SerialPort: {
|
|
30
|
+
new (): SerialPort;
|
|
31
|
+
} = addon.SerialPort;
|
|
32
|
+
|
|
33
|
+
export interface NativeDataBridge {
|
|
34
|
+
open(port: string, baud?: number): boolean;
|
|
35
|
+
close(): boolean;
|
|
36
|
+
send(
|
|
37
|
+
data: Buffer | string,
|
|
38
|
+
ackTimeoutMs?: number,
|
|
39
|
+
maxRetries?: number,
|
|
40
|
+
fragmentSize?: number,
|
|
41
|
+
): number;
|
|
42
|
+
isOpen(): boolean;
|
|
43
|
+
onData(callback: (data: Buffer) => void): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const NativeDataBridge: {
|
|
47
|
+
new (): NativeDataBridge;
|
|
48
|
+
} = addon.DataBridge;
|
|
49
|
+
|
|
50
|
+
export interface NativeResilientDataBridge {
|
|
51
|
+
open(port: string): boolean;
|
|
52
|
+
close(): boolean;
|
|
53
|
+
send(data: Buffer | string): number;
|
|
54
|
+
isConnected(): boolean;
|
|
55
|
+
queueLength(): number;
|
|
56
|
+
onData(callback: (data: Buffer) => void): void;
|
|
57
|
+
onError(callback: (message: string) => void): void;
|
|
58
|
+
onDisconnect(callback: () => void): void;
|
|
59
|
+
onReconnecting(callback: (attempt: number, delay: number) => void): void;
|
|
60
|
+
onReconnected(callback: () => void): void;
|
|
61
|
+
onClose(callback: () => void): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const NativeResilientDataBridge: {
|
|
65
|
+
new (options?: any): NativeResilientDataBridge;
|
|
66
|
+
} = addon.ResilientDataBridge;
|
|
67
|
+
|
|
68
|
+
export interface Frame {
|
|
69
|
+
valid: boolean;
|
|
70
|
+
header: {
|
|
71
|
+
type: number;
|
|
72
|
+
seq_id: number;
|
|
73
|
+
fragment_id: number;
|
|
74
|
+
total_frags: number;
|
|
75
|
+
payload_len: number;
|
|
76
|
+
crc32: number;
|
|
77
|
+
};
|
|
78
|
+
payload: Buffer;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface PacketHelper {
|
|
82
|
+
serialize(
|
|
83
|
+
type: number,
|
|
84
|
+
seq: number,
|
|
85
|
+
payload: string | Buffer,
|
|
86
|
+
frag_id?: number,
|
|
87
|
+
total_frags?: number,
|
|
88
|
+
): Buffer;
|
|
89
|
+
deserialize(buffer: Buffer): { frame: Frame; remaining: Buffer };
|
|
90
|
+
TYPE_DATA: number;
|
|
91
|
+
TYPE_ACK: number;
|
|
92
|
+
TYPE_NACK: number;
|
|
93
|
+
TYPE_SYN: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const Packet: PacketHelper = addon.Packet;
|
|
97
|
+
|
|
98
|
+
export interface Reassembler {
|
|
99
|
+
processFragment(frame: Frame): boolean;
|
|
100
|
+
isComplete(frame: Frame): boolean;
|
|
101
|
+
getData(): Buffer;
|
|
102
|
+
isDuplicate(frame: Frame): boolean;
|
|
103
|
+
getBufferedSize(): number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const Reassembler: {
|
|
107
|
+
new (): Reassembler;
|
|
108
|
+
} = addon.Reassembler;
|
package/lib/reliable.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import { NativeDataBridge } from "./native";
|
|
3
|
+
|
|
4
|
+
export interface DataBridgeOptions {
|
|
5
|
+
baudRate?: number;
|
|
6
|
+
maxRetries?: number;
|
|
7
|
+
ackTimeoutMs?: number;
|
|
8
|
+
fragmentSize?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ReliableDataBridge
|
|
13
|
+
*
|
|
14
|
+
* Thin wrapper around the native C++ DataBridge implementation.
|
|
15
|
+
*/
|
|
16
|
+
export class ReliableDataBridge extends EventEmitter {
|
|
17
|
+
private bridge: NativeDataBridge;
|
|
18
|
+
private isOpen_ = false;
|
|
19
|
+
|
|
20
|
+
// Config
|
|
21
|
+
private options: Required<DataBridgeOptions>;
|
|
22
|
+
|
|
23
|
+
constructor(options: DataBridgeOptions = {}) {
|
|
24
|
+
super();
|
|
25
|
+
this.bridge = new NativeDataBridge();
|
|
26
|
+
|
|
27
|
+
this.options = {
|
|
28
|
+
baudRate: options.baudRate ?? 115200,
|
|
29
|
+
maxRetries: options.maxRetries ?? 10,
|
|
30
|
+
ackTimeoutMs: options.ackTimeoutMs ?? 500,
|
|
31
|
+
fragmentSize: options.fragmentSize ?? 200,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this.bridge.onData((data) => {
|
|
35
|
+
this.emit("data", data);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static async open(
|
|
40
|
+
port: string,
|
|
41
|
+
baudOrOptions?: number | DataBridgeOptions,
|
|
42
|
+
cb?: (data: Buffer) => void,
|
|
43
|
+
): Promise<ReliableDataBridge> {
|
|
44
|
+
let opts: DataBridgeOptions = {};
|
|
45
|
+
if (typeof baudOrOptions === "number") {
|
|
46
|
+
opts.baudRate = baudOrOptions;
|
|
47
|
+
} else if (baudOrOptions) {
|
|
48
|
+
opts = baudOrOptions;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const bridge = new ReliableDataBridge(opts);
|
|
52
|
+
if (cb) bridge.on("data", cb);
|
|
53
|
+
|
|
54
|
+
await bridge.open(port);
|
|
55
|
+
return bridge;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async open(port: string, baud?: number): Promise<boolean> {
|
|
59
|
+
if (this.isOpen_) return true;
|
|
60
|
+
|
|
61
|
+
// Backward compatibility: override baud from options if provided
|
|
62
|
+
const baudRate = baud ?? this.options.baudRate;
|
|
63
|
+
|
|
64
|
+
const success = this.bridge.open(port, baudRate);
|
|
65
|
+
if (!success) {
|
|
66
|
+
throw new Error(`Failed to open ${port}`);
|
|
67
|
+
}
|
|
68
|
+
this.isOpen_ = true;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async close(): Promise<void> {
|
|
73
|
+
if (this.isOpen_) {
|
|
74
|
+
this.bridge.close();
|
|
75
|
+
this.isOpen_ = false;
|
|
76
|
+
this.emit("close");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get isOpen(): boolean {
|
|
81
|
+
return this.isOpen_;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async send(data: string | Buffer): Promise<void> {
|
|
85
|
+
if (!this.isOpen_) throw new Error("Port not open");
|
|
86
|
+
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
87
|
+
this.bridge.send(
|
|
88
|
+
buf,
|
|
89
|
+
this.options.ackTimeoutMs,
|
|
90
|
+
this.options.maxRetries,
|
|
91
|
+
this.options.fragmentSize,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
package/lib/resilient.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResilientDataBridge - Connection-resilient wrapper
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around the native C++ ResilientDataBridge implementation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { EventEmitter } from "events";
|
|
8
|
+
import { DataBridgeOptions } from "./reliable";
|
|
9
|
+
import { NativeResilientDataBridge } from "./native";
|
|
10
|
+
|
|
11
|
+
export interface ResilientOptions extends DataBridgeOptions {
|
|
12
|
+
/** Enable auto-reconnection (default: true) */
|
|
13
|
+
reconnect?: boolean;
|
|
14
|
+
/** Initial reconnect delay in ms (default: 1000) */
|
|
15
|
+
reconnectDelay?: number;
|
|
16
|
+
/** Maximum reconnect delay in ms (default: 30000) */
|
|
17
|
+
maxReconnectDelay?: number;
|
|
18
|
+
/** Maximum queue size before dropping old messages (default: 1000) */
|
|
19
|
+
maxQueueSize?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ResilientEvents {
|
|
23
|
+
data: (data: Buffer) => void;
|
|
24
|
+
error: (error: Error) => void;
|
|
25
|
+
close: () => void;
|
|
26
|
+
disconnect: () => void;
|
|
27
|
+
reconnecting: (attempt: number, delay: number) => void;
|
|
28
|
+
reconnected: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class ResilientDataBridge extends EventEmitter {
|
|
32
|
+
private bridge: NativeResilientDataBridge;
|
|
33
|
+
private port: string;
|
|
34
|
+
private options: ResilientOptions;
|
|
35
|
+
|
|
36
|
+
private constructor(port: string, options: ResilientOptions) {
|
|
37
|
+
super();
|
|
38
|
+
this.port = port;
|
|
39
|
+
this.options = options;
|
|
40
|
+
|
|
41
|
+
this.bridge = new NativeResilientDataBridge(options);
|
|
42
|
+
this.bridge.onData((data) => this.emit("data", data));
|
|
43
|
+
this.bridge.onError((message) => this.emit("error", new Error(message)));
|
|
44
|
+
this.bridge.onDisconnect(() => this.emit("disconnect"));
|
|
45
|
+
this.bridge.onReconnecting((attempt, delay) =>
|
|
46
|
+
this.emit("reconnecting", attempt, delay),
|
|
47
|
+
);
|
|
48
|
+
this.bridge.onReconnected(() => this.emit("reconnected"));
|
|
49
|
+
this.bridge.onClose(() => this.emit("close"));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Open a resilient serial connection.
|
|
54
|
+
*
|
|
55
|
+
* @param port - Serial port path
|
|
56
|
+
* @param options - Configuration including reconnection settings
|
|
57
|
+
*/
|
|
58
|
+
static async open(
|
|
59
|
+
port: string,
|
|
60
|
+
options: ResilientOptions = {},
|
|
61
|
+
): Promise<ResilientDataBridge> {
|
|
62
|
+
const instance = new ResilientDataBridge(port, options);
|
|
63
|
+
await instance.open();
|
|
64
|
+
return instance;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async open(): Promise<boolean> {
|
|
68
|
+
return this.bridge.open(this.port);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Send data with guaranteed delivery.
|
|
73
|
+
*
|
|
74
|
+
* @param data - Data to send
|
|
75
|
+
* @returns Promise that resolves when data is acknowledged
|
|
76
|
+
*/
|
|
77
|
+
async send(data: Buffer | string): Promise<void> {
|
|
78
|
+
const buffer = typeof data === "string" ? Buffer.from(data) : data;
|
|
79
|
+
this.bridge.send(buffer);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Close the connection permanently.
|
|
84
|
+
*/
|
|
85
|
+
async close(): Promise<void> {
|
|
86
|
+
this.bridge.close();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Current connection state */
|
|
90
|
+
get connected(): boolean {
|
|
91
|
+
return this.bridge.isConnected();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Number of messages waiting in queue */
|
|
95
|
+
get queueLength(): number {
|
|
96
|
+
return this.bridge.queueLength();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Type-safe event emitter
|
|
100
|
+
on<K extends keyof ResilientEvents>(
|
|
101
|
+
event: K,
|
|
102
|
+
listener: ResilientEvents[K],
|
|
103
|
+
): this {
|
|
104
|
+
return super.on(event, listener);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
once<K extends keyof ResilientEvents>(
|
|
108
|
+
event: K,
|
|
109
|
+
listener: ResilientEvents[K],
|
|
110
|
+
): this {
|
|
111
|
+
return super.once(event, listener);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
emit<K extends keyof ResilientEvents>(
|
|
115
|
+
event: K,
|
|
116
|
+
...args: Parameters<ResilientEvents[K]>
|
|
117
|
+
): boolean {
|
|
118
|
+
return super.emit(event, ...args);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default ResilientDataBridge;
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@technoculture/safeserial",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Guaranteed reliable serial communication for Node.js and Electron",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"install": "node scripts/copy_deps.js && (prebuild-install || cmake-js compile)",
|
|
17
|
+
"build": "npm run build:native && npm run build:ts",
|
|
18
|
+
"build:native": "cmake-js compile",
|
|
19
|
+
"build:ts": "tsup lib/index.ts --format cjs,esm --dts",
|
|
20
|
+
"prebuild": "cmake-js compile -O prebuilds/$(node -p \"process.platform + '-' + process.arch\")",
|
|
21
|
+
"prepack": "node scripts/copy_deps.js && npm run build:ts",
|
|
22
|
+
"prepublishOnly": "npm run build:ts",
|
|
23
|
+
"test": "VITE_CJS_IGNORE_WARNING=1 vitest run",
|
|
24
|
+
"clean": "cmake-js clean && rm -rf dist prebuilds deps"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"prebuilds/**/*.node",
|
|
29
|
+
"deps",
|
|
30
|
+
"src",
|
|
31
|
+
"lib",
|
|
32
|
+
"scripts",
|
|
33
|
+
"CMakeLists.txt"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"node-addon-api": "^7.0.0",
|
|
37
|
+
"prebuild-install": "^7.1.1",
|
|
38
|
+
"cmake-js": "^7.3.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^20.10.0",
|
|
42
|
+
"tsup": "^8.0.0",
|
|
43
|
+
"typescript": "^5.3.0",
|
|
44
|
+
"vitest": "^1.0.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0"
|
|
48
|
+
},
|
|
49
|
+
"os": [
|
|
50
|
+
"win32",
|
|
51
|
+
"linux",
|
|
52
|
+
"darwin"
|
|
53
|
+
],
|
|
54
|
+
"cpu": [
|
|
55
|
+
"x64",
|
|
56
|
+
"arm64",
|
|
57
|
+
"ia32"
|
|
58
|
+
],
|
|
59
|
+
"binary": {
|
|
60
|
+
"napi_versions": [
|
|
61
|
+
8
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "https://github.com/technoculture/safeserial"
|
|
67
|
+
},
|
|
68
|
+
"keywords": [
|
|
69
|
+
"serial",
|
|
70
|
+
"uart",
|
|
71
|
+
"reliable",
|
|
72
|
+
"medical",
|
|
73
|
+
"embedded",
|
|
74
|
+
"electron",
|
|
75
|
+
"napi"
|
|
76
|
+
],
|
|
77
|
+
"license": "MIT"
|
|
78
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const PROJECT_ROOT = path.resolve(__dirname, '../../..');
|
|
5
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
6
|
+
const DEPS_DIR = path.join(PKG_ROOT, 'deps');
|
|
7
|
+
|
|
8
|
+
function copyDir(src, dest) {
|
|
9
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
10
|
+
let entries = fs.readdirSync(src, { withFileTypes: true });
|
|
11
|
+
|
|
12
|
+
for (let entry of entries) {
|
|
13
|
+
let srcPath = path.join(src, entry.name);
|
|
14
|
+
let destPath = path.join(dest, entry.name);
|
|
15
|
+
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
copyDir(srcPath, destPath);
|
|
18
|
+
} else {
|
|
19
|
+
fs.copyFileSync(srcPath, destPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log('[copy_deps] Bundling C++ core files...');
|
|
25
|
+
|
|
26
|
+
// Clean deps
|
|
27
|
+
if (fs.existsSync(DEPS_DIR)) {
|
|
28
|
+
fs.rmSync(DEPS_DIR, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
fs.mkdirSync(DEPS_DIR);
|
|
31
|
+
|
|
32
|
+
// Copy src
|
|
33
|
+
const srcDir = path.join(PROJECT_ROOT, 'src');
|
|
34
|
+
const destSrc = path.join(DEPS_DIR, 'src');
|
|
35
|
+
console.log(`Copying ${srcDir} -> ${destSrc}`);
|
|
36
|
+
copyDir(srcDir, destSrc);
|
|
37
|
+
|
|
38
|
+
// Copy include
|
|
39
|
+
const incDir = path.join(PROJECT_ROOT, 'include');
|
|
40
|
+
const destInc = path.join(DEPS_DIR, 'include');
|
|
41
|
+
console.log(`Copying ${incDir} -> ${destInc}`);
|
|
42
|
+
copyDir(incDir, destInc);
|
|
43
|
+
|
|
44
|
+
console.log('[copy_deps] Done.');
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const { ResilientDataBridge } = require('../dist/index.js');
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const port = process.argv[2];
|
|
5
|
+
|
|
6
|
+
// We don't strictly need item count here for logic, but helpful for knowing when to stop logging?
|
|
7
|
+
// The python runner kills us, so we just log what we get.
|
|
8
|
+
|
|
9
|
+
if (!port) {
|
|
10
|
+
console.error("Usage: node receiver.js <port>");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log(`[RECEIVER] Connecting to ${port}...`);
|
|
15
|
+
const bridge = await ResilientDataBridge.open(port, {
|
|
16
|
+
baudRate: 115200,
|
|
17
|
+
reconnect: true
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let receivedCount = 0;
|
|
21
|
+
|
|
22
|
+
bridge.on('data', (data) => {
|
|
23
|
+
const str = data.toString();
|
|
24
|
+
receivedCount++;
|
|
25
|
+
console.log(`[RECEIVER] Got: ${str} (Total: ${receivedCount})`);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log("[RECEIVER] Listening...");
|
|
29
|
+
|
|
30
|
+
// Keep alive until killed
|
|
31
|
+
await new Promise(() => { });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
main().catch(err => {
|
|
35
|
+
console.error("[RECEIVER] Fatal:", err);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const { ResilientDataBridge } = require('../dist/index.js');
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const port = process.argv[2];
|
|
5
|
+
const itemCount = parseInt(process.argv[3] || '20', 10);
|
|
6
|
+
const baudRate = 115200; // Match Chaos Monkey defaults
|
|
7
|
+
|
|
8
|
+
if (!port) {
|
|
9
|
+
console.error("Usage: node sender.js <port> [item_count]");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
console.log(`[SENDER] Connecting to ${port}...`);
|
|
14
|
+
const bridge = await ResilientDataBridge.open(port, {
|
|
15
|
+
baudRate,
|
|
16
|
+
reconnect: true
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
console.log(`[SENDER] Starting transmission of ${itemCount} items...`);
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < itemCount; i++) {
|
|
22
|
+
const msg = `Packet-${i}`;
|
|
23
|
+
try {
|
|
24
|
+
await bridge.send(msg);
|
|
25
|
+
console.log(`[SENDER] Sent: ${msg}`);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error(`[SENDER] Error sending ${msg}:`, err);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Small delay to prevent overwhelming the chaos monkey's internal buffer too fast
|
|
31
|
+
// if we want to simulate realistic traffic, though the bridge handles backpressure.
|
|
32
|
+
await new Promise(r => setTimeout(r, 50));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Give time for final ACKs
|
|
36
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
37
|
+
|
|
38
|
+
console.log("[SENDER] TEST COMPLETE");
|
|
39
|
+
|
|
40
|
+
// Clean exit
|
|
41
|
+
await bridge.close();
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
main().catch(err => {
|
|
46
|
+
console.error("[SENDER] Fatal:", err);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
});
|