elero-usb-transmitter-client 1.0.6 → 1.1.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/.github/workflows/nodejs.yml +27 -0
- package/README.md +62 -1
- package/dist/UsbTransmitterClient.d.ts +18 -0
- package/dist/UsbTransmitterClient.js +284 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +277 -0
- package/dist/domain/constants.d.ts +32 -0
- package/dist/domain/constants.js +44 -0
- package/dist/domain/enums.d.ts +34 -0
- package/dist/domain/enums.js +40 -0
- package/dist/domain/types.d.ts +3 -0
- package/dist/domain/types.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +18 -0
- package/dist/model/Response.d.ts +10 -0
- package/dist/model/Response.js +2 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +277 -0
- package/jest.json +1 -1
- package/package.json +9 -2
- package/src/UsbTransmitterClient.ts +46 -39
- package/src/cli.ts +167 -0
- package/test/UsbTransmitterClient.test.ts +39 -0
- package/test/UsbTransmitterClientMock.test.ts +182 -0
- package/__test__/UsbTransmitterClient.test.ts +0 -31
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
13
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
14
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
15
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
16
|
+
function step(op) {
|
|
17
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
18
|
+
while (_) try {
|
|
19
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
20
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
21
|
+
switch (op[0]) {
|
|
22
|
+
case 0: case 1: t = op; break;
|
|
23
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
24
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
25
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
26
|
+
default:
|
|
27
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
28
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
29
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
30
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
31
|
+
if (t[2]) _.ops.pop();
|
|
32
|
+
_.trys.pop(); continue;
|
|
33
|
+
}
|
|
34
|
+
op = body.call(thisArg, _);
|
|
35
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
36
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
var commander_1 = require("commander");
|
|
41
|
+
var inquirer = require("inquirer");
|
|
42
|
+
var UsbTransmitterClient_1 = require("./UsbTransmitterClient");
|
|
43
|
+
var enums_1 = require("./domain/enums");
|
|
44
|
+
var serialport_1 = require("serialport");
|
|
45
|
+
var program = new commander_1.Command();
|
|
46
|
+
var client = null;
|
|
47
|
+
program
|
|
48
|
+
.version('1.0.0')
|
|
49
|
+
.option('-p, --port <path>', 'Path to serial port')
|
|
50
|
+
.parse(process.argv);
|
|
51
|
+
var options = program.opts();
|
|
52
|
+
function main() {
|
|
53
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
54
|
+
var portPath, ports, portChoices, answer, error_1;
|
|
55
|
+
return __generator(this, function (_a) {
|
|
56
|
+
switch (_a.label) {
|
|
57
|
+
case 0:
|
|
58
|
+
portPath = options.port;
|
|
59
|
+
if (!!portPath) return [3 /*break*/, 3];
|
|
60
|
+
return [4 /*yield*/, serialport_1.SerialPort.list()];
|
|
61
|
+
case 1:
|
|
62
|
+
ports = _a.sent();
|
|
63
|
+
portChoices = ports.map(function (p) { return ({ name: p.path + " " + (p.manufacturer || ''), value: p.path }); });
|
|
64
|
+
if (portChoices.length === 0) {
|
|
65
|
+
console.error('No serial ports found. Please specify one with --port.');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
return [4 /*yield*/, inquirer.prompt([
|
|
69
|
+
{
|
|
70
|
+
type: 'list',
|
|
71
|
+
name: 'port',
|
|
72
|
+
message: 'Select Serial Port',
|
|
73
|
+
choices: portChoices,
|
|
74
|
+
},
|
|
75
|
+
])];
|
|
76
|
+
case 2:
|
|
77
|
+
answer = _a.sent();
|
|
78
|
+
portPath = answer.port;
|
|
79
|
+
_a.label = 3;
|
|
80
|
+
case 3:
|
|
81
|
+
client = new UsbTransmitterClient_1.UsbTransmitterClient(portPath);
|
|
82
|
+
_a.label = 4;
|
|
83
|
+
case 4:
|
|
84
|
+
_a.trys.push([4, 7, , 8]);
|
|
85
|
+
return [4 /*yield*/, client.open()];
|
|
86
|
+
case 5:
|
|
87
|
+
_a.sent();
|
|
88
|
+
console.log("Connected to " + portPath);
|
|
89
|
+
return [4 /*yield*/, mainMenu()];
|
|
90
|
+
case 6:
|
|
91
|
+
_a.sent();
|
|
92
|
+
return [3 /*break*/, 8];
|
|
93
|
+
case 7:
|
|
94
|
+
error_1 = _a.sent();
|
|
95
|
+
console.error('Error connecting to device:', error_1);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
return [3 /*break*/, 8];
|
|
98
|
+
case 8: return [2 /*return*/];
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function mainMenu() {
|
|
104
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
105
|
+
var answer, _a;
|
|
106
|
+
return __generator(this, function (_b) {
|
|
107
|
+
switch (_b.label) {
|
|
108
|
+
case 0: return [4 /*yield*/, inquirer.prompt([
|
|
109
|
+
{
|
|
110
|
+
type: 'list',
|
|
111
|
+
name: 'action',
|
|
112
|
+
message: 'Main Menu',
|
|
113
|
+
choices: [
|
|
114
|
+
{ name: 'Check Channels', value: 'check' },
|
|
115
|
+
{ name: 'Select Channel', value: 'select' },
|
|
116
|
+
new inquirer.Separator(),
|
|
117
|
+
{ name: 'Exit', value: 'exit' },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
])];
|
|
121
|
+
case 1:
|
|
122
|
+
answer = _b.sent();
|
|
123
|
+
_a = answer.action;
|
|
124
|
+
switch (_a) {
|
|
125
|
+
case 'check': return [3 /*break*/, 2];
|
|
126
|
+
case 'select': return [3 /*break*/, 4];
|
|
127
|
+
case 'exit': return [3 /*break*/, 6];
|
|
128
|
+
}
|
|
129
|
+
return [3 /*break*/, 8];
|
|
130
|
+
case 2: return [4 /*yield*/, checkChannels()];
|
|
131
|
+
case 3:
|
|
132
|
+
_b.sent();
|
|
133
|
+
return [3 /*break*/, 8];
|
|
134
|
+
case 4: return [4 /*yield*/, selectChannel()];
|
|
135
|
+
case 5:
|
|
136
|
+
_b.sent();
|
|
137
|
+
return [3 /*break*/, 8];
|
|
138
|
+
case 6: return [4 /*yield*/, client.close()];
|
|
139
|
+
case 7:
|
|
140
|
+
_b.sent();
|
|
141
|
+
process.exit(0);
|
|
142
|
+
_b.label = 8;
|
|
143
|
+
case 8: return [2 /*return*/];
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function checkChannels() {
|
|
149
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
150
|
+
var channels, error_2;
|
|
151
|
+
return __generator(this, function (_a) {
|
|
152
|
+
switch (_a.label) {
|
|
153
|
+
case 0:
|
|
154
|
+
console.log('Checking channels...');
|
|
155
|
+
_a.label = 1;
|
|
156
|
+
case 1:
|
|
157
|
+
_a.trys.push([1, 3, , 4]);
|
|
158
|
+
return [4 /*yield*/, client.checkChannels()];
|
|
159
|
+
case 2:
|
|
160
|
+
channels = _a.sent();
|
|
161
|
+
console.log('Active Channels:', channels.join(', '));
|
|
162
|
+
return [3 /*break*/, 4];
|
|
163
|
+
case 3:
|
|
164
|
+
error_2 = _a.sent();
|
|
165
|
+
console.error('Error checking channels:', error_2);
|
|
166
|
+
return [3 /*break*/, 4];
|
|
167
|
+
case 4: return [4 /*yield*/, mainMenu()];
|
|
168
|
+
case 5:
|
|
169
|
+
_a.sent();
|
|
170
|
+
return [2 /*return*/];
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function selectChannel() {
|
|
176
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
177
|
+
var answer, channel;
|
|
178
|
+
return __generator(this, function (_a) {
|
|
179
|
+
switch (_a.label) {
|
|
180
|
+
case 0: return [4 /*yield*/, inquirer.prompt([
|
|
181
|
+
{
|
|
182
|
+
type: 'input',
|
|
183
|
+
name: 'channel',
|
|
184
|
+
message: 'Enter Channel Number (1-9):',
|
|
185
|
+
validate: function (input) {
|
|
186
|
+
var num = parseInt(input, 10);
|
|
187
|
+
if (isNaN(num) || num < 1 || num > 9) {
|
|
188
|
+
return 'Please enter a number between 1 and 9';
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
])];
|
|
194
|
+
case 1:
|
|
195
|
+
answer = _a.sent();
|
|
196
|
+
channel = parseInt(answer.channel, 10);
|
|
197
|
+
return [4 /*yield*/, channelMenu(channel)];
|
|
198
|
+
case 2:
|
|
199
|
+
_a.sent();
|
|
200
|
+
return [2 /*return*/];
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function channelMenu(channel) {
|
|
206
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
207
|
+
var answer, info, cmd, response, error_3;
|
|
208
|
+
return __generator(this, function (_a) {
|
|
209
|
+
switch (_a.label) {
|
|
210
|
+
case 0: return [4 /*yield*/, inquirer.prompt([
|
|
211
|
+
{
|
|
212
|
+
type: 'list',
|
|
213
|
+
name: 'action',
|
|
214
|
+
message: "Channel " + channel + " Actions",
|
|
215
|
+
choices: [
|
|
216
|
+
{ name: 'Get Info', value: 'info' },
|
|
217
|
+
{ name: 'Move Up', value: 'up' },
|
|
218
|
+
{ name: 'Move Down', value: 'down' },
|
|
219
|
+
{ name: 'Stop', value: 'stop' },
|
|
220
|
+
new inquirer.Separator(),
|
|
221
|
+
{ name: 'Back to Main Menu', value: 'back' },
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
])];
|
|
225
|
+
case 1:
|
|
226
|
+
answer = _a.sent();
|
|
227
|
+
if (!(answer.action === 'back')) return [3 /*break*/, 3];
|
|
228
|
+
return [4 /*yield*/, mainMenu()];
|
|
229
|
+
case 2:
|
|
230
|
+
_a.sent();
|
|
231
|
+
return [2 /*return*/];
|
|
232
|
+
case 3:
|
|
233
|
+
_a.trys.push([3, 8, , 9]);
|
|
234
|
+
if (!(answer.action === 'info')) return [3 /*break*/, 5];
|
|
235
|
+
return [4 /*yield*/, client.getInfo(channel)];
|
|
236
|
+
case 4:
|
|
237
|
+
info = _a.sent();
|
|
238
|
+
console.log('Channel Info:', info);
|
|
239
|
+
return [3 /*break*/, 7];
|
|
240
|
+
case 5:
|
|
241
|
+
cmd = void 0;
|
|
242
|
+
switch (answer.action) {
|
|
243
|
+
case 'up':
|
|
244
|
+
cmd = enums_1.ControlCommand.up;
|
|
245
|
+
break;
|
|
246
|
+
case 'down':
|
|
247
|
+
cmd = enums_1.ControlCommand.down;
|
|
248
|
+
break;
|
|
249
|
+
case 'stop':
|
|
250
|
+
cmd = enums_1.ControlCommand.stop;
|
|
251
|
+
break;
|
|
252
|
+
default:
|
|
253
|
+
throw new Error('Unknown command');
|
|
254
|
+
}
|
|
255
|
+
console.log("Sending " + answer.action + " command to channel " + channel + "...");
|
|
256
|
+
return [4 /*yield*/, client.sendControlCommand(channel, cmd)];
|
|
257
|
+
case 6:
|
|
258
|
+
response = _a.sent();
|
|
259
|
+
console.log('Response:', response);
|
|
260
|
+
_a.label = 7;
|
|
261
|
+
case 7: return [3 /*break*/, 9];
|
|
262
|
+
case 8:
|
|
263
|
+
error_3 = _a.sent();
|
|
264
|
+
console.error('Error executing command:', error_3);
|
|
265
|
+
return [3 /*break*/, 9];
|
|
266
|
+
case 9: return [4 /*yield*/, channelMenu(channel)];
|
|
267
|
+
case 10:
|
|
268
|
+
_a.sent();
|
|
269
|
+
return [2 /*return*/];
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
main().catch(function (err) {
|
|
275
|
+
console.error('Unexpected error:', err);
|
|
276
|
+
process.exit(1);
|
|
277
|
+
});
|
package/jest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elero-usb-transmitter-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/src/index.js",
|
|
6
6
|
"types": "./dist/src/index.d.ts",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"build": "tsc",
|
|
10
10
|
"setup": "npm install"
|
|
11
11
|
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"elero-usb-transmitter-client": "./dist/src/cli.js",
|
|
14
|
+
"elero-cli": "./dist/src/cli.js"
|
|
15
|
+
},
|
|
12
16
|
"repository": {
|
|
13
17
|
"type": "git",
|
|
14
18
|
"url": "git+https://github.com/marc2016/elero-usb-transmitter-client.git"
|
|
@@ -20,16 +24,19 @@
|
|
|
20
24
|
},
|
|
21
25
|
"homepage": "https://github.com/marc2016/elero-usb-transmitter-client#readme",
|
|
22
26
|
"devDependencies": {
|
|
27
|
+
"@types/inquirer": "^8.2.12",
|
|
23
28
|
"@types/jest": "^24.0.25",
|
|
24
29
|
"@types/lodash": "^4.14.149",
|
|
25
30
|
"@types/node": "^13.1.5",
|
|
26
31
|
"@types/serialport": "^8.0.1",
|
|
27
32
|
"@typescript-eslint/eslint-plugin": "^2.23.0",
|
|
28
33
|
"@typescript-eslint/parser": "^2.23.0",
|
|
34
|
+
"commander": "^14.0.2",
|
|
29
35
|
"eslint": "^6.8.0",
|
|
30
36
|
"eslint-config-google": "^0.14.0",
|
|
31
37
|
"eslint-config-prettier": "^6.10.1",
|
|
32
38
|
"eslint-plugin-prettier": "^3.1.2",
|
|
39
|
+
"inquirer": "^8.2.7",
|
|
33
40
|
"jest": "^24.9.0",
|
|
34
41
|
"prettier": "^2.0.2",
|
|
35
42
|
"ts-jest": "^24.2.0",
|
|
@@ -40,4 +47,4 @@
|
|
|
40
47
|
"lodash": "^4.17.15",
|
|
41
48
|
"serialport": "^13.0.0"
|
|
42
49
|
}
|
|
43
|
-
}
|
|
50
|
+
}
|
|
@@ -60,20 +60,14 @@ export class UsbTransmitterClient {
|
|
|
60
60
|
public async checkChannels(): Promise<number[]> {
|
|
61
61
|
const data = [BYTE_HEADER, BYTE_LENGTH_2, EasyCommand.EASY_CHECK]
|
|
62
62
|
const release = await mutex.acquire()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
this.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
const response = that.parseResponse(responseBytes as Buffer)
|
|
73
|
-
release()
|
|
74
|
-
return resolve(response.activeChannels)
|
|
75
|
-
})
|
|
76
|
-
})
|
|
63
|
+
try {
|
|
64
|
+
await this.sendCommand(data)
|
|
65
|
+
const responseBytes = await this.waitForResponse(RESPONSE_LENGTH_CHECK)
|
|
66
|
+
const response = this.parseResponse(responseBytes)
|
|
67
|
+
return response.activeChannels
|
|
68
|
+
} finally {
|
|
69
|
+
release()
|
|
70
|
+
}
|
|
77
71
|
}
|
|
78
72
|
|
|
79
73
|
public async getInfo(channel: number): Promise<Response> {
|
|
@@ -88,20 +82,14 @@ export class UsbTransmitterClient {
|
|
|
88
82
|
lowChannels,
|
|
89
83
|
]
|
|
90
84
|
const release = await mutex.acquire()
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
const response = that.parseResponse(responseBytes as Buffer)
|
|
101
|
-
release()
|
|
102
|
-
return resolve(response)
|
|
103
|
-
})
|
|
104
|
-
})
|
|
85
|
+
try {
|
|
86
|
+
await this.sendCommand(data)
|
|
87
|
+
const responseBytes = await this.waitForResponse(RESPONSE_LENGTH_INFO)
|
|
88
|
+
const response = this.parseResponse(responseBytes)
|
|
89
|
+
return response
|
|
90
|
+
} finally {
|
|
91
|
+
release()
|
|
92
|
+
}
|
|
105
93
|
}
|
|
106
94
|
|
|
107
95
|
public async sendControlCommand(
|
|
@@ -120,19 +108,38 @@ export class UsbTransmitterClient {
|
|
|
120
108
|
controlCommand,
|
|
121
109
|
]
|
|
122
110
|
const release = await mutex.acquire()
|
|
123
|
-
|
|
111
|
+
try {
|
|
112
|
+
await this.sendCommand(data)
|
|
113
|
+
const responseBytes = await this.waitForResponse(RESPONSE_LENGTH_INFO)
|
|
114
|
+
const response = this.parseResponse(responseBytes)
|
|
115
|
+
return response
|
|
116
|
+
} finally {
|
|
117
|
+
release()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private waitForResponse(length: number): Promise<Buffer> {
|
|
124
122
|
return new Promise((resolve, reject) => {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
123
|
+
const timeout = setTimeout(() => {
|
|
124
|
+
cleanup()
|
|
125
|
+
reject(new Error('Timeout waiting for response'))
|
|
126
|
+
}, 2000)
|
|
127
|
+
|
|
128
|
+
const tryRead = () => {
|
|
129
|
+
const buffer = this.serialPort.read(length)
|
|
130
|
+
if (buffer) {
|
|
131
|
+
cleanup()
|
|
132
|
+
resolve(buffer)
|
|
131
133
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const cleanup = () => {
|
|
137
|
+
clearTimeout(timeout)
|
|
138
|
+
this.serialPort.removeListener('readable', tryRead)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.serialPort.on('readable', tryRead)
|
|
142
|
+
tryRead()
|
|
136
143
|
})
|
|
137
144
|
}
|
|
138
145
|
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander'
|
|
3
|
+
import * as inquirer from 'inquirer'
|
|
4
|
+
import { UsbTransmitterClient } from './UsbTransmitterClient'
|
|
5
|
+
import { ControlCommand } from './domain/enums'
|
|
6
|
+
import { SerialPort } from 'serialport'
|
|
7
|
+
|
|
8
|
+
const program = new Command()
|
|
9
|
+
let client: UsbTransmitterClient | null = null
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.version('1.0.0')
|
|
13
|
+
.option('-p, --port <path>', 'Path to serial port')
|
|
14
|
+
.parse(process.argv)
|
|
15
|
+
|
|
16
|
+
const options = program.opts()
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
let portPath = options.port
|
|
20
|
+
|
|
21
|
+
if (!portPath) {
|
|
22
|
+
const ports = await SerialPort.list()
|
|
23
|
+
const portChoices = ports.map((p) => ({ name: `${p.path} ${p.manufacturer || ''}`, value: p.path }))
|
|
24
|
+
|
|
25
|
+
if (portChoices.length === 0) {
|
|
26
|
+
console.error('No serial ports found. Please specify one with --port.')
|
|
27
|
+
process.exit(1)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const answer = await inquirer.prompt([
|
|
31
|
+
{
|
|
32
|
+
type: 'list',
|
|
33
|
+
name: 'port',
|
|
34
|
+
message: 'Select Serial Port',
|
|
35
|
+
choices: portChoices,
|
|
36
|
+
},
|
|
37
|
+
])
|
|
38
|
+
portPath = answer.port
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
client = new UsbTransmitterClient(portPath)
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await client.open()
|
|
45
|
+
console.log(`Connected to ${portPath}`)
|
|
46
|
+
await mainMenu()
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error('Error connecting to device:', error)
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function mainMenu() {
|
|
54
|
+
const answer = await inquirer.prompt([
|
|
55
|
+
{
|
|
56
|
+
type: 'list',
|
|
57
|
+
name: 'action',
|
|
58
|
+
message: 'Main Menu',
|
|
59
|
+
choices: [
|
|
60
|
+
{ name: 'Check Channels', value: 'check' },
|
|
61
|
+
{ name: 'Select Channel', value: 'select' },
|
|
62
|
+
new inquirer.Separator(),
|
|
63
|
+
{ name: 'Exit', value: 'exit' },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
])
|
|
67
|
+
|
|
68
|
+
switch (answer.action) {
|
|
69
|
+
case 'check':
|
|
70
|
+
await checkChannels()
|
|
71
|
+
break
|
|
72
|
+
case 'select':
|
|
73
|
+
await selectChannel()
|
|
74
|
+
break
|
|
75
|
+
case 'exit':
|
|
76
|
+
await client!.close()
|
|
77
|
+
process.exit(0)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function checkChannels() {
|
|
82
|
+
console.log('Checking channels...')
|
|
83
|
+
try {
|
|
84
|
+
const channels = await client!.checkChannels()
|
|
85
|
+
console.log('Active Channels:', channels.join(', '))
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Error checking channels:', error)
|
|
88
|
+
}
|
|
89
|
+
await mainMenu()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function selectChannel() {
|
|
93
|
+
const answer = await inquirer.prompt([
|
|
94
|
+
{
|
|
95
|
+
type: 'input',
|
|
96
|
+
name: 'channel',
|
|
97
|
+
message: 'Enter Channel Number (1-9):',
|
|
98
|
+
validate: (input) => {
|
|
99
|
+
const num = parseInt(input, 10)
|
|
100
|
+
if (isNaN(num) || num < 1 || num > 9) {
|
|
101
|
+
return 'Please enter a number between 1 and 9'
|
|
102
|
+
}
|
|
103
|
+
return true
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
const channel = parseInt(answer.channel, 10)
|
|
109
|
+
await channelMenu(channel)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function channelMenu(channel: number) {
|
|
113
|
+
const answer = await inquirer.prompt([
|
|
114
|
+
{
|
|
115
|
+
type: 'list',
|
|
116
|
+
name: 'action',
|
|
117
|
+
message: `Channel ${channel} Actions`,
|
|
118
|
+
choices: [
|
|
119
|
+
{ name: 'Get Info', value: 'info' },
|
|
120
|
+
{ name: 'Move Up', value: 'up' },
|
|
121
|
+
{ name: 'Move Down', value: 'down' },
|
|
122
|
+
{ name: 'Stop', value: 'stop' },
|
|
123
|
+
new inquirer.Separator(),
|
|
124
|
+
{ name: 'Back to Main Menu', value: 'back' },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
])
|
|
128
|
+
|
|
129
|
+
if (answer.action === 'back') {
|
|
130
|
+
await mainMenu()
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
if (answer.action === 'info') {
|
|
136
|
+
const info = await client!.getInfo(channel)
|
|
137
|
+
console.log('Channel Info:', info)
|
|
138
|
+
} else {
|
|
139
|
+
let cmd: ControlCommand
|
|
140
|
+
switch (answer.action) {
|
|
141
|
+
case 'up':
|
|
142
|
+
cmd = ControlCommand.up
|
|
143
|
+
break
|
|
144
|
+
case 'down':
|
|
145
|
+
cmd = ControlCommand.down
|
|
146
|
+
break
|
|
147
|
+
case 'stop':
|
|
148
|
+
cmd = ControlCommand.stop
|
|
149
|
+
break
|
|
150
|
+
default:
|
|
151
|
+
throw new Error('Unknown command')
|
|
152
|
+
}
|
|
153
|
+
console.log(`Sending ${answer.action} command to channel ${channel}...`)
|
|
154
|
+
const response = await client!.sendControlCommand(channel, cmd)
|
|
155
|
+
console.log('Response:', response)
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error('Error executing command:', error)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await channelMenu(channel)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
main().catch((err) => {
|
|
165
|
+
console.error('Unexpected error:', err)
|
|
166
|
+
process.exit(1)
|
|
167
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { UsbTransmitterClient } from "../src/UsbTransmitterClient"
|
|
2
|
+
import { ControlCommand, EasyCommand, InfoData } from "../src/domain/enums"
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
|
|
5
|
+
jest.setTimeout(200000)
|
|
6
|
+
|
|
7
|
+
const devPath = '/dev/ttyUSB0'
|
|
8
|
+
const client = new UsbTransmitterClient(devPath)
|
|
9
|
+
|
|
10
|
+
const aktiveChannelsForTest = [1, 2]
|
|
11
|
+
|
|
12
|
+
// Skip tests if hardware not present
|
|
13
|
+
const describeHardware = fs.existsSync(devPath) ? describe : describe.skip
|
|
14
|
+
|
|
15
|
+
describeHardware('Integration Tests (Hardware)', () => {
|
|
16
|
+
|
|
17
|
+
test('checkChannels', async () => {
|
|
18
|
+
await client.open()
|
|
19
|
+
const channels = await client.checkChannels()
|
|
20
|
+
expect(channels).toEqual(aktiveChannelsForTest)
|
|
21
|
+
await client.close()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('getInfo', async () => {
|
|
25
|
+
await client.open()
|
|
26
|
+
const response = await client.getInfo(1)
|
|
27
|
+
console.log(response)
|
|
28
|
+
expect(response.command).toEqual(EasyCommand.EASY_ACK)
|
|
29
|
+
await client.close()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('sendControlCommand', async () => {
|
|
33
|
+
await client.open()
|
|
34
|
+
const response = await client.sendControlCommand(1, ControlCommand.down)
|
|
35
|
+
console.log(response)
|
|
36
|
+
expect(response.status).toEqual(InfoData.INFO_MOVING_DOWN)
|
|
37
|
+
await client.close()
|
|
38
|
+
})
|
|
39
|
+
})
|