@microtronics/studio-cli 0.42.0 → 0.43.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/CHANGELOG.md +16 -3
- package/out/main.js +28 -0
- package/out/usb.js +189 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.43.0 (2026-01-19)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
|
|
7
|
-
* add
|
|
8
|
-
* add
|
|
7
|
+
* add debug option to enable mdnCom logging ([34dfd29](https://bitbucket.org/microtronics-core/vscode-studio/commits/34dfd29cf2f5e6776e31463a85f0484a6907b13b))
|
|
8
|
+
* add support for uploading any amx file to an usb device ([e89f108](https://bitbucket.org/microtronics-core/vscode-studio/commits/e89f1087e22e6c0cc88c8b68bbbcc4f1d813dc4f))
|
|
9
|
+
* add USB device detection and listing functionality ([906e3e1](https://bitbucket.org/microtronics-core/vscode-studio/commits/906e3e12bca647f5ceba8b4196f00d507be163d9))
|
|
10
|
+
* add USB device listing command ([fb16e1a](https://bitbucket.org/microtronics-core/vscode-studio/commits/fb16e1ab0c9a52e38467b664e4b2b6adb135a249)), closes [#close](https://bitbucket.org/microtronics-core/vscode-studio/issues/close)
|
|
9
11
|
|
|
10
12
|
### Bug Fixes
|
|
11
13
|
|
|
14
|
+
* ensure proper exit code on successful USB upload ([cc1ae63](https://bitbucket.org/microtronics-core/vscode-studio/commits/cc1ae632669e785017c2afaa689c3750ba22d7fb))
|
|
15
|
+
|
|
16
|
+
## 0.42.0 (2026-01-13)
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* add isIotApp helper function to check IoT app project type ([98ce8a1](https://bitbucket.org/microtronics-core/vscode-studio/commits/98ce8a1358bdd4d2ba1635cd4ef1a3f6e8ec0004))
|
|
21
|
+
* add POST endpoint for addon timeseries data insertion ([ace6880](https://bitbucket.org/microtronics-core/vscode-studio/commits/ace6880f801faefc303a8020c7acf87b06db5709))
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
12
25
|
* restrict write access rejection to IoT apps only ([13aa98f](https://bitbucket.org/microtronics-core/vscode-studio/commits/13aa98f261632b576681a894ecb375b7ab5a4b99))
|
|
13
26
|
|
|
14
27
|
## 0.41.0 (2026-01-12)
|
package/out/main.js
CHANGED
|
@@ -13,6 +13,7 @@ const update_notifier_1 = __importDefault(require("update-notifier"));
|
|
|
13
13
|
const api_1 = require("./api");
|
|
14
14
|
const log_1 = require("./log");
|
|
15
15
|
const argumentParser_1 = require("./argumentParser");
|
|
16
|
+
const usb_1 = require("./usb");
|
|
16
17
|
const program = new commander_1.Command();
|
|
17
18
|
const notifier = (0, update_notifier_1.default)({ pkg: package_json_1.default });
|
|
18
19
|
const logger = new log_1.Log.Logger();
|
|
@@ -145,6 +146,33 @@ program
|
|
|
145
146
|
const { env, token } = getDefaultOptions(opts);
|
|
146
147
|
main(command, (0, api_1.pack)(cwd, cliFS_1.cliFS, logger, env, token, opts.phase));
|
|
147
148
|
});
|
|
149
|
+
// USB commands
|
|
150
|
+
const usbCommand = program.command('usb').description('USB device management commands');
|
|
151
|
+
usbCommand
|
|
152
|
+
.command('list')
|
|
153
|
+
.description('List available USB devices')
|
|
154
|
+
.action(async () => {
|
|
155
|
+
await (0, usb_1.printUsbDevices)(logger);
|
|
156
|
+
notifier.notify();
|
|
157
|
+
});
|
|
158
|
+
usbCommand
|
|
159
|
+
.command('upload')
|
|
160
|
+
.description('Upload an AMX binary to a USB device')
|
|
161
|
+
.requiredOption('-s, --serial <serial>', 'Device serial number (16 characters hex)')
|
|
162
|
+
.requiredOption('-f, --file <path>', 'Path to the AMX file (relative to current directory)')
|
|
163
|
+
.requiredOption('--debug', 'Enable debug logging', false)
|
|
164
|
+
.action(async (opts) => {
|
|
165
|
+
const fileUri = vscode_uri_1.Utils.joinPath(cwd, opts.file);
|
|
166
|
+
const result = await (0, usb_1.uploadToDevice)(opts.serial, fileUri, cliFS_1.cliFS, logger, opts.debug);
|
|
167
|
+
notifier.notify();
|
|
168
|
+
if (!result.success) {
|
|
169
|
+
logger.error(result.message);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
148
176
|
module.exports = function (argv) {
|
|
149
177
|
program.parse(argv);
|
|
150
178
|
};
|
package/out/usb.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listUsbDevices = listUsbDevices;
|
|
4
|
+
exports.printUsbDevices = printUsbDevices;
|
|
5
|
+
exports.findDeviceBySerialNumber = findDeviceBySerialNumber;
|
|
6
|
+
exports.uploadToDevice = uploadToDevice;
|
|
7
|
+
const usb_1 = require("usb");
|
|
8
|
+
const uto_master_1 = require("uto-master");
|
|
9
|
+
const utoStack_1 = require("uto-master/dist/src/utilities/utoStack");
|
|
10
|
+
// Microtronics vendor ID
|
|
11
|
+
const MICROTRONICS_VENDOR_ID = 0x2544;
|
|
12
|
+
/**
|
|
13
|
+
* Custom WebUSB instance configured to allow Microtronics devices
|
|
14
|
+
*/
|
|
15
|
+
const customWebUSB = new usb_1.WebUSB({
|
|
16
|
+
// Bypass checking for authorized devices
|
|
17
|
+
allowedDevices: [
|
|
18
|
+
{
|
|
19
|
+
vendorId: MICROTRONICS_VENDOR_ID
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Lists all available USB devices filtered by Microtronics vendor ID
|
|
25
|
+
* @returns Promise resolving to an array of USB device information
|
|
26
|
+
*/
|
|
27
|
+
async function listUsbDevices() {
|
|
28
|
+
const devices = await customWebUSB.getDevices();
|
|
29
|
+
return devices.map((device) => ({
|
|
30
|
+
productName: device.productName,
|
|
31
|
+
serialNumber: device.serialNumber || device.productName?.split(' ').pop(),
|
|
32
|
+
vendorId: device.vendorId,
|
|
33
|
+
productId: device.productId
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Prints USB device product names using the provided logger
|
|
38
|
+
* @param logger - The logger instance to use for output
|
|
39
|
+
*/
|
|
40
|
+
async function printUsbDevices(logger) {
|
|
41
|
+
const devices = await listUsbDevices();
|
|
42
|
+
if (devices.length === 0) {
|
|
43
|
+
logger.info('No USB devices found.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
logger.info('Available USB devices:');
|
|
47
|
+
devices.forEach((device, index) => {
|
|
48
|
+
const name = device.productName ||
|
|
49
|
+
`Unknown Device (VID: 0x${device.vendorId.toString(16)}, PID: 0x${device.productId.toString(16)})`;
|
|
50
|
+
const serial = device.serialNumber ? ` [${device.serialNumber}]` : '';
|
|
51
|
+
logger.log(` ${index + 1}. ${name}${serial}`);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Find a USB device by its serial number
|
|
56
|
+
* @param serialNumber - The 16-character hex serial number of the device
|
|
57
|
+
* @returns The USB device or null if not found
|
|
58
|
+
*/
|
|
59
|
+
async function findDeviceBySerialNumber(serialNumber) {
|
|
60
|
+
const devices = await customWebUSB.getDevices();
|
|
61
|
+
for (const device of devices) {
|
|
62
|
+
// Check direct serial number or serial from product name
|
|
63
|
+
const deviceSerial = device.serialNumber || device.productName?.split(' ').pop();
|
|
64
|
+
if (deviceSerial?.toLowerCase() === serialNumber.toLowerCase()) {
|
|
65
|
+
return device;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Upload an AMX binary file to a USB device
|
|
72
|
+
* @param serialNumber - The serial number of the target device
|
|
73
|
+
* @param fileUri - The URI to the AMX file
|
|
74
|
+
* @param fs - The filesystem interface for file operations
|
|
75
|
+
* @param logger - The logger instance for output
|
|
76
|
+
* @param debugLog - Enable debug logging
|
|
77
|
+
* @returns Promise resolving to an UploadResult
|
|
78
|
+
*/
|
|
79
|
+
async function uploadToDevice(serialNumber, fileUri, fs, logger, debugLog = false) {
|
|
80
|
+
// Check if file exists
|
|
81
|
+
const fileStat = await fs.stat(fileUri);
|
|
82
|
+
if (!fileStat) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
message: `File not found: ${fileUri.fsPath}`
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Find the device by serial number
|
|
89
|
+
logger.info(`Searching for device with serial number: ${serialNumber}`);
|
|
90
|
+
const device = await findDeviceBySerialNumber(serialNumber);
|
|
91
|
+
if (!device) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
message: `Device not found with serial number: ${serialNumber}`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
logger.info(`Device found: ${device.productName}`);
|
|
98
|
+
// Read the file contents
|
|
99
|
+
const fileData = (await fs.readFile(fileUri));
|
|
100
|
+
// Initialize UTO Master
|
|
101
|
+
const utoMaster = new uto_master_1.UtoMaster();
|
|
102
|
+
let deviceConnected = false;
|
|
103
|
+
try {
|
|
104
|
+
await utoMaster.init();
|
|
105
|
+
// Set up UTO transmit callback
|
|
106
|
+
utoMaster.utoTx(async (data) => {
|
|
107
|
+
if (device.opened) {
|
|
108
|
+
await device.transferOut(uto_master_1.USB_CONFIGURATION.utoEndpoint, data);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
if (debugLog) {
|
|
112
|
+
utoMaster.on('mdnCom', (message) => {
|
|
113
|
+
logger.log('mdnCom', message);
|
|
114
|
+
});
|
|
115
|
+
utoMaster.on('mdnComError', (message) => {
|
|
116
|
+
logger.log('mdnComError', message);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// Set up progress reporting
|
|
120
|
+
let lastProgress = -1;
|
|
121
|
+
utoMaster.on('installing', (data) => {
|
|
122
|
+
if (!data) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// /*const progress = Math.round(data.progress);
|
|
126
|
+
if (data !== lastProgress) {
|
|
127
|
+
lastProgress = data;
|
|
128
|
+
logger.info(`Upload progress: ${data}%`);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
// Open device and claim interfaces
|
|
132
|
+
await device.open();
|
|
133
|
+
await device.selectConfiguration(uto_master_1.USB_CONFIGURATION.configurationNumber);
|
|
134
|
+
await device.claimInterface(uto_master_1.USB_CONFIGURATION.utoInterface);
|
|
135
|
+
deviceConnected = true;
|
|
136
|
+
logger.info('Device connected, starting upload...');
|
|
137
|
+
// Start UTO polling for receiving data
|
|
138
|
+
startUtoPolling(device, utoMaster, () => deviceConnected);
|
|
139
|
+
await utoMaster.requestDeviceInfo();
|
|
140
|
+
// Upload the file
|
|
141
|
+
await utoMaster.sendFile(utoStack_1.UtoFileType.pawn, new Uint8Array(fileData.buffer, fileData.byteOffset, fileData.length));
|
|
142
|
+
logger.done('Upload completed successfully!');
|
|
143
|
+
return {
|
|
144
|
+
success: true,
|
|
145
|
+
message: 'File uploaded successfully'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
150
|
+
return {
|
|
151
|
+
success: false,
|
|
152
|
+
message: `Upload failed: ${errorMessage}`
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
// Clean up - close device
|
|
157
|
+
deviceConnected = false;
|
|
158
|
+
if (device.opened) {
|
|
159
|
+
try {
|
|
160
|
+
await device.close().catch();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Ignore close errors
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Start polling for UTO data from the device
|
|
170
|
+
* @param device - The USB device
|
|
171
|
+
* @param utoMaster - The UTO Master instance
|
|
172
|
+
* @param isConnected - Function that returns connection status
|
|
173
|
+
*/
|
|
174
|
+
async function startUtoPolling(device, utoMaster, isConnected) {
|
|
175
|
+
while (isConnected()) {
|
|
176
|
+
try {
|
|
177
|
+
const result = await device.transferIn(uto_master_1.USB_CONFIGURATION.utoEndpoint, uto_master_1.USB_CONFIGURATION.utoSize);
|
|
178
|
+
if (result.status === 'ok' && result.data) {
|
|
179
|
+
const uint8Data = new Uint8Array(result.data.buffer);
|
|
180
|
+
await utoMaster.utoRx(uint8Data);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Connection lost or polling stopped
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=usb.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microtronics/studio-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "Microtronics Studio CLI Tool",
|
|
5
5
|
"main": "./out/api.js",
|
|
6
6
|
"typings": "./dist/studio-cli.d.ts",
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
"semver": "^7.7.2",
|
|
46
46
|
"tinytar-fix": "^0.1.1",
|
|
47
47
|
"update-notifier": "^7.3.1",
|
|
48
|
+
"usb": "^2.14.0",
|
|
49
|
+
"uto-master": "bitbucket:microtronics-core/wasm-uto-master",
|
|
48
50
|
"vscode-uri": "^3.0.8",
|
|
49
51
|
"yaml": "^2.6.1"
|
|
50
52
|
},
|