pxt-core
Version:
Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors
262 lines (261 loc) • 8.19 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HidIO = exports.HIDError = exports.connectSerial = exports.initAsync = exports.mkWebUSBOrHidPacketIOAsync = exports.hf2ConnectAsync = exports.getHF2DevicesAsync = exports.deviceInfo = exports.dmesgAsync = exports.serialAsync = exports.listAsync = exports.isInstalled = void 0;
var HF2 = pxt.HF2;
var U = pxt.U;
const nodeutil = require("./nodeutil");
const PXT_USE_HID = !!process.env["PXT_USE_HID"];
function useWebUSB() {
return !!pxt.appTarget.compile.webUSB;
}
let HID = undefined;
function requireHID(install) {
if (!PXT_USE_HID)
return false;
if (useWebUSB()) {
// in node.js, we need "webusb" package
if (pxt.Util.isNodeJS)
return !!nodeutil.lazyRequire("webusb", install);
// in the browser, check that USB is defined
return pxt.usb.isAvailable();
}
else {
if (!HID)
HID = nodeutil.lazyRequire("node-hid", install);
return !!HID;
}
}
function isInstalled(install) {
return requireHID(!!install);
}
exports.isInstalled = isInstalled;
function listAsync() {
if (!isInstalled(true))
return Promise.resolve();
return getHF2DevicesAsync()
.then(devices => {
pxt.log(`found ${devices.length} HID devices`);
devices.forEach(device => pxt.log(device));
});
}
exports.listAsync = listAsync;
function serialAsync() {
if (!isInstalled(true))
return Promise.resolve();
return initAsync()
.then(d => {
d.autoReconnect = true;
connectSerial(d);
});
}
exports.serialAsync = serialAsync;
function dmesgAsync() {
HF2.enableLog();
return initAsync()
.then(d => d.talkAsync(pxt.HF2.HF2_CMD_DMESG)
.then(resp => {
console.log(U.fromUTF8Array(resp));
return d.disconnectAsync();
}));
}
exports.dmesgAsync = dmesgAsync;
function hex(n) {
return ("000" + n.toString(16)).slice(-4);
}
function deviceInfo(h) {
return `${h.product} (by ${h.manufacturer} at USB ${hex(h.vendorId)}:${hex(h.productId)})`;
}
exports.deviceInfo = deviceInfo;
function getHF2Devices() {
if (!isInstalled(false))
return [];
let devices = HID.devices();
for (let d of devices) {
pxt.debug(JSON.stringify(d));
}
let serial = pxt.appTarget.serial;
return devices.filter(d => (serial && parseInt(serial.productId) == d.productId && parseInt(serial.vendorId) == d.vendorId) ||
(d.release & 0xff00) == 0x4200);
}
function getHF2DevicesAsync() {
return Promise.resolve(getHF2Devices());
}
exports.getHF2DevicesAsync = getHF2DevicesAsync;
function handleDevicesFound(devices, selectFn) {
if (devices.length > 1) {
let d42 = devices.filter(d => d.deviceVersionMajor == 42);
if (d42.length > 0)
devices = d42;
}
devices.forEach((device) => {
console.log(`DEV: ${device.productName || device.serialNumber}`);
});
selectFn(devices[0]);
}
function hf2ConnectAsync(path, raw = false) {
if (useWebUSB()) {
const g = global;
if (!g.navigator)
g.navigator = {};
if (!g.navigator.usb) {
const webusb = nodeutil.lazyRequire("webusb", true);
const load = webusb.USBAdapter.prototype.loadDevice;
webusb.USBAdapter.prototype.loadDevice = function (device) {
// skip class 9 - USB HUB, as it causes SEGV on Windows
if (device.deviceDescriptor.bDeviceClass == 9)
return Promise.resolve(null);
return load.apply(this, arguments);
};
const USB = webusb.USB;
g.navigator.usb = new USB({
devicesFound: handleDevicesFound
});
}
return pxt.usb.pairAsync()
.then(() => pxt.usb.mkWebUSBHIDPacketIOAsync())
.then(io => new HF2.Wrapper(io))
.then(d => d.reconnectAsync().then(() => d));
}
if (!isInstalled(true))
return Promise.resolve(undefined);
// in .then() to make sure we catch errors
let h = new HF2.Wrapper(new HidIO(path));
h.rawMode = raw;
return h.reconnectAsync().then(() => h);
}
exports.hf2ConnectAsync = hf2ConnectAsync;
function mkWebUSBOrHidPacketIOAsync() {
if (useWebUSB()) {
pxt.debug(`packetio: mk cli webusb`);
return hf2ConnectAsync("");
}
pxt.debug(`packetio: mk cli hidio`);
return Promise.resolve()
.then(() => {
// in .then() to make sure we catch errors
return new HidIO(null);
});
}
exports.mkWebUSBOrHidPacketIOAsync = mkWebUSBOrHidPacketIOAsync;
pxt.packetio.mkPacketIOAsync = mkWebUSBOrHidPacketIOAsync;
let hf2Dev;
function initAsync(path = null) {
if (!hf2Dev) {
hf2Dev = hf2ConnectAsync(path);
}
return hf2Dev;
}
exports.initAsync = initAsync;
function connectSerial(w) {
process.stdin.on("data", (buf) => {
w.sendSerialAsync(new Uint8Array(buf));
});
w.onSerial = (arr, iserr) => {
let buf = Buffer.from(arr);
if (iserr)
process.stderr.write(buf);
else
process.stdout.write(buf);
};
}
exports.connectSerial = connectSerial;
class HIDError extends Error {
constructor(m) {
super(m);
this.message = m;
}
}
exports.HIDError = HIDError;
class HidIO {
constructor(requestedPath) {
this.requestedPath = requestedPath;
this.connecting = false;
this.onDeviceConnectionChanged = (connect) => { };
this.onConnectionChanged = () => { };
this.onData = (v) => { };
this.onEvent = (v) => { };
this.onError = (e) => { };
this.connect();
}
setConnecting(v) {
if (v != this.connecting) {
this.connecting = v;
if (this.onConnectionChanged)
this.onConnectionChanged();
}
}
connect() {
U.assert(isInstalled(false));
this.setConnecting(true);
try {
if (this.requestedPath == null) {
let devs = getHF2Devices();
if (devs.length == 0)
throw new HIDError("no devices found");
this.path = devs[0].path;
}
else {
this.path = this.requestedPath;
}
this.dev = new HID.HID(this.path);
this.dev.on("data", (v) => {
//console.log("got", v.toString("hex"))
this.onData(new Uint8Array(v));
});
this.dev.on("error", (v) => this.onError(v));
}
finally {
this.setConnecting(false);
}
}
disposeAsync() {
return Promise.resolve();
}
isConnecting() {
return this.connecting;
}
isConnected() {
return !!this.dev;
}
sendPacketAsync(pkt) {
//console.log("SEND: " + Buffer.from(pkt).toString("hex"))
return Promise.resolve()
.then(() => {
let lst = [0];
for (let i = 0; i < Math.max(64, pkt.length); ++i)
lst.push(pkt[i] || 0);
this.dev.write(lst);
});
}
error(msg) {
let fullmsg = "HID error on " + this.path + ": " + msg;
console.error(fullmsg);
throw new HIDError(fullmsg);
}
disconnectAsync() {
if (!this.dev)
return Promise.resolve();
// see https://github.com/node-hid/node-hid/issues/61
this.dev.removeAllListeners("data");
this.dev.removeAllListeners("error");
const pkt = new Uint8Array([0x48]);
this.sendPacketAsync(pkt).catch(e => { });
return U.delay(100)
.then(() => {
if (this.dev) {
const d = this.dev;
delete this.dev;
d.close();
}
if (this.onConnectionChanged)
this.onConnectionChanged();
});
}
reconnectAsync() {
return this.disconnectAsync()
.then(() => {
this.connect();
});
}
}
exports.HidIO = HidIO;