UNPKG

pxt-core

Version:

Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors

262 lines (261 loc) • 8.19 kB
"use strict"; 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;