secs4js 0.4.0 → 0.4.2

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/README.md CHANGED
@@ -1,442 +1,526 @@
1
- <h1 align="center">Secs4js</h1>
2
-
3
- <p align="center">A simple, efficient, and user-friendly SECS/GEM protocol library implemented in TypeScript.</p>
4
-
5
- ## Introduction
6
-
7
- This project is a TypeScript implementation of the SECS/GEM protocol, inspired by [pysemisecs](https://github.com/kenta-shimizu/pysemisecs).
8
-
9
- A special thanks to the author **kenta-shimizu** for their open-source contribution.
10
-
11
- Secs4js is a simple, efficient, and user-friendly SECS/GEM protocol library implemented in TypeScript. It provides a straightforward way to communicate with SECS/GEM devices, enabling you to easily read and write data using the SECS/GEM protocol.
12
-
13
- ## Supported Features
14
-
15
- - SECS-I (SEMI-E4)
16
- - SECS-I Virtual Serial Port (SECS-I on TCP/IP)
17
- - SECS-II (SEMI-E5)
18
- - GEM (SEMI-E30)
19
- - HSMS-SS (SEMI-E37.1)
20
- - **No HSMS-GS (SEMI-E37.2)**
21
-
22
- ## Installation
23
-
24
- ```shell
25
- npm i secs4js
26
-
27
- pnpm add secs4js
28
-
29
- yarn add secs4js
30
-
31
- bun add secs4js
32
- ```
33
-
34
- ## Getting Started from Source
35
-
36
- If you want to run some examples, they can be found in the `examples` directory.
37
-
38
- Run the following commands to start these examples:
39
-
40
- ```shell
41
- pnpm dlx tsx examples/gem_example.ts
42
-
43
- # ...
44
- pnpm dlx tsx examples/<example_file_name>.ts
45
- ```
46
-
47
- ## Usage
48
-
49
- ### 1. Creating SECS-II Messages
50
-
51
- I provide a concise, clear, and efficient way to create SECS-II message types. You can use the following code to import the required items:
52
-
53
- ```ts
54
- import { B, U1, U2, U4, U8, I1, I2, I4, I8, F4, F8, A, L } from "secs4js";
55
- ```
56
-
57
- Using these items, you can easily create SECS-II message types. For example, to create a message containing L, A, and U1 items, you can use the following code:
58
-
59
- ```ts
60
- import { L, A, U1, SecsMessage } from "secs4js";
61
-
62
- const body: AbstractSecs2Item = L(A("Hello, SECS/GEM!"), U1(123));
63
- ```
64
-
65
- Doesn't this highly resemble SML text syntax?
66
-
67
- All SECS-II messages are derived from the `AbstractSecs2Item` class, so you can use it to declare any SECS-II message of unknown type.
68
-
69
- If you don't like this approach, you can also use SML text syntax or the factory methods we provide to create SECS-II messages.
70
-
71
- Factory methods:
72
-
73
- ```ts
74
- import { Secs2ItemFactory } from "secs4js";
75
-
76
- // Create a message containing L, A, and U1 items
77
- const newMsg = Secs2ItemFactory.createListItem(
78
- Secs2ItemFactory.createAsciiItem("Hello World"),
79
- Secs2ItemFactory.createU1Item(123),
80
- );
81
- ```
82
-
83
- SML Conversion Support:
84
-
85
- You can use the `toSml` method of the `AbstractSecs2Item` class to convert SECS-II messages to SML text. For example:
86
-
87
- ```ts
88
- console.log(newMsg.toSml());
89
-
90
- // Output:
91
- // <L
92
- // <A "Hello World">
93
- // <U1 123>
94
- // >.
95
- ```
96
-
97
- ### 2. Creating SECS Messages
98
-
99
- We provide two ways to create SECS-II messages:
100
-
101
- 1. Use the `SecsMessage` class to create SECS-II messages.
102
- 2. Create SECS-II messages by parsing SML syntax text. You can use the `SmlParser` static class to parse SML text and create corresponding SECS-II messages.
103
-
104
- #### new SecsMessage(...)
105
-
106
- > You can use the `SecsMessage` class to create SECS-II messages. The class constructor accepts the following parameters:
107
- >
108
- > - `stream`: Stream number, one byte, range 0-255.
109
- > - `function`: Function number, one byte, range 0-255.
110
- > - `wBit`: W-bit, a boolean indicating whether to enable W-bit (i.e., whether a reply is required).
111
- > - `body`: SECS-II message body, an `AbstractSecs2Item` instance.
112
- > - We will automatically generate the message's `length` and `systemBytes`, so you don't need to manage them manually.
113
-
114
- ```ts
115
- import { SecsMessage } from "secs4js";
116
-
117
- const newMsg = new SecsMessage(1, 13, true, L(L(A("Hello World"), U1(123))));
118
- ```
119
-
120
- #### SmlParser
121
-
122
- ```ts
123
- import { SmlParser } from "secs4js";
124
-
125
- // Complete SML text
126
- const sml = `
127
- S1F13 W
128
- <L
129
- <B 0x20>
130
- <A "Hello World">
131
- >.
132
- `;
133
-
134
- // SML text containing only the message body
135
- const smlBody = `
136
- <L
137
- <B 0x20>
138
- <A "Hello World">
139
- >.
140
- `;
141
-
142
- // Parse complete SML text into a SecsMessage instance using the parse method
143
- const parsedMessage = SmlParser.parse(sml);
144
- const firstBodyItem =
145
- parsedMessage.body instanceof Secs2ItemList
146
- ? parsedMessage.body.value[0]
147
- : null;
148
- const bytes =
149
- firstBodyItem instanceof Secs2ItemBinary ? firstBodyItem.value : null;
150
- console.log(
151
- parsedMessage.stream,
152
- parsedMessage.func,
153
- parsedMessage.wBit,
154
- bytes,
155
- );
156
-
157
- // Parse SML text containing only the message body into an AbstractSecs2Item instance using the parseBody method
158
- const parsedBody = SmlParser.parseBody(smlBody);
159
- console.log(parsedBody?.toSml());
160
- ```
161
-
162
- ## HSMS-SS
163
-
164
- For HSMS-SS protocol support, you can act as the passive end (Equipment) or the active end (HOST/EAP).
165
-
166
- ### Active
167
-
168
- Quick start:
169
-
170
- ```ts
171
- const active = new HsmsActiveCommunicator({
172
- ip: "127.0.0.1",
173
- port: 5000,
174
- deviceId: 10,
175
- isEquip: false,
176
- // If you need to customize the timeout values, you can add additional parameters
177
- // timeoutT1: 10,
178
- // ...
179
- });
180
-
181
- active.on("connected", () => console.log("Active TCP Connected"));
182
- active.on("disconnected", () => console.log("Active Disconnected"));
183
- active.on("selected", () => console.log("Active Selected (HSMS Ready)"));
184
-
185
- await active.open();
186
- console.log("Active opened");
187
-
188
- // Active will automatically send SelectReq and start heartbeat
189
-
190
- await active.untilConnected(); // Wait for Select success
191
-
192
- // When you need to receive and process messages, you can listen for the "message" event
193
- active.on("message", (msg: SecsMessage) => {
194
- void (async () => {
195
- console.log(`Active received: ${msg.toSml()}`);
196
- if (msg.stream === 1 && msg.func === 1) {
197
- await active.reply(msg, 1, 2, L(A("MDLN-A"), A("SOFTREV-1")));
198
- }
199
- if (msg.stream === 1 && msg.func === 13) {
200
- await active.reply(msg, 1, 14, L(A("ACK")));
201
- }
202
- })();
203
- });
204
-
205
- const reply = await active.send(1, 1, true);
206
- console.log(`Active received reply: ${reply?.toSml()}`);
207
-
208
- // Interaction results with the simulator
209
- // Our reply message:
210
- // 2025-12-30 01:26:44.866:onReceivedEvent[TOOL] DeviceID=[10] SB=[6110]
211
- // S1F2
212
- // <L[2/1]
213
- // <A[6/1] "MDLN-A">
214
- // <A[9/1] "SOFTREV-1">
215
- // >.
216
-
217
- // Message actively sent by the simulator:
218
- // 2025-12-30 01:26:44.864:OnSent[TOOL] DeviceID=[1] SB=[6110]
219
-
220
- // S1F1 W.
221
- // 2025-12-30 01:26:44.864:Send the Message successfully.
222
-
223
- // Message replied by the simulator:
224
- // 2025-12-30 01:26:40.449:OnSent[TOOL] DeviceID=[10] SB=[2]
225
- // S1F2
226
- // <L[0/1]>.
227
-
228
- // Message we actively sent:
229
- // 2025-12-30 01:26:40.445:Do not find Tool in QutoReply List by Tool[TOOL] SFName=[S1F1]
230
- // 2025-12-30 01:26:40.444:onReceivedEvent[TOOL] DeviceID=[10] SB=[2]
231
- // S1F1 W.
232
- ```
233
-
234
- ### Passive
235
-
236
- ```ts
237
- import {
238
- HsmsPassiveCommunicator,
239
- SecsMessage,
240
- CommAck,
241
- OnlAck,
242
- Gem,
243
- } from "secs4js";
244
-
245
- // 1. Set up Equipment side (Passive)
246
- const equipComm = new HsmsPassiveCommunicator({
247
- ip: "127.0.0.1",
248
- port: 5000,
249
- deviceId: 1,
250
- isEquip: true,
251
- name: "Equipment",
252
- });
253
-
254
- // Use the GEM helper class (optional)
255
- const equipGem = new Gem(equipComm);
256
- equipGem.mdln = "MyEquip";
257
- equipGem.softrev = "1.0.0";
258
-
259
- // Handle received messages
260
- equipComm.on("message", (msg: SecsMessage) => {
261
- void (async () => {
262
- try {
263
- // S1F13: Establish Communications Request
264
- if (msg.stream === 1 && msg.func === 13) {
265
- console.log("[Equip] Received S1F13, replying S1F14...");
266
- await equipGem.s1f14(msg, CommAck.OK);
267
- }
268
- // S1F17: Request ON-LINE
269
- else if (msg.stream === 1 && msg.func === 17) {
270
- console.log("[Equip] Received S1F17, replying S1F18...");
271
- await equipGem.s1f18(msg, OnlAck.OK);
272
- }
273
- // S2F17: Date and Time Request
274
- else if (msg.stream === 2 && msg.func === 17) {
275
- console.log("[Equip] Received S2F17, replying S2F18...");
276
- await equipGem.s2f18Now(msg);
277
- } else {
278
- console.log(
279
- `[Equip] Received unhandled message S${msg.stream}F${msg.func}`,
280
- );
281
- }
282
- } catch (err) {
283
- console.error("[Equip] Error handling message:", err);
284
- }
285
- })();
286
- });
287
-
288
- await equipComm.open();
289
- console.log("Passive opened and listening");
290
- ```
291
-
292
- ## SECS-I Serial
293
-
294
- Supports SECS-I communication via serial port.
295
-
296
- **Note**:
297
-
298
- - Serial port communication needs to be tested on devices that support the SECS-I protocol.
299
- - Ensure the serial port path and baud rate match your device configuration.
300
- - If you want to test locally first, we recommend using a **virtual serial port tool** to simulate serial port communication.
301
-
302
- ### Active
303
-
304
- ```ts
305
- import { A, L, Secs1SerialCommunicator, SecsMessage } from "secs4js";
306
-
307
- async function SerialActive() {
308
- const active = new Secs1SerialCommunicator({
309
- path: "COM5", // Serial port path
310
- baudRate: 9600, // Baud rate
311
- deviceId: 10,
312
- isEquip: false, // Whether it is equipment
313
- });
314
-
315
- active.on("message", (msg: SecsMessage) => {
316
- void (async () => {
317
- console.log(`Active received: ${msg.toSml()}`);
318
- if (msg.stream === 1 && msg.func === 1) {
319
- await active.reply(msg, 1, 2, L(A("MDLN-A"), A("SOFTREV-1")));
320
- }
321
- })();
322
- });
323
-
324
- active.on("connected", () => {
325
- console.log("Active connected");
326
- });
327
-
328
- await active.open();
329
- console.log("Active opened");
330
- }
331
-
332
- SerialActive().catch((err) => console.error(err));
333
-
334
- // Communication results with the simulator
335
- // Our reply message:
336
- // 2025-12-30 01:35:40.187:onReceivedEvent[SERIAL_EQP] DeviceID=[10] SB=[5985]
337
- // S1F2
338
- // <L[2/1]
339
- // <A[6/1] "MDLN-A">
340
- // <A[9/1] "SOFTREV-1">
341
- // >.
342
-
343
- // Message actively sent by the simulator:
344
- // 2025-12-30 01:35:40.155:OnSent[SERIAL_EQP] DeviceID=[1] SB=[5985]
345
-
346
- // S1F1 W.
347
- // 2025-12-30 01:35:40.095:Send the Message successfully.
348
- ```
349
-
350
- ### Passive
351
-
352
- ```ts
353
- import { Secs1SerialCommunicator, SecsMessage, L, A } from "secs4js";
354
-
355
- async function SerialPassive() {
356
- const passive = new Secs1SerialCommunicator({
357
- path: "COM5",
358
- baudRate: 9600,
359
- deviceId: 10,
360
- isEquip: true,
361
- });
362
-
363
- passive.on("message", (msg: SecsMessage) => {
364
- void (async () => {
365
- if (msg.stream === 1 && msg.func === 1) {
366
- await passive.reply(msg, 1, 2, L(A("MDLN-A"), A("SOFTREV-1")));
367
- }
368
- console.log(`Passive received: ${msg.toSml()}`);
369
- })();
370
- });
371
-
372
- await passive.open();
373
- console.log("Passive opened");
374
- }
375
-
376
- SerialPassive().catch((err) => console.error(err));
377
- ```
378
-
379
- ## SECS-I On TCP/IP
380
-
381
- Supports SECS-I serial communication via TCP/IP (usually used for testing or connecting through a terminal server).
382
-
383
- ### Active
384
-
385
- ```ts
386
- import { Secs1OnTcpIpActiveCommunicator, SecsMessage, L, A } from "secs4js";
387
-
388
- async function TcpActive() {
389
- const active = new Secs1OnTcpIpActiveCommunicator({
390
- ip: "127.0.0.1",
391
- port: 5000,
392
- deviceId: 10,
393
- isEquip: false,
394
- });
395
-
396
- active.on("message", (msg: SecsMessage) => {
397
- void (async () => {
398
- console.log(`Active received: ${msg.toSml()}`);
399
- // Handle message...
400
- })();
401
- });
402
-
403
- active.on("connected", () => {
404
- console.log("Active connected");
405
- });
406
-
407
- await active.open();
408
- }
409
- ```
410
-
411
- ### Passive
412
-
413
- ```ts
414
- import { Secs1OnTcpIpPassiveCommunicator, SecsMessage, L, A } from "secs4js";
415
-
416
- async function TcpPassive() {
417
- const passive = new Secs1OnTcpIpPassiveCommunicator({
418
- ip: "0.0.0.0",
419
- port: 5000,
420
- deviceId: 10,
421
- isEquip: true,
422
- });
423
-
424
- passive.on("message", (msg: SecsMessage) => {
425
- void (async () => {
426
- console.log(`Passive received: ${msg.toSml()}`);
427
- // Process message and reply...
428
- })();
429
- });
430
-
431
- await passive.open();
432
- console.log("Passive server started");
433
- }
434
- ```
435
-
436
- ## Development
437
-
438
- If you are interested in this project, welcome to contribute your code!
439
-
440
- Thank you for your contribution! 💖
441
-
442
- > 💝 This project was generated using [`create-typescript-app`](https://github.com/JoshuaKGoldberg/create-typescript-app) and the [Bingo framework](https://create.bingo).
1
+ <h1 align="center">Secs4js</h1>
2
+
3
+ <p align="center">A simple, efficient, and user-friendly SECS/GEM protocol library implemented in TypeScript.</p>
4
+
5
+ ## Introduction
6
+
7
+ This project is a TypeScript implementation of the SECS/GEM protocol, inspired by [pysemisecs](https://github.com/kenta-shimizu/pysemisecs).
8
+
9
+ A special thanks to the author **kenta-shimizu** for their open-source contribution.
10
+
11
+ Secs4js is a simple, efficient, and user-friendly SECS/GEM protocol library implemented in TypeScript. It provides a straightforward way to communicate with SECS/GEM devices, enabling you to easily read and write data using the SECS/GEM protocol.
12
+
13
+ ## Supported Features
14
+
15
+ - SECS-I (SEMI-E4)
16
+ - SECS-I Virtual Serial Port (SECS-I on TCP/IP)
17
+ - SECS-II (SEMI-E5)
18
+ - GEM (SEMI-E30)
19
+ - HSMS-SS (SEMI-E37.1)
20
+ - **No HSMS-GS (SEMI-E37.2)**
21
+
22
+ ## Installation
23
+
24
+ ```shell
25
+ npm i secs4js
26
+
27
+ pnpm add secs4js
28
+
29
+ yarn add secs4js
30
+
31
+ bun add secs4js
32
+ ```
33
+
34
+ ## Getting Started from Source
35
+
36
+ If you want to run some examples, they can be found in the `examples` directory.
37
+
38
+ Run the following commands to start these examples:
39
+
40
+ ```shell
41
+ pnpm dlx tsx examples/gem_example.ts
42
+
43
+ # ...
44
+ pnpm dlx tsx examples/<example_file_name>.ts
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ### 1. Creating SECS-II Messages
50
+
51
+ I provide a concise, clear, and efficient way to create SECS-II message types. You can use the following code to import the required items:
52
+
53
+ ```ts
54
+ import { B, U1, U2, U4, U8, I1, I2, I4, I8, F4, F8, A, L } from "secs4js";
55
+ ```
56
+
57
+ Using these items, you can easily create SECS-II message types. For example, to create a message containing L, A, and U1 items, you can use the following code:
58
+
59
+ ```ts
60
+ import { L, A, U1, SecsMessage } from "secs4js";
61
+
62
+ const body: AbstractSecs2Item = L(A("Hello, SECS/GEM!"), U1(123));
63
+ ```
64
+
65
+ Doesn't this highly resemble SML text syntax?
66
+
67
+ All SECS-II messages are derived from the `AbstractSecs2Item` class, so you can use it to declare any SECS-II message of unknown type.
68
+
69
+ If you don't like this approach, you can also use SML text syntax or the factory methods we provide to create SECS-II messages.
70
+
71
+ Factory methods:
72
+
73
+ ```ts
74
+ import { Secs2ItemFactory } from "secs4js";
75
+
76
+ // Create a message containing L, A, and U1 items
77
+ const newMsg = Secs2ItemFactory.createListItem(
78
+ Secs2ItemFactory.createAsciiItem("Hello World"),
79
+ Secs2ItemFactory.createU1Item(123),
80
+ );
81
+ ```
82
+
83
+ SML Conversion Support:
84
+
85
+ You can use the `toSml` method of the `AbstractSecs2Item` class to convert SECS-II messages to SML text. For example:
86
+
87
+ ```ts
88
+ console.log(newMsg.toSml());
89
+
90
+ // Output:
91
+ // <L
92
+ // <A "Hello World">
93
+ // <U1 123>
94
+ // >.
95
+ ```
96
+
97
+ ### 2. Creating SECS Messages
98
+
99
+ We provide two ways to create SECS-II messages:
100
+
101
+ 1. Use the `SecsMessage` class to create SECS-II messages.
102
+ 2. Create SECS-II messages by parsing SML syntax text. You can use the `SmlParser` static class to parse SML text and create corresponding SECS-II messages.
103
+
104
+ #### new SecsMessage(...)
105
+
106
+ > You can use the `SecsMessage` class to create SECS-II messages. The class constructor accepts the following parameters:
107
+ >
108
+ > - `stream`: Stream number, one byte, range 0-255.
109
+ > - `function`: Function number, one byte, range 0-255.
110
+ > - `wBit`: W-bit, a boolean indicating whether to enable W-bit (i.e., whether a reply is required).
111
+ > - `body`: SECS-II message body, an `AbstractSecs2Item` instance.
112
+ > - We will automatically generate the message's `length` and `systemBytes`, so you don't need to manage them manually.
113
+
114
+ ```ts
115
+ import { SecsMessage } from "secs4js";
116
+
117
+ const newMsg = new SecsMessage(1, 13, true, L(L(A("Hello World"), U1(123))));
118
+ ```
119
+
120
+ #### SmlParser
121
+
122
+ ```ts
123
+ import { SmlParser } from "secs4js";
124
+
125
+ // Complete SML text
126
+ const sml = `
127
+ S1F13 W
128
+ <L
129
+ <B 0x20>
130
+ <A "Hello World">
131
+ >.
132
+ `;
133
+
134
+ // SML text containing only the message body
135
+ const smlBody = `
136
+ <L
137
+ <B 0x20>
138
+ <A "Hello World">
139
+ >.
140
+ `;
141
+
142
+ // Parse complete SML text into a SecsMessage instance using the parse method
143
+ const parsedMessage = SmlParser.parse(sml);
144
+ const firstBodyItem =
145
+ parsedMessage.body instanceof Secs2ItemList
146
+ ? parsedMessage.body.value[0]
147
+ : null;
148
+ const bytes =
149
+ firstBodyItem instanceof Secs2ItemBinary ? firstBodyItem.value : null;
150
+ console.log(
151
+ parsedMessage.stream,
152
+ parsedMessage.func,
153
+ parsedMessage.wBit,
154
+ bytes,
155
+ );
156
+
157
+ // Parse SML text containing only the message body into an AbstractSecs2Item instance using the parseBody method
158
+ const parsedBody = SmlParser.parseBody(smlBody);
159
+ console.log(parsedBody?.toSml());
160
+ ```
161
+
162
+ ## Sending and Replying to Messages
163
+
164
+ In this library, you can actively send messages and passively reply to messages.
165
+
166
+ For actively sent messages, a new `SystemBytes` will be automatically generated. For reply messages, the `SystemBytes` from the primary message will be automatically read and used for the reply.
167
+
168
+ - **Send:** `send(stream: number, func: number, wBit: boolean, body?: AbstractSecs2Item)`
169
+ - **Reply:** `reply(primaryMsg: SecsMessage, stream: number, func: number, body?: AbstractSecs2Item)`
170
+
171
+ ```ts
172
+ active.on("message", (msg: SecsMessage) => {
173
+ void (async () => {
174
+ console.log(`Active received: ${msg.toSml()}`);
175
+
176
+ await active.send(2, 18, true, L());
177
+
178
+ if (msg.stream === 1 && msg.func === 1) {
179
+ await active.reply(msg, 1, 2, L(A("MDLN-A"), A("SOFTREV-1")));
180
+ }
181
+
182
+ if (msg.stream === 1 && msg.func === 13) {
183
+ await active.reply(msg, 1, 14, L(A("ACK")));
184
+ }
185
+ })();
186
+ });
187
+ ```
188
+
189
+ ## HSMS-SS
190
+
191
+ For HSMS-SS protocol support, you can act as the passive end (Equipment) or the active end (HOST/EAP).
192
+
193
+ ### Active
194
+
195
+ Quick start:
196
+
197
+ ```ts
198
+ const active = new HsmsActiveCommunicator({
199
+ ip: "127.0.0.1",
200
+ port: 5000,
201
+ deviceId: 10,
202
+ isEquip: false,
203
+ // If you need to customize the timeout values, you can add additional parameters
204
+ // timeoutT1: 10,
205
+ // ...
206
+ });
207
+
208
+ active.on("connected", () => console.log("Active TCP Connected"));
209
+ active.on("disconnected", () => console.log("Active Disconnected"));
210
+ active.on("selected", () => console.log("Active Selected (HSMS Ready)"));
211
+
212
+ await active.open();
213
+ console.log("Active opened");
214
+
215
+ // Active will automatically send SelectReq and start heartbeat
216
+
217
+ await active.untilConnected(); // Wait for Select success
218
+
219
+ // When you need to receive and process messages, you can listen for the "message" event
220
+ active.on("message", (msg: SecsMessage) => {
221
+ void (async () => {
222
+ console.log(`Active received: ${msg.toSml()}`);
223
+ if (msg.stream === 1 && msg.func === 1) {
224
+ await active.reply(msg, 1, 2, L(A("MDLN-A"), A("SOFTREV-1")));
225
+ }
226
+ if (msg.stream === 1 && msg.func === 13) {
227
+ await active.reply(msg, 1, 14, L(A("ACK")));
228
+ }
229
+ })();
230
+ });
231
+
232
+ const reply = await active.send(1, 1, true);
233
+ console.log(`Active received reply: ${reply?.toSml()}`);
234
+
235
+ // Interaction results with the simulator
236
+ // Our reply message:
237
+ // 2025-12-30 01:26:44.866:onReceivedEvent[TOOL] DeviceID=[10] SB=[6110]
238
+ // S1F2
239
+ // <L[2/1]
240
+ // <A[6/1] "MDLN-A">
241
+ // <A[9/1] "SOFTREV-1">
242
+ // >.
243
+
244
+ // Message actively sent by the simulator:
245
+ // 2025-12-30 01:26:44.864:OnSent[TOOL] DeviceID=[1] SB=[6110]
246
+
247
+ // S1F1 W.
248
+ // 2025-12-30 01:26:44.864:Send the Message successfully.
249
+
250
+ // Message replied by the simulator:
251
+ // 2025-12-30 01:26:40.449:OnSent[TOOL] DeviceID=[10] SB=[2]
252
+ // S1F2
253
+ // <L[0/1]>.
254
+
255
+ // Message we actively sent:
256
+ // 2025-12-30 01:26:40.445:Do not find Tool in QutoReply List by Tool[TOOL] SFName=[S1F1]
257
+ // 2025-12-30 01:26:40.444:onReceivedEvent[TOOL] DeviceID=[10] SB=[2]
258
+ // S1F1 W.
259
+ ```
260
+
261
+ ### Passive
262
+
263
+ ```ts
264
+ import {
265
+ HsmsPassiveCommunicator,
266
+ SecsMessage,
267
+ CommAck,
268
+ OnlAck,
269
+ Gem,
270
+ } from "secs4js";
271
+
272
+ // 1. Set up Equipment side (Passive)
273
+ const equipComm = new HsmsPassiveCommunicator({
274
+ ip: "127.0.0.1",
275
+ port: 5000,
276
+ deviceId: 1,
277
+ isEquip: true,
278
+ name: "Equipment",
279
+ });
280
+
281
+ // Use the GEM helper class (optional)
282
+ const equipGem = new Gem(equipComm);
283
+ equipGem.mdln = "MyEquip";
284
+ equipGem.softrev = "1.0.0";
285
+
286
+ // Handle received messages
287
+ equipComm.on("message", (msg: SecsMessage) => {
288
+ void (async () => {
289
+ try {
290
+ // S1F13: Establish Communications Request
291
+ if (msg.stream === 1 && msg.func === 13) {
292
+ console.log("[Equip] Received S1F13, replying S1F14...");
293
+ await equipGem.s1f14(msg, CommAck.OK);
294
+ }
295
+ // S1F17: Request ON-LINE
296
+ else if (msg.stream === 1 && msg.func === 17) {
297
+ console.log("[Equip] Received S1F17, replying S1F18...");
298
+ await equipGem.s1f18(msg, OnlAck.OK);
299
+ }
300
+ // S2F17: Date and Time Request
301
+ else if (msg.stream === 2 && msg.func === 17) {
302
+ console.log("[Equip] Received S2F17, replying S2F18...");
303
+ await equipGem.s2f18Now(msg);
304
+ } else {
305
+ console.log(
306
+ `[Equip] Received unhandled message S${msg.stream}F${msg.func}`,
307
+ );
308
+ }
309
+ } catch (err) {
310
+ console.error("[Equip] Error handling message:", err);
311
+ }
312
+ })();
313
+ });
314
+
315
+ await equipComm.open();
316
+ console.log("Passive opened and listening");
317
+ ```
318
+
319
+ ## SECS-I Serial
320
+
321
+ Supports SECS-I communication via serial port.
322
+
323
+ **Note**:
324
+
325
+ - Serial port communication needs to be tested on devices that support the SECS-I protocol.
326
+ - Ensure the serial port path and baud rate match your device configuration.
327
+ - If you want to test locally first, we recommend using a **virtual serial port tool** to simulate serial port communication.
328
+
329
+ ### Active
330
+
331
+ ```ts
332
+ import { A, L, Secs1SerialCommunicator, SecsMessage } from "secs4js";
333
+
334
+ async function SerialActive() {
335
+ const active = new Secs1SerialCommunicator({
336
+ path: "COM5", // Serial port path
337
+ baudRate: 9600, // Baud rate
338
+ deviceId: 10,
339
+ isEquip: false, // Whether it is equipment
340
+ });
341
+
342
+ active.on("message", (msg: SecsMessage) => {
343
+ void (async () => {
344
+ console.log(`Active received: ${msg.toSml()}`);
345
+ if (msg.stream === 1 && msg.func === 1) {
346
+ await active.reply(msg, 1, 2, L(A("MDLN-A"), A("SOFTREV-1")));
347
+ }
348
+ })();
349
+ });
350
+
351
+ active.on("connected", () => {
352
+ console.log("Active connected");
353
+ });
354
+
355
+ await active.open();
356
+ console.log("Active opened");
357
+ }
358
+
359
+ SerialActive().catch((err) => console.error(err));
360
+
361
+ // Communication results with the simulator
362
+ // Our reply message:
363
+ // 2025-12-30 01:35:40.187:onReceivedEvent[SERIAL_EQP] DeviceID=[10] SB=[5985]
364
+ // S1F2
365
+ // <L[2/1]
366
+ // <A[6/1] "MDLN-A">
367
+ // <A[9/1] "SOFTREV-1">
368
+ // >.
369
+
370
+ // Message actively sent by the simulator:
371
+ // 2025-12-30 01:35:40.155:OnSent[SERIAL_EQP] DeviceID=[1] SB=[5985]
372
+
373
+ // S1F1 W.
374
+ // 2025-12-30 01:35:40.095:Send the Message successfully.
375
+ ```
376
+
377
+ ### Passive
378
+
379
+ ```ts
380
+ import { Secs1SerialCommunicator, SecsMessage, L, A } from "secs4js";
381
+
382
+ async function SerialPassive() {
383
+ const passive = new Secs1SerialCommunicator({
384
+ path: "COM5",
385
+ baudRate: 9600,
386
+ deviceId: 10,
387
+ isEquip: true,
388
+ });
389
+
390
+ passive.on("message", (msg: SecsMessage) => {
391
+ void (async () => {
392
+ if (msg.stream === 1 && msg.func === 1) {
393
+ await passive.reply(msg, 1, 2, L(A("MDLN-A"), A("SOFTREV-1")));
394
+ }
395
+ console.log(`Passive received: ${msg.toSml()}`);
396
+ })();
397
+ });
398
+
399
+ await passive.open();
400
+ console.log("Passive opened");
401
+ }
402
+
403
+ SerialPassive().catch((err) => console.error(err));
404
+ ```
405
+
406
+ ## SECS-I On TCP/IP
407
+
408
+ Supports SECS-I serial communication via TCP/IP (usually used for testing or connecting through a terminal server).
409
+
410
+ ### Active
411
+
412
+ ```ts
413
+ import { Secs1OnTcpIpActiveCommunicator, SecsMessage, L, A } from "secs4js";
414
+
415
+ async function TcpActive() {
416
+ const active = new Secs1OnTcpIpActiveCommunicator({
417
+ ip: "127.0.0.1",
418
+ port: 5000,
419
+ deviceId: 10,
420
+ isEquip: false,
421
+ });
422
+
423
+ active.on("message", (msg: SecsMessage) => {
424
+ void (async () => {
425
+ console.log(`Active received: ${msg.toSml()}`);
426
+ // Handle message...
427
+ })();
428
+ });
429
+
430
+ active.on("connected", () => {
431
+ console.log("Active connected");
432
+ });
433
+
434
+ await active.open();
435
+ }
436
+ ```
437
+
438
+ ### Passive
439
+
440
+ ```ts
441
+ import { Secs1OnTcpIpPassiveCommunicator, SecsMessage, L, A } from "secs4js";
442
+
443
+ async function TcpPassive() {
444
+ const passive = new Secs1OnTcpIpPassiveCommunicator({
445
+ ip: "0.0.0.0",
446
+ port: 5000,
447
+ deviceId: 10,
448
+ isEquip: true,
449
+ });
450
+
451
+ passive.on("message", (msg: SecsMessage) => {
452
+ void (async () => {
453
+ console.log(`Passive received: ${msg.toSml()}`);
454
+ // Process message and reply...
455
+ })();
456
+ });
457
+
458
+ await passive.open();
459
+ console.log("Passive server started");
460
+ }
461
+ ```
462
+
463
+ ## GEM
464
+
465
+ This library provides partial `GEM` (Generic Equipment Model) support. You can access commonly used GEM methods through the `Gem` object.
466
+
467
+ ```ts
468
+ // 1. Set up equipment side (Passive)
469
+ const equipComm = new HsmsPassiveCommunicator({
470
+ ip: "127.0.0.1",
471
+ port: 5000,
472
+ deviceId: 1,
473
+ isEquip: true,
474
+ name: "Equipment",
475
+ });
476
+
477
+ // Use the GEM helper class (optional)
478
+ const equipGem = new Gem(equipComm);
479
+ equipGem.mdln = "MyEquip";
480
+ equipGem.softrev = "1.0.0";
481
+
482
+ equipComm.on("message", (msg: SecsMessage) => {
483
+ void (async () => {
484
+ console.log(`Passive received: ${msg.toSml()}`);
485
+
486
+ // Reply to Host using messages defined in the Generic Equipment Model
487
+ if (msg.stream === 1 && msg.func === 1) {
488
+ await equipGem.s1f2(msg);
489
+ }
490
+ })();
491
+ });
492
+ ```
493
+
494
+ ## Logging
495
+
496
+ Logging is implemented using the `Pino` library.
497
+
498
+ There are two types of logs: `DETAIL` logs that record all detailed information, and SECS-II `SML` logs that only record bidirectional communication. The default level for DETAIL logs is `DEBUG`, and the default level for SECS-II logs is `INFO`.
499
+
500
+ You can configure logging properties by passing the `log` configuration parameter when initializing the communicator.
501
+
502
+ ```ts
503
+ const active = new HsmsActiveCommunicator({
504
+ ip: "127.0.0.1",
505
+ port: 5000,
506
+ deviceId: 10,
507
+ isEquip: false,
508
+ log: {
509
+ enabled: true, // Whether to enable logging
510
+ console: true, // Whether to output logs to console
511
+ baseDir: "./secs4js-logs", // Path for log storage
512
+ retentionDays: 30, // Number of days to retain logs
513
+ detailLevel: "trace", // Level for DETAIL logs
514
+ secs2Level: "info", // Level for SECS-II logs
515
+ maxHexBytes: 65536, // Maximum number of hex bytes to record
516
+ },
517
+ });
518
+ ```
519
+
520
+ ## Development
521
+
522
+ If you are interested in this project, welcome to contribute your code!
523
+
524
+ Thank you for your contribution! 💖
525
+
526
+ > 💝 This project was generated using [`create-typescript-app`](https://github.com/JoshuaKGoldberg/create-typescript-app) and the [Bingo framework](https://create.bingo).