secs4js 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/LICENSE.md +20 -0
- package/README.md +442 -0
- package/lib/core/AbstractSecsCommunicator.d.ts +76 -0
- package/lib/core/AbstractSecsCommunicator.d.ts.map +1 -0
- package/lib/core/AbstractSecsCommunicator.js +85 -0
- package/lib/core/AbstractSecsCommunicator.js.map +1 -0
- package/lib/core/AbstractSecsMessage.d.ts +36 -0
- package/lib/core/AbstractSecsMessage.d.ts.map +1 -0
- package/lib/core/AbstractSecsMessage.js +66 -0
- package/lib/core/AbstractSecsMessage.js.map +1 -0
- package/lib/core/enums/HsmsSsControlType.d.ts +59 -0
- package/lib/core/enums/HsmsSsControlType.d.ts.map +1 -0
- package/lib/core/enums/HsmsSsControlType.js +105 -0
- package/lib/core/enums/HsmsSsControlType.js.map +1 -0
- package/lib/core/enums/RejectReason.d.ts +11 -0
- package/lib/core/enums/RejectReason.d.ts.map +1 -0
- package/lib/core/enums/RejectReason.js +13 -0
- package/lib/core/enums/RejectReason.js.map +1 -0
- package/lib/core/enums/SecsItemType.d.ts +33 -0
- package/lib/core/enums/SecsItemType.d.ts.map +1 -0
- package/lib/core/enums/SecsItemType.js +22 -0
- package/lib/core/enums/SecsItemType.js.map +1 -0
- package/lib/core/enums/SelectStatus.d.ts +11 -0
- package/lib/core/enums/SelectStatus.d.ts.map +1 -0
- package/lib/core/enums/SelectStatus.js +13 -0
- package/lib/core/enums/SelectStatus.js.map +1 -0
- package/lib/core/secs2item/AbstractSecs2Item.d.ts +34 -0
- package/lib/core/secs2item/AbstractSecs2Item.d.ts.map +1 -0
- package/lib/core/secs2item/AbstractSecs2Item.js +59 -0
- package/lib/core/secs2item/AbstractSecs2Item.js.map +1 -0
- package/lib/core/secs2item/Secs2ItemAscii.d.ts +18 -0
- package/lib/core/secs2item/Secs2ItemAscii.d.ts.map +1 -0
- package/lib/core/secs2item/Secs2ItemAscii.js +27 -0
- package/lib/core/secs2item/Secs2ItemAscii.js.map +1 -0
- package/lib/core/secs2item/Secs2ItemBinary.d.ts +11 -0
- package/lib/core/secs2item/Secs2ItemBinary.d.ts.map +1 -0
- package/lib/core/secs2item/Secs2ItemBinary.js +22 -0
- package/lib/core/secs2item/Secs2ItemBinary.js.map +1 -0
- package/lib/core/secs2item/Secs2ItemBoolean.d.ts +12 -0
- package/lib/core/secs2item/Secs2ItemBoolean.d.ts.map +1 -0
- package/lib/core/secs2item/Secs2ItemBoolean.js +27 -0
- package/lib/core/secs2item/Secs2ItemBoolean.js.map +1 -0
- package/lib/core/secs2item/Secs2ItemFactory.d.ts +27 -0
- package/lib/core/secs2item/Secs2ItemFactory.d.ts.map +1 -0
- package/lib/core/secs2item/Secs2ItemFactory.js +56 -0
- package/lib/core/secs2item/Secs2ItemFactory.js.map +1 -0
- package/lib/core/secs2item/Secs2ItemList.d.ts +11 -0
- package/lib/core/secs2item/Secs2ItemList.d.ts.map +1 -0
- package/lib/core/secs2item/Secs2ItemList.js +26 -0
- package/lib/core/secs2item/Secs2ItemList.js.map +1 -0
- package/lib/core/secs2item/Secs2ItemNumeric.d.ts +14 -0
- package/lib/core/secs2item/Secs2ItemNumeric.d.ts.map +1 -0
- package/lib/core/secs2item/Secs2ItemNumeric.js +128 -0
- package/lib/core/secs2item/Secs2ItemNumeric.js.map +1 -0
- package/lib/core/secs2item/Secs2ItemParser.d.ts +12 -0
- package/lib/core/secs2item/Secs2ItemParser.d.ts.map +1 -0
- package/lib/core/secs2item/Secs2ItemParser.js +74 -0
- package/lib/core/secs2item/Secs2ItemParser.js.map +1 -0
- package/lib/gem/Clock.d.ts +21 -0
- package/lib/gem/Clock.d.ts.map +1 -0
- package/lib/gem/Clock.js +65 -0
- package/lib/gem/Clock.js.map +1 -0
- package/lib/gem/Gem.d.ts +55 -0
- package/lib/gem/Gem.d.ts.map +1 -0
- package/lib/gem/Gem.js +137 -0
- package/lib/gem/Gem.js.map +1 -0
- package/lib/helper/Secs2ItemHelper.d.ts +96 -0
- package/lib/helper/Secs2ItemHelper.d.ts.map +1 -0
- package/lib/helper/Secs2ItemHelper.js +135 -0
- package/lib/helper/Secs2ItemHelper.js.map +1 -0
- package/lib/hsms/HsmsActiveCommunicator.d.ts +29 -0
- package/lib/hsms/HsmsActiveCommunicator.d.ts.map +1 -0
- package/lib/hsms/HsmsActiveCommunicator.js +123 -0
- package/lib/hsms/HsmsActiveCommunicator.js.map +1 -0
- package/lib/hsms/HsmsCommunicator.d.ts +70 -0
- package/lib/hsms/HsmsCommunicator.d.ts.map +1 -0
- package/lib/hsms/HsmsCommunicator.js +322 -0
- package/lib/hsms/HsmsCommunicator.js.map +1 -0
- package/lib/hsms/HsmsMessage.d.ts +92 -0
- package/lib/hsms/HsmsMessage.d.ts.map +1 -0
- package/lib/hsms/HsmsMessage.js +163 -0
- package/lib/hsms/HsmsMessage.js.map +1 -0
- package/lib/hsms/HsmsPassiveCommunicator.d.ts +48 -0
- package/lib/hsms/HsmsPassiveCommunicator.d.ts.map +1 -0
- package/lib/hsms/HsmsPassiveCommunicator.js +304 -0
- package/lib/hsms/HsmsPassiveCommunicator.js.map +1 -0
- package/lib/hsms/enums/HsmsControlType.d.ts +15 -0
- package/lib/hsms/enums/HsmsControlType.d.ts.map +1 -0
- package/lib/hsms/enums/HsmsControlType.js +17 -0
- package/lib/hsms/enums/HsmsControlType.js.map +1 -0
- package/lib/hsms/enums/RejectReason.d.ts +11 -0
- package/lib/hsms/enums/RejectReason.d.ts.map +1 -0
- package/lib/hsms/enums/RejectReason.js +13 -0
- package/lib/hsms/enums/RejectReason.js.map +1 -0
- package/lib/hsms/enums/SelectStatus.d.ts +11 -0
- package/lib/hsms/enums/SelectStatus.d.ts.map +1 -0
- package/lib/hsms/enums/SelectStatus.js +13 -0
- package/lib/hsms/enums/SelectStatus.js.map +1 -0
- package/lib/index.d.ts +28 -0
- package/lib/index.js +29 -0
- package/lib/secs1/Secs1Communicator.d.ts +51 -0
- package/lib/secs1/Secs1Communicator.d.ts.map +1 -0
- package/lib/secs1/Secs1Communicator.js +342 -0
- package/lib/secs1/Secs1Communicator.js.map +1 -0
- package/lib/secs1/Secs1Message.d.ts +21 -0
- package/lib/secs1/Secs1Message.d.ts.map +1 -0
- package/lib/secs1/Secs1Message.js +77 -0
- package/lib/secs1/Secs1Message.js.map +1 -0
- package/lib/secs1/Secs1MessageBlock.d.ts +23 -0
- package/lib/secs1/Secs1MessageBlock.d.ts.map +1 -0
- package/lib/secs1/Secs1MessageBlock.js +86 -0
- package/lib/secs1/Secs1MessageBlock.js.map +1 -0
- package/lib/secs1/Secs1OnTcpIpActiveCommunicator.d.ts +23 -0
- package/lib/secs1/Secs1OnTcpIpActiveCommunicator.d.ts.map +1 -0
- package/lib/secs1/Secs1OnTcpIpActiveCommunicator.js +82 -0
- package/lib/secs1/Secs1OnTcpIpActiveCommunicator.js.map +1 -0
- package/lib/secs1/Secs1OnTcpIpPassiveCommunicator.d.ts +19 -0
- package/lib/secs1/Secs1OnTcpIpPassiveCommunicator.d.ts.map +1 -0
- package/lib/secs1/Secs1OnTcpIpPassiveCommunicator.js +49 -0
- package/lib/secs1/Secs1OnTcpIpPassiveCommunicator.js.map +1 -0
- package/lib/secs1/Secs1SerialCommunicator.d.ts +18 -0
- package/lib/secs1/Secs1SerialCommunicator.d.ts.map +1 -0
- package/lib/secs1/Secs1SerialCommunicator.js +59 -0
- package/lib/secs1/Secs1SerialCommunicator.js.map +1 -0
- package/lib/sml/SmlParser.d.ts +27 -0
- package/lib/sml/SmlParser.d.ts.map +1 -0
- package/lib/sml/SmlParser.js +184 -0
- package/lib/sml/SmlParser.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Secs1Communicator } from "./Secs1Communicator.js";
|
|
2
|
+
import { Socket } from "net";
|
|
3
|
+
|
|
4
|
+
//#region src/secs1/Secs1OnTcpIpActiveCommunicator.ts
|
|
5
|
+
var Secs1OnTcpIpActiveCommunicator = class extends Secs1Communicator {
|
|
6
|
+
ip;
|
|
7
|
+
port;
|
|
8
|
+
shouldStop = false;
|
|
9
|
+
reconnectTimer = null;
|
|
10
|
+
connectionPromiseResolver = null;
|
|
11
|
+
pendingSocket = null;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
super(config);
|
|
14
|
+
this.ip = config.ip;
|
|
15
|
+
this.port = config.port;
|
|
16
|
+
this.on("disconnected", () => {
|
|
17
|
+
if (!this.shouldStop) {
|
|
18
|
+
console.log(`Connection lost. Reconnecting in ${String(this.timeoutT5)}s...`);
|
|
19
|
+
this.scheduleReconnect();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async open() {
|
|
24
|
+
if (this.stream && !this.stream.destroyed) return;
|
|
25
|
+
if (this.pendingSocket && !this.pendingSocket.destroyed) return;
|
|
26
|
+
this.shouldStop = false;
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
this.connectionPromiseResolver = resolve;
|
|
29
|
+
this.connect();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
connect() {
|
|
33
|
+
if (this.shouldStop) return;
|
|
34
|
+
if (this.stream && !this.stream.destroyed) return;
|
|
35
|
+
if (this.pendingSocket && !this.pendingSocket.destroyed) return;
|
|
36
|
+
const socket = new Socket();
|
|
37
|
+
socket.setNoDelay(true);
|
|
38
|
+
this.pendingSocket = socket;
|
|
39
|
+
const onError = (err) => {
|
|
40
|
+
socket.destroy();
|
|
41
|
+
console.log(`Connection failed: ${err.message}. Retrying in ${String(this.timeoutT5)}s...`);
|
|
42
|
+
if (!this.shouldStop) this.scheduleReconnect();
|
|
43
|
+
};
|
|
44
|
+
socket.once("error", onError);
|
|
45
|
+
socket.once("close", () => {
|
|
46
|
+
if (this.pendingSocket === socket) this.pendingSocket = null;
|
|
47
|
+
});
|
|
48
|
+
socket.connect(this.port, this.ip, () => {
|
|
49
|
+
socket.removeListener("error", onError);
|
|
50
|
+
if (this.pendingSocket === socket) this.pendingSocket = null;
|
|
51
|
+
this.attachStream(socket);
|
|
52
|
+
if (this.connectionPromiseResolver) {
|
|
53
|
+
this.connectionPromiseResolver();
|
|
54
|
+
this.connectionPromiseResolver = null;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
scheduleReconnect() {
|
|
59
|
+
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
60
|
+
this.reconnectTimer = setTimeout(() => {
|
|
61
|
+
this.reconnectTimer = null;
|
|
62
|
+
this.connect();
|
|
63
|
+
}, this.timeoutT5 * 1e3);
|
|
64
|
+
}
|
|
65
|
+
async close() {
|
|
66
|
+
this.shouldStop = true;
|
|
67
|
+
if (this.reconnectTimer) {
|
|
68
|
+
clearTimeout(this.reconnectTimer);
|
|
69
|
+
this.reconnectTimer = null;
|
|
70
|
+
}
|
|
71
|
+
if (this.pendingSocket) {
|
|
72
|
+
this.pendingSocket.destroy();
|
|
73
|
+
this.pendingSocket = null;
|
|
74
|
+
}
|
|
75
|
+
this.stop();
|
|
76
|
+
await Promise.resolve();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
export { Secs1OnTcpIpActiveCommunicator };
|
|
82
|
+
//# sourceMappingURL=Secs1OnTcpIpActiveCommunicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Secs1OnTcpIpActiveCommunicator.js","names":[],"sources":["../../src/secs1/Secs1OnTcpIpActiveCommunicator.ts"],"sourcesContent":["import { Socket } from \"net\";\nimport {\n\tSecs1Communicator,\n\tSecs1CommunicatorConfig,\n} from \"./Secs1Communicator.js\";\n\nexport interface Secs1OnTcpIpActiveCommunicatorConfig extends Secs1CommunicatorConfig {\n\tip: string;\n\tport: number;\n}\n\nexport class Secs1OnTcpIpActiveCommunicator extends Secs1Communicator {\n\tpublic ip: string;\n\tpublic port: number;\n\n\tprivate shouldStop = false;\n\tprivate reconnectTimer: NodeJS.Timeout | null = null;\n\tprivate connectionPromiseResolver: (() => void) | null = null;\n\tprivate pendingSocket: Socket | null = null;\n\n\tconstructor(config: Secs1OnTcpIpActiveCommunicatorConfig) {\n\t\tsuper(config);\n\t\tthis.ip = config.ip;\n\t\tthis.port = config.port;\n\t\tthis.on(\"disconnected\", () => {\n\t\t\tif (!this.shouldStop) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`Connection lost. Reconnecting in ${String(this.timeoutT5)}s...`,\n\t\t\t\t);\n\t\t\t\tthis.scheduleReconnect();\n\t\t\t}\n\t\t});\n\t}\n\n\tasync open(): Promise<void> {\n\t\tif (this.stream && !this.stream.destroyed) return;\n\t\tif (this.pendingSocket && !this.pendingSocket.destroyed) return;\n\n\t\tthis.shouldStop = false;\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.connectionPromiseResolver = resolve;\n\t\t\tthis.connect();\n\t\t});\n\t}\n\n\tprivate connect() {\n\t\tif (this.shouldStop) return;\n\t\tif (this.stream && !this.stream.destroyed) return;\n\t\tif (this.pendingSocket && !this.pendingSocket.destroyed) return;\n\n\t\tconst socket = new Socket();\n\t\tsocket.setNoDelay(true);\n\t\tthis.pendingSocket = socket;\n\n\t\tconst onError = (err: Error) => {\n\t\t\tsocket.destroy();\n\t\t\tconsole.log(\n\t\t\t\t`Connection failed: ${err.message}. Retrying in ${String(this.timeoutT5)}s...`,\n\t\t\t);\n\t\t\tif (!this.shouldStop) {\n\t\t\t\tthis.scheduleReconnect();\n\t\t\t}\n\t\t};\n\n\t\tsocket.once(\"error\", onError);\n\n\t\tsocket.once(\"close\", () => {\n\t\t\tif (this.pendingSocket === socket) {\n\t\t\t\tthis.pendingSocket = null;\n\t\t\t}\n\t\t});\n\n\t\tsocket.connect(this.port, this.ip, () => {\n\t\t\tsocket.removeListener(\"error\", onError);\n\t\t\tif (this.pendingSocket === socket) {\n\t\t\t\tthis.pendingSocket = null;\n\t\t\t}\n\t\t\tthis.attachStream(socket);\n\n\t\t\tif (this.connectionPromiseResolver) {\n\t\t\t\tthis.connectionPromiseResolver();\n\t\t\t\tthis.connectionPromiseResolver = null;\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate scheduleReconnect() {\n\t\tif (this.reconnectTimer) {\n\t\t\tclearTimeout(this.reconnectTimer);\n\t\t}\n\t\tthis.reconnectTimer = setTimeout(() => {\n\t\t\tthis.reconnectTimer = null;\n\t\t\tthis.connect();\n\t\t}, this.timeoutT5 * 1000);\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.shouldStop = true;\n\t\tif (this.reconnectTimer) {\n\t\t\tclearTimeout(this.reconnectTimer);\n\t\t\tthis.reconnectTimer = null;\n\t\t}\n\t\tif (this.pendingSocket) {\n\t\t\tthis.pendingSocket.destroy();\n\t\t\tthis.pendingSocket = null;\n\t\t}\n\t\tthis.stop();\n\t\tawait Promise.resolve();\n\t}\n}\n"],"mappings":";;;;AAWA,IAAa,iCAAb,cAAoD,kBAAkB;CACrE,AAAO;CACP,AAAO;CAEP,AAAQ,aAAa;CACrB,AAAQ,iBAAwC;CAChD,AAAQ,4BAAiD;CACzD,AAAQ,gBAA+B;CAEvC,YAAY,QAA8C;AACzD,QAAM,OAAO;AACb,OAAK,KAAK,OAAO;AACjB,OAAK,OAAO,OAAO;AACnB,OAAK,GAAG,sBAAsB;AAC7B,OAAI,CAAC,KAAK,YAAY;AACrB,YAAQ,IACP,oCAAoC,OAAO,KAAK,UAAU,CAAC,MAC3D;AACD,SAAK,mBAAmB;;IAExB;;CAGH,MAAM,OAAsB;AAC3B,MAAI,KAAK,UAAU,CAAC,KAAK,OAAO,UAAW;AAC3C,MAAI,KAAK,iBAAiB,CAAC,KAAK,cAAc,UAAW;AAEzD,OAAK,aAAa;AAElB,SAAO,IAAI,SAAS,YAAY;AAC/B,QAAK,4BAA4B;AACjC,QAAK,SAAS;IACb;;CAGH,AAAQ,UAAU;AACjB,MAAI,KAAK,WAAY;AACrB,MAAI,KAAK,UAAU,CAAC,KAAK,OAAO,UAAW;AAC3C,MAAI,KAAK,iBAAiB,CAAC,KAAK,cAAc,UAAW;EAEzD,MAAM,SAAS,IAAI,QAAQ;AAC3B,SAAO,WAAW,KAAK;AACvB,OAAK,gBAAgB;EAErB,MAAM,WAAW,QAAe;AAC/B,UAAO,SAAS;AAChB,WAAQ,IACP,sBAAsB,IAAI,QAAQ,gBAAgB,OAAO,KAAK,UAAU,CAAC,MACzE;AACD,OAAI,CAAC,KAAK,WACT,MAAK,mBAAmB;;AAI1B,SAAO,KAAK,SAAS,QAAQ;AAE7B,SAAO,KAAK,eAAe;AAC1B,OAAI,KAAK,kBAAkB,OAC1B,MAAK,gBAAgB;IAErB;AAEF,SAAO,QAAQ,KAAK,MAAM,KAAK,UAAU;AACxC,UAAO,eAAe,SAAS,QAAQ;AACvC,OAAI,KAAK,kBAAkB,OAC1B,MAAK,gBAAgB;AAEtB,QAAK,aAAa,OAAO;AAEzB,OAAI,KAAK,2BAA2B;AACnC,SAAK,2BAA2B;AAChC,SAAK,4BAA4B;;IAEjC;;CAGH,AAAQ,oBAAoB;AAC3B,MAAI,KAAK,eACR,cAAa,KAAK,eAAe;AAElC,OAAK,iBAAiB,iBAAiB;AACtC,QAAK,iBAAiB;AACtB,QAAK,SAAS;KACZ,KAAK,YAAY,IAAK;;CAG1B,MAAM,QAAuB;AAC5B,OAAK,aAAa;AAClB,MAAI,KAAK,gBAAgB;AACxB,gBAAa,KAAK,eAAe;AACjC,QAAK,iBAAiB;;AAEvB,MAAI,KAAK,eAAe;AACvB,QAAK,cAAc,SAAS;AAC5B,QAAK,gBAAgB;;AAEtB,OAAK,MAAM;AACX,QAAM,QAAQ,SAAS"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Secs1Communicator, Secs1CommunicatorConfig } from "./Secs1Communicator.js";
|
|
2
|
+
|
|
3
|
+
//#region src/secs1/Secs1OnTcpIpPassiveCommunicator.d.ts
|
|
4
|
+
interface Secs1OnTcpIpPassiveCommunicatorConfig extends Secs1CommunicatorConfig {
|
|
5
|
+
ip: string;
|
|
6
|
+
port: number;
|
|
7
|
+
}
|
|
8
|
+
declare class Secs1OnTcpIpPassiveCommunicator extends Secs1Communicator {
|
|
9
|
+
ip: string;
|
|
10
|
+
port: number;
|
|
11
|
+
private server;
|
|
12
|
+
constructor(config: Secs1OnTcpIpPassiveCommunicatorConfig);
|
|
13
|
+
open(): Promise<void>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
private handleIncomingSocket;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { Secs1OnTcpIpPassiveCommunicator, Secs1OnTcpIpPassiveCommunicatorConfig };
|
|
19
|
+
//# sourceMappingURL=Secs1OnTcpIpPassiveCommunicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Secs1OnTcpIpPassiveCommunicator.d.ts","names":[],"sources":["../../src/secs1/Secs1OnTcpIpPassiveCommunicator.ts"],"sourcesContent":[],"mappings":";;;UAMiB,qCAAA,SAA8C;;EAA9C,IAAA,EAAA,MAAA;AAKjB;AAMqB,cANR,+BAAA,SAAwC,iBAAA,CAMhC;EAMN,EAAA,EAAA,MAAA;EA0BL,IAAA,EAAA,MAAA;EAtC2C,QAAA,MAAA;EAAiB,WAAA,CAAA,MAAA,EAMjD,qCANiD;UAYvD;WA0BL"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Secs1Communicator } from "./Secs1Communicator.js";
|
|
2
|
+
import { createServer } from "net";
|
|
3
|
+
|
|
4
|
+
//#region src/secs1/Secs1OnTcpIpPassiveCommunicator.ts
|
|
5
|
+
var Secs1OnTcpIpPassiveCommunicator = class extends Secs1Communicator {
|
|
6
|
+
ip;
|
|
7
|
+
port;
|
|
8
|
+
server = null;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
super(config);
|
|
11
|
+
this.ip = config.ip;
|
|
12
|
+
this.port = config.port;
|
|
13
|
+
}
|
|
14
|
+
async open() {
|
|
15
|
+
if (this.server) return;
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
this.server = createServer((socket) => {
|
|
18
|
+
if (this.stream && !this.stream.destroyed) {
|
|
19
|
+
console.warn("Rejecting new connection (SECS-I Single Session)");
|
|
20
|
+
socket.destroy();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
console.log(`Accepted connection from ${socket.remoteAddress ?? "unknown"}:${socket.remotePort ?? "unknown"}`);
|
|
24
|
+
this.handleIncomingSocket(socket);
|
|
25
|
+
});
|
|
26
|
+
this.server.on("error", (err) => {
|
|
27
|
+
reject(err);
|
|
28
|
+
});
|
|
29
|
+
this.server.listen(this.port, this.ip, () => {
|
|
30
|
+
resolve();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
close() {
|
|
35
|
+
this.stop();
|
|
36
|
+
if (this.server) {
|
|
37
|
+
this.server.close();
|
|
38
|
+
this.server = null;
|
|
39
|
+
}
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
}
|
|
42
|
+
handleIncomingSocket(socket) {
|
|
43
|
+
this.attachStream(socket);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { Secs1OnTcpIpPassiveCommunicator };
|
|
49
|
+
//# sourceMappingURL=Secs1OnTcpIpPassiveCommunicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Secs1OnTcpIpPassiveCommunicator.js","names":[],"sources":["../../src/secs1/Secs1OnTcpIpPassiveCommunicator.ts"],"sourcesContent":["import { Server, Socket, createServer } from \"net\";\nimport {\n\tSecs1Communicator,\n\tSecs1CommunicatorConfig,\n} from \"./Secs1Communicator.js\";\n\nexport interface Secs1OnTcpIpPassiveCommunicatorConfig extends Secs1CommunicatorConfig {\n\tip: string;\n\tport: number;\n}\n\nexport class Secs1OnTcpIpPassiveCommunicator extends Secs1Communicator {\n\tpublic ip: string;\n\tpublic port: number;\n\n\tprivate server: Server | null = null;\n\n\tconstructor(config: Secs1OnTcpIpPassiveCommunicatorConfig) {\n\t\tsuper(config);\n\t\tthis.ip = config.ip;\n\t\tthis.port = config.port;\n\t}\n\n\tasync open(): Promise<void> {\n\t\tif (this.server) return;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.server = createServer((socket) => {\n\t\t\t\tif (this.stream && !this.stream.destroyed) {\n\t\t\t\t\tconsole.warn(\"Rejecting new connection (SECS-I Single Session)\");\n\t\t\t\t\tsocket.destroy();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconsole.log(\n\t\t\t\t\t`Accepted connection from ${socket.remoteAddress ?? \"unknown\"}:${socket.remotePort ?? \"unknown\"}`,\n\t\t\t\t);\n\t\t\t\tthis.handleIncomingSocket(socket);\n\t\t\t});\n\n\t\t\tthis.server.on(\"error\", (err) => {\n\t\t\t\treject(err);\n\t\t\t});\n\n\t\t\tthis.server.listen(this.port, this.ip, () => {\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\n\tclose(): Promise<void> {\n\t\tthis.stop();\n\t\tif (this.server) {\n\t\t\tthis.server.close();\n\t\t\tthis.server = null;\n\t\t}\n\t\treturn Promise.resolve();\n\t}\n\n\tprivate handleIncomingSocket(socket: Socket) {\n\t\tthis.attachStream(socket);\n\t}\n}\n"],"mappings":";;;;AAWA,IAAa,kCAAb,cAAqD,kBAAkB;CACtE,AAAO;CACP,AAAO;CAEP,AAAQ,SAAwB;CAEhC,YAAY,QAA+C;AAC1D,QAAM,OAAO;AACb,OAAK,KAAK,OAAO;AACjB,OAAK,OAAO,OAAO;;CAGpB,MAAM,OAAsB;AAC3B,MAAI,KAAK,OAAQ;AAEjB,SAAO,IAAI,SAAS,SAAS,WAAW;AACvC,QAAK,SAAS,cAAc,WAAW;AACtC,QAAI,KAAK,UAAU,CAAC,KAAK,OAAO,WAAW;AAC1C,aAAQ,KAAK,mDAAmD;AAChE,YAAO,SAAS;AAChB;;AAED,YAAQ,IACP,4BAA4B,OAAO,iBAAiB,UAAU,GAAG,OAAO,cAAc,YACtF;AACD,SAAK,qBAAqB,OAAO;KAChC;AAEF,QAAK,OAAO,GAAG,UAAU,QAAQ;AAChC,WAAO,IAAI;KACV;AAEF,QAAK,OAAO,OAAO,KAAK,MAAM,KAAK,UAAU;AAC5C,aAAS;KACR;IACD;;CAGH,QAAuB;AACtB,OAAK,MAAM;AACX,MAAI,KAAK,QAAQ;AAChB,QAAK,OAAO,OAAO;AACnB,QAAK,SAAS;;AAEf,SAAO,QAAQ,SAAS;;CAGzB,AAAQ,qBAAqB,QAAgB;AAC5C,OAAK,aAAa,OAAO"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Secs1Communicator, Secs1CommunicatorConfig } from "./Secs1Communicator.js";
|
|
2
|
+
|
|
3
|
+
//#region src/secs1/Secs1SerialCommunicator.d.ts
|
|
4
|
+
interface Secs1SerialCommunicatorConfig extends Secs1CommunicatorConfig {
|
|
5
|
+
path: string;
|
|
6
|
+
baudRate: number;
|
|
7
|
+
}
|
|
8
|
+
declare class Secs1SerialCommunicator extends Secs1Communicator {
|
|
9
|
+
private port;
|
|
10
|
+
path: string;
|
|
11
|
+
baudRate: number;
|
|
12
|
+
constructor(config: Secs1SerialCommunicatorConfig);
|
|
13
|
+
open(): Promise<void>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { Secs1SerialCommunicator, Secs1SerialCommunicatorConfig };
|
|
18
|
+
//# sourceMappingURL=Secs1SerialCommunicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Secs1SerialCommunicator.d.ts","names":[],"sources":["../../src/secs1/Secs1SerialCommunicator.ts"],"sourcesContent":[],"mappings":";;;UAMiB,6BAAA,SAAsC;;EAAtC,QAAA,EAAA,MAAA;AAKjB;AAMqB,cANR,uBAAA,SAAgC,iBAAA,CAMxB;EAMN,QAAA,IAAA;EA4BC,IAAA,EAAA,MAAA;EAxC6B,QAAA,EAAA,MAAA;EAAiB,WAAA,CAAA,MAAA,EAMzC,6BANyC;UAY/C;WA4BC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Secs1Communicator } from "./Secs1Communicator.js";
|
|
2
|
+
import { SerialPort } from "serialport";
|
|
3
|
+
|
|
4
|
+
//#region src/secs1/Secs1SerialCommunicator.ts
|
|
5
|
+
var Secs1SerialCommunicator = class extends Secs1Communicator {
|
|
6
|
+
port = null;
|
|
7
|
+
path;
|
|
8
|
+
baudRate;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
super(config);
|
|
11
|
+
this.path = config.path;
|
|
12
|
+
this.baudRate = config.baudRate;
|
|
13
|
+
}
|
|
14
|
+
async open() {
|
|
15
|
+
if (this.port?.isOpen) return;
|
|
16
|
+
const port = new SerialPort({
|
|
17
|
+
path: this.path,
|
|
18
|
+
baudRate: this.baudRate,
|
|
19
|
+
autoOpen: false
|
|
20
|
+
});
|
|
21
|
+
this.port = port;
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const onError = (err) => {
|
|
24
|
+
reject(err);
|
|
25
|
+
};
|
|
26
|
+
port.once("error", onError);
|
|
27
|
+
port.open((err) => {
|
|
28
|
+
port.removeListener("error", onError);
|
|
29
|
+
if (err) {
|
|
30
|
+
reject(err);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.attachStream(port);
|
|
34
|
+
resolve();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async close() {
|
|
39
|
+
this.stop();
|
|
40
|
+
const port = this.port;
|
|
41
|
+
this.port = null;
|
|
42
|
+
if (!port) return;
|
|
43
|
+
if (!port.isOpen) {
|
|
44
|
+
port.destroy();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await new Promise((resolve, reject) => {
|
|
48
|
+
port.close((err) => {
|
|
49
|
+
if (err) reject(err);
|
|
50
|
+
else resolve();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
port.destroy();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { Secs1SerialCommunicator };
|
|
59
|
+
//# sourceMappingURL=Secs1SerialCommunicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Secs1SerialCommunicator.js","names":[],"sources":["../../src/secs1/Secs1SerialCommunicator.ts"],"sourcesContent":["import { SerialPort } from \"serialport\";\nimport {\n\tSecs1Communicator,\n\tSecs1CommunicatorConfig,\n} from \"./Secs1Communicator.js\";\n\nexport interface Secs1SerialCommunicatorConfig extends Secs1CommunicatorConfig {\n\tpath: string;\n\tbaudRate: number;\n}\n\nexport class Secs1SerialCommunicator extends Secs1Communicator {\n\tprivate port: SerialPort | null = null;\n\n\tpublic path: string;\n\tpublic baudRate: number;\n\n\tconstructor(config: Secs1SerialCommunicatorConfig) {\n\t\tsuper(config);\n\t\tthis.path = config.path;\n\t\tthis.baudRate = config.baudRate;\n\t}\n\n\tasync open(): Promise<void> {\n\t\tif (this.port?.isOpen) return;\n\n\t\tconst port = new SerialPort({\n\t\t\tpath: this.path,\n\t\t\tbaudRate: this.baudRate,\n\t\t\tautoOpen: false,\n\t\t});\n\t\tthis.port = port;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst onError = (err: Error) => {\n\t\t\t\treject(err);\n\t\t\t};\n\n\t\t\tport.once(\"error\", onError);\n\t\t\tport.open((err) => {\n\t\t\t\tport.removeListener(\"error\", onError);\n\t\t\t\tif (err) {\n\t\t\t\t\treject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.attachStream(port);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.stop();\n\n\t\tconst port = this.port;\n\t\tthis.port = null;\n\t\tif (!port) return;\n\n\t\tif (!port.isOpen) {\n\t\t\tport.destroy();\n\t\t\treturn;\n\t\t}\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tport.close((err) => {\n\t\t\t\tif (err) reject(err);\n\t\t\t\telse resolve();\n\t\t\t});\n\t\t});\n\n\t\tport.destroy();\n\t}\n}\n"],"mappings":";;;;AAWA,IAAa,0BAAb,cAA6C,kBAAkB;CAC9D,AAAQ,OAA0B;CAElC,AAAO;CACP,AAAO;CAEP,YAAY,QAAuC;AAClD,QAAM,OAAO;AACb,OAAK,OAAO,OAAO;AACnB,OAAK,WAAW,OAAO;;CAGxB,MAAM,OAAsB;AAC3B,MAAI,KAAK,MAAM,OAAQ;EAEvB,MAAM,OAAO,IAAI,WAAW;GAC3B,MAAM,KAAK;GACX,UAAU,KAAK;GACf,UAAU;GACV,CAAC;AACF,OAAK,OAAO;AAEZ,SAAO,IAAI,SAAS,SAAS,WAAW;GACvC,MAAM,WAAW,QAAe;AAC/B,WAAO,IAAI;;AAGZ,QAAK,KAAK,SAAS,QAAQ;AAC3B,QAAK,MAAM,QAAQ;AAClB,SAAK,eAAe,SAAS,QAAQ;AACrC,QAAI,KAAK;AACR,YAAO,IAAI;AACX;;AAED,SAAK,aAAa,KAAK;AACvB,aAAS;KACR;IACD;;CAGH,MAAM,QAAuB;AAC5B,OAAK,MAAM;EAEX,MAAM,OAAO,KAAK;AAClB,OAAK,OAAO;AACZ,MAAI,CAAC,KAAM;AAEX,MAAI,CAAC,KAAK,QAAQ;AACjB,QAAK,SAAS;AACd;;AAGD,QAAM,IAAI,SAAe,SAAS,WAAW;AAC5C,QAAK,OAAO,QAAQ;AACnB,QAAI,IAAK,QAAO,IAAI;QACf,UAAS;KACb;IACD;AAEF,OAAK,SAAS"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AbstractSecs2Item } from "../core/secs2item/AbstractSecs2Item.js";
|
|
2
|
+
import { SecsMessage } from "../core/AbstractSecsMessage.js";
|
|
3
|
+
|
|
4
|
+
//#region src/sml/SmlParser.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @description A parser for SECS-II SML strings.
|
|
8
|
+
*/
|
|
9
|
+
declare class SmlParser {
|
|
10
|
+
private static readonly SML_REGEX;
|
|
11
|
+
/**
|
|
12
|
+
* @description Parses an SML string into a SecsMessage.
|
|
13
|
+
* @param sml The SML string to parse.
|
|
14
|
+
* @returns The parsed SecsMessage.
|
|
15
|
+
*/
|
|
16
|
+
static parse(sml: string): SecsMessage;
|
|
17
|
+
/**
|
|
18
|
+
* @description Parses an SML body string into a Secs2Item.
|
|
19
|
+
* @param smlBody The SML body string to parse.
|
|
20
|
+
* @returns The parsed Secs2Item.
|
|
21
|
+
*/
|
|
22
|
+
static parseBody(smlBody: string): AbstractSecs2Item | null;
|
|
23
|
+
private static parseItem;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { SmlParser };
|
|
27
|
+
//# sourceMappingURL=SmlParser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SmlParser.d.ts","names":[],"sources":["../../src/sml/SmlParser.ts"],"sourcesContent":[],"mappings":";;;;;;;AAkEA;cAAa,SAAA;;;;;;;6BAQe;;;;;;qCAuBQ"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { SecsMessage } from "../core/AbstractSecsMessage.js";
|
|
2
|
+
import { SecsItemType } from "../core/enums/SecsItemType.js";
|
|
3
|
+
import { Secs2ItemNumeric } from "../core/secs2item/Secs2ItemNumeric.js";
|
|
4
|
+
import { Secs2ItemAscii } from "../core/secs2item/Secs2ItemAscii.js";
|
|
5
|
+
import { Secs2ItemBinary } from "../core/secs2item/Secs2ItemBinary.js";
|
|
6
|
+
import { Secs2ItemBoolean } from "../core/secs2item/Secs2ItemBoolean.js";
|
|
7
|
+
import { Secs2ItemList } from "../core/secs2item/Secs2ItemList.js";
|
|
8
|
+
|
|
9
|
+
//#region src/sml/SmlParser.ts
|
|
10
|
+
/**
|
|
11
|
+
* @description A cursor for SML parsing.
|
|
12
|
+
*/
|
|
13
|
+
var SmlCursor = class {
|
|
14
|
+
constructor(str, pos = 0) {
|
|
15
|
+
this.str = str;
|
|
16
|
+
this.pos = pos;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @description Returns the next character in the string without consuming it.
|
|
20
|
+
* @returns The next character in the string.
|
|
21
|
+
*/
|
|
22
|
+
peek() {
|
|
23
|
+
return this.str[this.pos];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @description Consumes the next character in the string and returns it.
|
|
27
|
+
* @returns The consumed character.
|
|
28
|
+
*/
|
|
29
|
+
consume() {
|
|
30
|
+
return this.str[this.pos++];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* @description Returns true if the cursor is at the end of the string.
|
|
34
|
+
* @returns True if the cursor is at the end of the string.
|
|
35
|
+
*/
|
|
36
|
+
eof() {
|
|
37
|
+
return this.pos >= this.str.length;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* @description Skips whitespace characters in the string.
|
|
41
|
+
*/
|
|
42
|
+
skipWs() {
|
|
43
|
+
while (!this.eof() && /\s/.test(this.peek())) this.pos++;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* @description Matches the next character in the string with the given character.
|
|
47
|
+
* @param char The character to match.
|
|
48
|
+
* @returns True if the next character in the string is the given character.
|
|
49
|
+
*/
|
|
50
|
+
match(char) {
|
|
51
|
+
if (this.peek() === char) {
|
|
52
|
+
this.consume();
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* @description A parser for SECS-II SML strings.
|
|
60
|
+
*/
|
|
61
|
+
var SmlParser = class {
|
|
62
|
+
static SML_REGEX = /^[Ss](\d+)[Ff](\d+)\s*(W?)\s*(.*)\.$/s;
|
|
63
|
+
/**
|
|
64
|
+
* @description Parses an SML string into a SecsMessage.
|
|
65
|
+
* @param sml The SML string to parse.
|
|
66
|
+
* @returns The parsed SecsMessage.
|
|
67
|
+
*/
|
|
68
|
+
static parse(sml) {
|
|
69
|
+
const trimmed = sml.trim();
|
|
70
|
+
const match = this.SML_REGEX.exec(trimmed);
|
|
71
|
+
if (!match) throw new Error("Invalid SML format. Must match 'SxFy [W] <Body>.'");
|
|
72
|
+
const stream = parseInt(match[1], 10);
|
|
73
|
+
const func = parseInt(match[2], 10);
|
|
74
|
+
const wBit = match[3].toUpperCase() === "W";
|
|
75
|
+
const bodyStr = match[4].trim();
|
|
76
|
+
return new SecsMessage(stream, func, wBit, this.parseBody(bodyStr));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* @description Parses an SML body string into a Secs2Item.
|
|
80
|
+
* @param smlBody The SML body string to parse.
|
|
81
|
+
* @returns The parsed Secs2Item.
|
|
82
|
+
*/
|
|
83
|
+
static parseBody(smlBody) {
|
|
84
|
+
const trimmed = smlBody.trim();
|
|
85
|
+
if (trimmed.length === 0) return null;
|
|
86
|
+
const cursor = new SmlCursor(trimmed);
|
|
87
|
+
return this.parseItem(cursor);
|
|
88
|
+
}
|
|
89
|
+
static parseItem(cursor) {
|
|
90
|
+
cursor.skipWs();
|
|
91
|
+
if (cursor.consume() !== "<") throw new Error(`Expected '<' at pos ${(cursor.pos - 1).toString()}`);
|
|
92
|
+
cursor.skipWs();
|
|
93
|
+
const typeStart = cursor.pos;
|
|
94
|
+
while (!cursor.eof() && /[A-Z0-9]/.test(cursor.peek().toUpperCase())) cursor.consume();
|
|
95
|
+
const typeStr = cursor.str.substring(typeStart, cursor.pos).toUpperCase();
|
|
96
|
+
cursor.skipWs();
|
|
97
|
+
if (cursor.peek() === "[") {
|
|
98
|
+
while (!cursor.eof() && cursor.peek() !== "]") cursor.consume();
|
|
99
|
+
if (cursor.peek() === "]") cursor.consume();
|
|
100
|
+
}
|
|
101
|
+
let item;
|
|
102
|
+
if (typeStr === "L") {
|
|
103
|
+
const items = [];
|
|
104
|
+
while (true) {
|
|
105
|
+
cursor.skipWs();
|
|
106
|
+
if (cursor.peek() === ">") {
|
|
107
|
+
cursor.consume();
|
|
108
|
+
break;
|
|
109
|
+
} else if (cursor.peek() === "<") items.push(this.parseItem(cursor));
|
|
110
|
+
else throw new Error(`Unexpected char in List: '${cursor.peek()}' at ${cursor.pos.toString()}`);
|
|
111
|
+
}
|
|
112
|
+
item = new Secs2ItemList(items);
|
|
113
|
+
} else if (typeStr === "A") {
|
|
114
|
+
cursor.skipWs();
|
|
115
|
+
if (cursor.peek() === "\"") {
|
|
116
|
+
cursor.consume();
|
|
117
|
+
const start = cursor.pos;
|
|
118
|
+
while (!cursor.eof() && cursor.peek() !== "\"") cursor.consume();
|
|
119
|
+
const val = cursor.str.substring(start, cursor.pos);
|
|
120
|
+
cursor.consume();
|
|
121
|
+
item = new Secs2ItemAscii(val);
|
|
122
|
+
} else if (cursor.peek() === ">") item = new Secs2ItemAscii("");
|
|
123
|
+
else throw new Error(`Expected string value for type A at ${cursor.pos.toString()}`);
|
|
124
|
+
cursor.skipWs();
|
|
125
|
+
if (cursor.consume() !== ">") throw new Error("Expected '>' ending A");
|
|
126
|
+
} else if (typeStr === "B") {
|
|
127
|
+
const buffer = [];
|
|
128
|
+
while (true) {
|
|
129
|
+
cursor.skipWs();
|
|
130
|
+
if (cursor.peek() === ">") {
|
|
131
|
+
cursor.consume();
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
if (cursor.str.startsWith("0x", cursor.pos) || cursor.str.startsWith("0X", cursor.pos)) cursor.pos += 2;
|
|
135
|
+
const byteStr = cursor.str.substring(cursor.pos, cursor.pos + 2);
|
|
136
|
+
const byteVal = parseInt(byteStr, 16);
|
|
137
|
+
if (isNaN(byteVal)) throw new Error(`Invalid binary byte at ${cursor.pos.toString()}`);
|
|
138
|
+
buffer.push(byteVal);
|
|
139
|
+
cursor.pos += 2;
|
|
140
|
+
}
|
|
141
|
+
item = new Secs2ItemBinary(Buffer.from(buffer));
|
|
142
|
+
} else if (typeStr === "BOOLEAN") {
|
|
143
|
+
const bools = [];
|
|
144
|
+
while (true) {
|
|
145
|
+
cursor.skipWs();
|
|
146
|
+
if (cursor.peek() === ">") {
|
|
147
|
+
cursor.consume();
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
const start = cursor.pos;
|
|
151
|
+
while (!cursor.eof() && /[A-Z]/.test(cursor.peek().toUpperCase())) cursor.consume();
|
|
152
|
+
const val = cursor.str.substring(start, cursor.pos).toUpperCase();
|
|
153
|
+
if (["T", "TRUE"].includes(val)) bools.push(true);
|
|
154
|
+
else if (["F", "FALSE"].includes(val)) bools.push(false);
|
|
155
|
+
else throw new Error(`Invalid Boolean value ${val}`);
|
|
156
|
+
}
|
|
157
|
+
item = new Secs2ItemBoolean(bools.length === 1 ? bools[0] : bools);
|
|
158
|
+
} else {
|
|
159
|
+
const nums = [];
|
|
160
|
+
const isFloat = ["F4", "F8"].includes(typeStr);
|
|
161
|
+
while (true) {
|
|
162
|
+
cursor.skipWs();
|
|
163
|
+
if (cursor.peek() === ">") {
|
|
164
|
+
cursor.consume();
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
const start = cursor.pos;
|
|
168
|
+
while (!cursor.eof() && /[0-9.\-+]/.test(cursor.peek())) cursor.consume();
|
|
169
|
+
const valStr = cursor.str.substring(start, cursor.pos);
|
|
170
|
+
const val = isFloat ? parseFloat(valStr) : Number(valStr);
|
|
171
|
+
if (isNaN(val)) throw new Error(`Invalid number ${valStr} at ${start.toString()}`);
|
|
172
|
+
nums.push(val);
|
|
173
|
+
}
|
|
174
|
+
const type = SecsItemType[typeStr];
|
|
175
|
+
if (type === void 0) throw new Error(`Unknown type ${typeStr}`);
|
|
176
|
+
item = new Secs2ItemNumeric(type, nums.length === 1 ? nums[0] : nums);
|
|
177
|
+
}
|
|
178
|
+
return item;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
//#endregion
|
|
183
|
+
export { SmlParser };
|
|
184
|
+
//# sourceMappingURL=SmlParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SmlParser.js","names":["str: string","item: AbstractSecs2Item","items: AbstractSecs2Item[]","buffer: number[]","bools: boolean[]","nums: number[]"],"sources":["../../src/sml/SmlParser.ts"],"sourcesContent":["import { SecsMessage } from \"../core/AbstractSecsMessage.js\";\nimport { AbstractSecs2Item } from \"../core/secs2item/AbstractSecs2Item.js\";\nimport { SecsItemType } from \"../core/enums/SecsItemType.js\";\nimport { Secs2ItemAscii } from \"../core/secs2item/Secs2ItemAscii.js\";\nimport { Secs2ItemList } from \"../core/secs2item/Secs2ItemList.js\";\nimport { Secs2ItemBinary } from \"../core/secs2item/Secs2ItemBinary.js\";\nimport { Secs2ItemBoolean } from \"../core/secs2item/Secs2ItemBoolean.js\";\nimport { Secs2ItemNumeric } from \"../core/secs2item/Secs2ItemNumeric.js\";\n\n/**\n * @description A cursor for SML parsing.\n */\nclass SmlCursor {\n\tconstructor(\n\t\tpublic str: string,\n\t\tpublic pos = 0,\n\t) {}\n\n\t/**\n\t * @description Returns the next character in the string without consuming it.\n\t * @returns The next character in the string.\n\t */\n\tpeek(): string {\n\t\treturn this.str[this.pos];\n\t}\n\n\t/**\n\t * @description Consumes the next character in the string and returns it.\n\t * @returns The consumed character.\n\t */\n\tconsume(): string {\n\t\treturn this.str[this.pos++];\n\t}\n\n\t/**\n\t * @description Returns true if the cursor is at the end of the string.\n\t * @returns True if the cursor is at the end of the string.\n\t */\n\teof(): boolean {\n\t\treturn this.pos >= this.str.length;\n\t}\n\n\t/**\n\t * @description Skips whitespace characters in the string.\n\t */\n\tskipWs() {\n\t\twhile (!this.eof() && /\\s/.test(this.peek())) this.pos++;\n\t}\n\n\t/**\n\t * @description Matches the next character in the string with the given character.\n\t * @param char The character to match.\n\t * @returns True if the next character in the string is the given character.\n\t */\n\tmatch(char: string): boolean {\n\t\tif (this.peek() === char) {\n\t\t\tthis.consume();\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n}\n\n/**\n * @description A parser for SECS-II SML strings.\n */\nexport class SmlParser {\n\tprivate static readonly SML_REGEX = /^[Ss](\\d+)[Ff](\\d+)\\s*(W?)\\s*(.*)\\.$/s;\n\n\t/**\n\t * @description Parses an SML string into a SecsMessage.\n\t * @param sml The SML string to parse.\n\t * @returns The parsed SecsMessage.\n\t */\n\tstatic parse(sml: string): SecsMessage {\n\t\tconst trimmed = sml.trim();\n\t\tconst match = this.SML_REGEX.exec(trimmed);\n\n\t\tif (!match) {\n\t\t\tthrow new Error(\"Invalid SML format. Must match 'SxFy [W] <Body>.'\");\n\t\t}\n\n\t\tconst stream = parseInt(match[1], 10);\n\t\tconst func = parseInt(match[2], 10);\n\t\tconst wBit = match[3].toUpperCase() === \"W\";\n\t\tconst bodyStr = match[4].trim();\n\n\t\tconst body = this.parseBody(bodyStr);\n\n\t\treturn new SecsMessage(stream, func, wBit, body);\n\t}\n\n\t/**\n\t * @description Parses an SML body string into a Secs2Item.\n\t * @param smlBody The SML body string to parse.\n\t * @returns The parsed Secs2Item.\n\t */\n\tstatic parseBody(smlBody: string): AbstractSecs2Item | null {\n\t\tconst trimmed = smlBody.trim();\n\t\tif (trimmed.length === 0) {\n\t\t\treturn null;\n\t\t}\n\t\tconst cursor = new SmlCursor(trimmed);\n\t\treturn this.parseItem(cursor);\n\t}\n\n\tprivate static parseItem(cursor: SmlCursor): AbstractSecs2Item {\n\t\tcursor.skipWs();\n\t\tif (cursor.consume() !== \"<\") {\n\t\t\tthrow new Error(`Expected '<' at pos ${(cursor.pos - 1).toString()}`);\n\t\t}\n\n\t\tcursor.skipWs();\n\t\tconst typeStart = cursor.pos;\n\t\twhile (!cursor.eof() && /[A-Z0-9]/.test(cursor.peek().toUpperCase())) {\n\t\t\tcursor.consume();\n\t\t}\n\t\tconst typeStr = cursor.str.substring(typeStart, cursor.pos).toUpperCase();\n\n\t\t// Skip length [size]\n\t\tcursor.skipWs();\n\t\tif (cursor.peek() === \"[\") {\n\t\t\twhile (!cursor.eof() && cursor.peek() !== \"]\") cursor.consume();\n\t\t\tif (cursor.peek() === \"]\") cursor.consume();\n\t\t}\n\n\t\tlet item: AbstractSecs2Item;\n\n\t\tif (typeStr === \"L\") {\n\t\t\tconst items: AbstractSecs2Item[] = [];\n\n\t\t\twhile (true) {\n\t\t\t\tcursor.skipWs();\n\t\t\t\tif (cursor.peek() === \">\") {\n\t\t\t\t\tcursor.consume();\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (cursor.peek() === \"<\") {\n\t\t\t\t\titems.push(this.parseItem(cursor));\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Unexpected char in List: '${cursor.peek()}' at ${cursor.pos.toString()}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\titem = new Secs2ItemList(items);\n\t\t} else if (typeStr === \"A\") {\n\t\t\tcursor.skipWs();\n\t\t\t// Expect quoted string\n\t\t\tif (cursor.peek() === '\"') {\n\t\t\t\tcursor.consume();\n\t\t\t\tconst start = cursor.pos;\n\t\t\t\twhile (!cursor.eof() && cursor.peek() !== '\"') cursor.consume(); // Handle escaping? SML usually simple.\n\t\t\t\tconst val = cursor.str.substring(start, cursor.pos);\n\t\t\t\tcursor.consume(); // quote\n\t\t\t\titem = new Secs2ItemAscii(val);\n\t\t\t} else {\n\t\t\t\t// Empty string or unquoted? Standard says quoted.\n\t\t\t\t// Assuming empty if > follows immediately\n\t\t\t\tif (cursor.peek() === \">\") {\n\t\t\t\t\titem = new Secs2ItemAscii(\"\");\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Expected string value for type A at ${cursor.pos.toString()}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcursor.skipWs();\n\t\t\tif (cursor.consume() !== \">\") throw new Error(\"Expected '>' ending A\");\n\t\t} else if (typeStr === \"B\") {\n\t\t\t// Binary: 0xXX or XX\n\t\t\tconst buffer: number[] = [];\n\n\t\t\twhile (true) {\n\t\t\t\tcursor.skipWs();\n\t\t\t\tif (cursor.peek() === \">\") {\n\t\t\t\t\tcursor.consume();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// Read byte\n\t\t\t\t// Handle 0x prefix\n\t\t\t\tif (\n\t\t\t\t\tcursor.str.startsWith(\"0x\", cursor.pos) ||\n\t\t\t\t\tcursor.str.startsWith(\"0X\", cursor.pos)\n\t\t\t\t) {\n\t\t\t\t\tcursor.pos += 2;\n\t\t\t\t}\n\t\t\t\tconst byteStr = cursor.str.substring(cursor.pos, cursor.pos + 2);\n\t\t\t\tconst byteVal = parseInt(byteStr, 16);\n\t\t\t\tif (isNaN(byteVal))\n\t\t\t\t\tthrow new Error(`Invalid binary byte at ${cursor.pos.toString()}`);\n\t\t\t\tbuffer.push(byteVal);\n\t\t\t\tcursor.pos += 2;\n\t\t\t}\n\t\t\titem = new Secs2ItemBinary(Buffer.from(buffer));\n\t\t} else if (typeStr === \"BOOLEAN\") {\n\t\t\tconst bools: boolean[] = [];\n\n\t\t\twhile (true) {\n\t\t\t\tcursor.skipWs();\n\t\t\t\tif (cursor.peek() === \">\") {\n\t\t\t\t\tcursor.consume();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// Read T/True/F/False\n\t\t\t\tconst start = cursor.pos;\n\t\t\t\twhile (!cursor.eof() && /[A-Z]/.test(cursor.peek().toUpperCase()))\n\t\t\t\t\tcursor.consume();\n\t\t\t\tconst val = cursor.str.substring(start, cursor.pos).toUpperCase();\n\t\t\t\tif ([\"T\", \"TRUE\"].includes(val)) bools.push(true);\n\t\t\t\telse if ([\"F\", \"FALSE\"].includes(val)) bools.push(false);\n\t\t\t\telse throw new Error(`Invalid Boolean value ${val}`);\n\t\t\t}\n\t\t\titem = new Secs2ItemBoolean(bools.length === 1 ? bools[0] : bools);\n\t\t} else {\n\t\t\t// Numbers\n\t\t\tconst nums: number[] = []; // or bigint\n\t\t\t// Simplified number parsing\n\t\t\tconst isFloat = [\"F4\", \"F8\"].includes(typeStr);\n\n\t\t\twhile (true) {\n\t\t\t\tcursor.skipWs();\n\t\t\t\tif (cursor.peek() === \">\") {\n\t\t\t\t\tcursor.consume();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tconst start = cursor.pos;\n\t\t\t\twhile (!cursor.eof() && /[0-9.\\-+]/.test(cursor.peek()))\n\t\t\t\t\tcursor.consume();\n\t\t\t\tconst valStr = cursor.str.substring(start, cursor.pos);\n\t\t\t\tconst val = isFloat ? parseFloat(valStr) : Number(valStr); // Use BigInt if needed for U8/I8\n\t\t\t\tif (isNaN(val))\n\t\t\t\t\tthrow new Error(`Invalid number ${valStr} at ${start.toString()}`);\n\t\t\t\tnums.push(val);\n\t\t\t}\n\n\t\t\t// Determine SecsItemType\n\t\t\tconst type = SecsItemType[typeStr as keyof typeof SecsItemType];\n\n\t\t\tif (type === undefined) throw new Error(`Unknown type ${typeStr}`);\n\n\t\t\titem = new Secs2ItemNumeric(type, nums.length === 1 ? nums[0] : nums);\n\t\t}\n\n\t\treturn item;\n\t}\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "secs4js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A simple, efficient, and user-friendly SECS/GEM protocol library implemented in TypeScript.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com//secs4js.git"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": {
|
|
11
|
+
"name": "Ran Qian",
|
|
12
|
+
"email": "1151264028@qq.com"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./lib/index.js",
|
|
16
|
+
"types": "./lib/index.d.ts",
|
|
17
|
+
"files": [
|
|
18
|
+
"lib/"
|
|
19
|
+
],
|
|
20
|
+
"lint-staged": {
|
|
21
|
+
"*": "prettier --ignore-unknown --write"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@eslint/js": "9.39.1",
|
|
25
|
+
"@release-it/conventional-changelog": "10.0.2",
|
|
26
|
+
"@types/node": "24.10.1",
|
|
27
|
+
"@vitest/coverage-v8": "4.0.15",
|
|
28
|
+
"@vitest/eslint-plugin": "1.5.1",
|
|
29
|
+
"console-fail-test": "0.6.1",
|
|
30
|
+
"eslint": "9.39.1",
|
|
31
|
+
"husky": "9.1.7",
|
|
32
|
+
"lint-staged": "16.2.7",
|
|
33
|
+
"prettier": "3.7.3",
|
|
34
|
+
"release-it": "19.0.6",
|
|
35
|
+
"tsdown": "0.16.8",
|
|
36
|
+
"typescript": "5.9.3",
|
|
37
|
+
"typescript-eslint": "8.48.1",
|
|
38
|
+
"vitest": "4.0.15"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20.19.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"serialport": "^13.0.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsdown",
|
|
48
|
+
"format": "prettier .",
|
|
49
|
+
"lint": "eslint . --max-warnings 0",
|
|
50
|
+
"test": "vitest",
|
|
51
|
+
"tsc": "tsc"
|
|
52
|
+
}
|
|
53
|
+
}
|