diff --git a/public/esp32s3-robot-addon.js b/public/esp32s3-robot-addon.js new file mode 100644 index 0000000..19f14d3 --- /dev/null +++ b/public/esp32s3-robot-addon.js @@ -0,0 +1,163 @@ +// ESP32-S3 Robot addon +// +// Blocks: move(left, right) — set both motors -255..255 +// distance left — sonar distance in cm (value) +// distance right — sonar distance in cm (value) +// +// ─── CONFIGURE YOUR ROBOT ─────────────────────────────────── +// Edit the pin numbers below to match your wiring, then re-install +// the addon (Addons → Remove "esp32s3-robot-addon" → Install same file again). + +var LEFT_MOTOR_PIN1 = 3; +var LEFT_MOTOR_PIN2 = 4; +var RIGHT_MOTOR_PIN1 = 5; +var RIGHT_MOTOR_PIN2 = 6; +var LEFT_SONAR_TRIG = 9; +var LEFT_SONAR_ECHO = 8; +var RIGHT_SONAR_TRIG = 11; +var RIGHT_SONAR_ECHO = 10; + +// ─── Block definitions ─────────────────────────────────────── + +api.Blockly.Blocks['robot_move'] = { + init() { + this.appendValueInput('LEFT') + .setCheck('Number') + .appendField('move left'); + this.appendValueInput('RIGHT') + .setCheck('Number') + .appendField('right'); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(45); + this.setTooltip('Set left and right motor speed (-255 to 255). DRV8833 on pins 3,4 and 5,6.'); + }, +}; + +api.Blockly.Blocks['robot_distance_left'] = { + init() { + this.appendDummyInput() + .appendField('distance left (cm)'); + this.setOutput(true, 'Number'); + this.setColour(65); + this.setTooltip('Left sonar distance in cm (trig=9, echo=8)'); + }, +}; + +api.Blockly.Blocks['robot_distance_right'] = { + init() { + this.appendDummyInput() + .appendField('distance right (cm)'); + this.setOutput(true, 'Number'); + this.setColour(65); + this.setTooltip('Right sonar distance in cm (trig=11, echo=10)'); + }, +}; + +// --- Code generators --- + +var gen = api.pythonGenerator; + +// Generated Python pin constants (from config at top of this file). +var ROBOT_PINS = [ + '# Robot pin configuration', + 'LEFT_MOTOR_PIN1 = ' + LEFT_MOTOR_PIN1, + 'LEFT_MOTOR_PIN2 = ' + LEFT_MOTOR_PIN2, + 'RIGHT_MOTOR_PIN1 = ' + RIGHT_MOTOR_PIN1, + 'RIGHT_MOTOR_PIN2 = ' + RIGHT_MOTOR_PIN2, + 'LEFT_SONAR_TRIG = ' + LEFT_SONAR_TRIG, + 'LEFT_SONAR_ECHO = ' + LEFT_SONAR_ECHO, + 'RIGHT_SONAR_TRIG = ' + RIGHT_SONAR_TRIG, + 'RIGHT_SONAR_ECHO = ' + RIGHT_SONAR_ECHO, +].join('\n'); + +var ROBOT_MOTOR_DEF = [ + 'from machine import Pin, PWM', + '_robot_left1 = PWM(Pin(LEFT_MOTOR_PIN1), freq=20000, duty=0)', + '_robot_left2 = PWM(Pin(LEFT_MOTOR_PIN2), freq=20000, duty=0)', + '_robot_right1 = PWM(Pin(RIGHT_MOTOR_PIN1), freq=20000, duty=0)', + '_robot_right2 = PWM(Pin(RIGHT_MOTOR_PIN2), freq=20000, duty=0)', + 'def _robot_set_motor(pwm1, pwm2, speed):', + ' speed = max(-255, min(255, int(speed)))', + ' duty = abs(speed) * 4', + ' if speed > 0:', + ' pwm1.duty(duty)', + ' pwm2.duty(0)', + ' elif speed < 0:', + ' pwm1.duty(0)', + ' pwm2.duty(duty)', + ' else:', + ' pwm1.duty(0)', + ' pwm2.duty(0)', +].join('\n'); + +var ROBOT_SONAR_DEF = [ + 'def _robot_sonar_cm(trig, echo):', + ' from machine import Pin, time_pulse_us', + ' import time', + ' t = Pin(trig, Pin.OUT)', + ' e = Pin(echo, Pin.IN)', + ' t.off()', + ' time.sleep_us(2)', + ' t.on()', + ' time.sleep_us(10)', + ' t.off()', + ' d = time_pulse_us(e, 1, 30000)', + ' return round(d / 58.0, 1) if d >= 0 else -1', +].join('\n'); + +function addRobotDefs() { + gen.definitions_['robot_pins'] = ROBOT_PINS; + gen.definitions_['robot_motors'] = ROBOT_MOTOR_DEF; + gen.definitions_['robot_sonar'] = ROBOT_SONAR_DEF; +} + +gen.forBlock['robot_move'] = function (block) { + if (api.getDeviceId() !== 'esp32s3') { + return '# Robot move block is for ESP32-S3 robot (DRV8833 on 3,4,5,6)\n'; + } + addRobotDefs(); + var left = gen.valueToCode(block, 'LEFT', gen.ORDER_NONE) || '0'; + var right = gen.valueToCode(block, 'RIGHT', gen.ORDER_NONE) || '0'; + return '_robot_set_motor(_robot_left1, _robot_left2, ' + left + ')\n' + + '_robot_set_motor(_robot_right1, _robot_right2, ' + right + ')\n'; +}; + +gen.forBlock['robot_distance_left'] = function (block) { + if (api.getDeviceId() !== 'esp32s3') { + return ['-1', gen.ORDER_ATOMIC]; + } + addRobotDefs(); + return ['_robot_sonar_cm(LEFT_SONAR_TRIG, LEFT_SONAR_ECHO)', gen.ORDER_FUNCTION_CALL]; +}; + +gen.forBlock['robot_distance_right'] = function (block) { + if (api.getDeviceId() !== 'esp32s3') { + return ['-1', gen.ORDER_ATOMIC]; + } + addRobotDefs(); + return ['_robot_sonar_cm(RIGHT_SONAR_TRIG, RIGHT_SONAR_ECHO)', gen.ORDER_FUNCTION_CALL]; +}; + +// --- Register toolbox category --- + +api.registerCategories([ + { + kind: 'category', + name: 'ESP32-S3 Robot', + colour: '45', + contents: [ + { + kind: 'block', + type: 'robot_move', + inputs: { + LEFT: { shadow: { type: 'math_number', fields: { NUM: 0 } } }, + RIGHT: { shadow: { type: 'math_number', fields: { NUM: 0 } } }, + }, + }, + { kind: 'block', type: 'robot_distance_left' }, + { kind: 'block', type: 'robot_distance_right' }, + ], + }, +]); diff --git a/src/blocks/categories/hid.js b/src/blocks/categories/hid.js new file mode 100644 index 0000000..2225d14 --- /dev/null +++ b/src/blocks/categories/hid.js @@ -0,0 +1,47 @@ +export const hidCategory = { + kind: 'category', + name: 'HID (Keyboard / Mouse / Gamepad)', + colour: '330', + contents: [ + // Keyboard + { kind: 'block', type: 'hid_key_press' }, + { kind: 'block', type: 'hid_key_down' }, + { kind: 'block', type: 'hid_key_up' }, + { + kind: 'block', + type: 'hid_keyboard_type', + inputs: { + TEXT: { shadow: { type: 'text', fields: { TEXT: 'hello' } } }, + }, + }, + // Mouse + { + kind: 'block', + type: 'hid_mouse_move', + inputs: { + X: { shadow: { type: 'math_number', fields: { NUM: 10 } } }, + Y: { shadow: { type: 'math_number', fields: { NUM: 0 } } }, + }, + }, + { + kind: 'block', + type: 'hid_mouse_click', + }, + { + kind: 'block', + type: 'hid_mouse_scroll', + inputs: { + DELTA: { shadow: { type: 'math_number', fields: { NUM: 1 } } }, + }, + }, + // Gamepad + { kind: 'block', type: 'hid_gamepad_button' }, + { + kind: 'block', + type: 'hid_gamepad_axis', + inputs: { + VALUE: { shadow: { type: 'math_number', fields: { NUM: 0 } } }, + }, + }, + ], +}; diff --git a/src/blocks/esp32_blocks.js b/src/blocks/esp32_blocks.js index 1775232..0d20135 100644 --- a/src/blocks/esp32_blocks.js +++ b/src/blocks/esp32_blocks.js @@ -357,6 +357,158 @@ Blockly.Blocks['i2c_readfrom'] = { }, }; +// ─── HID (Keyboard / Mouse / Gamepad) ───────────────────── +// USB HID key codes (Usage Page 0x07). Value is decimal for generator. +const HID_KEY_OPTIONS = [ + ['A', '4'], ['B', '5'], ['C', '6'], ['D', '7'], ['E', '8'], ['F', '9'], ['G', '10'], + ['H', '11'], ['I', '12'], ['J', '13'], ['K', '14'], ['L', '15'], ['M', '16'], ['N', '17'], + ['O', '18'], ['P', '19'], ['Q', '20'], ['R', '21'], ['S', '22'], ['T', '23'], ['U', '24'], + ['V', '25'], ['W', '26'], ['X', '27'], ['Y', '28'], ['Z', '29'], + ['1', '30'], ['2', '31'], ['3', '32'], ['4', '33'], ['5', '34'], + ['6', '35'], ['7', '36'], ['8', '37'], ['9', '38'], ['0', '39'], + ['Enter', '40'], ['Esc', '41'], ['Backspace', '42'], ['Tab', '43'], ['Space', '44'], + ['-', '45'], ['=', '46'], ['[', '47'], [']', '48'], ['\\', '49'], [';', '51'], + ["'", '52'], ['`', '53'], [',', '54'], ['.', '55'], ['/', '56'], + ['F1', '58'], ['F2', '59'], ['F3', '60'], ['F4', '61'], ['F5', '62'], + ['F6', '63'], ['F7', '64'], ['F8', '65'], ['F9', '66'], ['F10', '67'], ['F11', '68'], ['F12', '69'], +]; + +Blockly.Blocks['hid_key_press'] = { + init() { + this.appendDummyInput() + .appendField('HID key press') + .appendField(new Blockly.FieldDropdown(HID_KEY_OPTIONS), 'KEY'); + this.appendDummyInput() + .appendField('modifier') + .appendField(new Blockly.FieldDropdown([ + ['none', '0'], ['Ctrl', '1'], ['Shift', '2'], ['Alt', '4'], ['GUI/Win', '8'], + ['Ctrl+Shift', '3'], ['Ctrl+Alt', '5'], ['Shift+Alt', '6'], + ]), 'MOD'); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip('Press and release a key (keyboard HID)'); + }, +}; + +Blockly.Blocks['hid_key_down'] = { + init() { + this.appendDummyInput() + .appendField('HID key down') + .appendField(new Blockly.FieldDropdown(HID_KEY_OPTIONS), 'KEY'); + this.appendDummyInput() + .appendField('modifier') + .appendField(new Blockly.FieldDropdown([ + ['none', '0'], ['Ctrl', '1'], ['Shift', '2'], ['Alt', '4'], ['GUI/Win', '8'], + ]), 'MOD'); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip('Hold key down (keyboard HID)'); + }, +}; + +Blockly.Blocks['hid_key_up'] = { + init() { + this.appendDummyInput().appendField('HID key up (release all)'); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip('Release all keyboard keys'); + }, +}; + +Blockly.Blocks['hid_keyboard_type'] = { + init() { + this.appendValueInput('TEXT') + .setCheck('String') + .appendField('HID type text'); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip('Type a string as keyboard input (HID)'); + }, +}; + +Blockly.Blocks['hid_mouse_move'] = { + init() { + this.appendValueInput('X').setCheck('Number').appendField('HID mouse move X'); + this.appendValueInput('Y').setCheck('Number').appendField('Y'); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip('Move mouse relative (pixels)'); + }, +}; + +Blockly.Blocks['hid_mouse_click'] = { + init() { + this.appendDummyInput() + .appendField('HID mouse click') + .appendField(new Blockly.FieldDropdown([ + ['left', '1'], ['right', '2'], ['middle', '4'], + ]), 'BUTTON'); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip('Click mouse button (press and release)'); + }, +}; + +Blockly.Blocks['hid_mouse_scroll'] = { + init() { + this.appendValueInput('DELTA') + .setCheck('Number') + .appendField('HID mouse scroll'); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip('Scroll wheel (positive = up, negative = down)'); + }, +}; + +Blockly.Blocks['hid_gamepad_button'] = { + init() { + this.appendDummyInput() + .appendField('HID gamepad button') + .appendField(new Blockly.FieldDropdown([ + ['1', '0'], ['2', '1'], ['3', '2'], ['4', '3'], ['5', '4'], ['6', '5'], + ['7', '6'], ['8', '7'], ['9', '8'], ['10', '9'], ['11', '10'], ['12', '11'], + ['13', '12'], ['14', '13'], ['15', '14'], ['16', '15'], + ]), 'BTN'); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown([ + ['press', '1'], + ['release', '0'], + ]), 'STATE'); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip('Set gamepad button state (1-16)'); + }, +}; + +Blockly.Blocks['hid_gamepad_axis'] = { + init() { + this.appendDummyInput() + .appendField('HID gamepad axis') + .appendField(new Blockly.FieldDropdown([ + ['left X', '0'], ['left Y', '1'], ['right X', '2'], ['right Y', '3'], + ['left trigger', '4'], ['right trigger', '5'], + ]), 'AXIS'); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField('value'); + this.appendDummyInput().appendField('(-127 to 127)'); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip('Set gamepad axis (-127 to 127, 0 = center)'); + }, +}; + // ─── Print ──────────────────────────────────────────────── Blockly.Blocks['print_text'] = { diff --git a/src/blocks/esp32_generators.js b/src/blocks/esp32_generators.js index e8bf546..7b94857 100644 --- a/src/blocks/esp32_generators.js +++ b/src/blocks/esp32_generators.js @@ -452,3 +452,168 @@ pythonGenerator.forBlock['superbit_motor_stop_all'] = function (block) { pythonGenerator.definitions_['superbit_lib'] = SUPERBIT_DEF; return '_superbit_motor_stop_all()\n'; }; + +// ─── HID (Keyboard / Mouse / Gamepad) ────────────────────── +// ESP32-S3 only; requires MicroPython firmware with USB HID support (e.g. TinyUSB). + +const HID_HELPER = `# HID helper (keyboard, mouse, gamepad) - ESP32-S3 USB +try: + import usb_hid +except ImportError: + usb_hid = None +_hid_kb = None +_hid_mouse = None +_hid_gamepad = None +def _hid_devices(): + global _hid_kb, _hid_mouse, _hid_gamepad + if usb_hid is None or _hid_kb is not None: + return + for d in usb_hid.devices: + if getattr(d, 'usage_page', None) == 0x01 and getattr(d, 'usage', None) == 0x06: + _hid_kb = d + elif getattr(d, 'usage_page', None) == 0x01 and getattr(d, 'usage', None) == 0x02: + _hid_mouse = d + elif getattr(d, 'usage_page', None) == 0x01 and getattr(d, 'usage', None) == 0x05: + _hid_gamepad = d +def _hid_keyboard_report(modifier, key): + return bytes([int(modifier) & 0xff, 0, int(key) & 0xff, 0, 0, 0, 0, 0]) +def _hid_key_press(modifier, key): + _hid_devices() + if _hid_kb: + _hid_kb.send(_hid_keyboard_report(modifier, key)) + _hid_kb.send(_hid_keyboard_report(0, 0)) +def _hid_key_down(modifier, key): + _hid_devices() + if _hid_kb: + _hid_kb.send(_hid_keyboard_report(modifier, key)) +def _hid_key_up(): + _hid_devices() + if _hid_kb: + _hid_kb.send(_hid_keyboard_report(0, 0)) +def _hid_type_string(s): + _hid_devices() + if not _hid_kb: + return + for c in s: + mod = 0x02 if c.isupper() and c.isalpha() else 0 + low = c.lower() if c.isalpha() else c + key = 0 + if len(low) == 1 and 'a' <= low <= 'z': + key = ord(low) - ord('a') + 4 + elif '0' <= c <= '9': + key = ord(c) - ord('0') + 30 + elif c == ' ': + key = 44 + elif c == chr(10) or c == chr(13): + key = 40 + if key: + _hid_kb.send(_hid_keyboard_report(mod, key)) + _hid_kb.send(_hid_keyboard_report(0, 0)) +def _s8(v): + return (max(-127, min(127, int(v))) + 128) & 0xff +def _hid_mouse_move(dx, dy): + _hid_devices() + if _hid_mouse: + _hid_mouse.send(bytes([0, _s8(dx), _s8(dy), 0])) +def _hid_mouse_click(btn): + _hid_devices() + if _hid_mouse: + _hid_mouse.send(bytes([int(btn) & 7, 0, 0, 0])) + _hid_mouse.send(bytes([0, 0, 0, 0])) +def _hid_mouse_scroll(delta): + _hid_devices() + if _hid_mouse: + _hid_mouse.send(bytes([0, 0, 0, _s8(delta)])) +def _hid_gamepad_report(buttons, axes): + out = [buttons & 0xff, buttons >> 8] + for v in axes: + out.append((max(-127, min(127, int(v))) + 128) & 0xff) + return bytes(out) +_hid_gp_buttons = 0 +_hid_gp_axes = [0, 0, 0, 0, 0, 0] +def _hid_gamepad_button(btn_index, state): + global _hid_gp_buttons + _hid_devices() + if _hid_gamepad: + if state: + _hid_gp_buttons |= 1 << btn_index + else: + _hid_gp_buttons &= ~(1 << btn_index) + _hid_gamepad.send(_hid_gamepad_report(_hid_gp_buttons, _hid_gp_axes)) +def _hid_gamepad_axis(axis_index, value): + _hid_devices() + if _hid_gamepad: + _hid_gp_axes[axis_index] = max(-127, min(127, int(value))) + _hid_gamepad.send(_hid_gamepad_report(_hid_gp_buttons, _hid_gp_axes)) +`; + +function hidNotSupported() { + return DEVICE() !== 'esp32s3'; +} + +pythonGenerator.forBlock['hid_key_press'] = function (block) { + if (hidNotSupported()) return '# HID blocks require ESP32-S3 with USB HID firmware\n'; + pythonGenerator.definitions_['hid_lib'] = HID_HELPER; + const key = block.getFieldValue('KEY'); + const mod = block.getFieldValue('MOD'); + return `_hid_key_press(${mod}, ${key})\n`; +}; + +pythonGenerator.forBlock['hid_key_down'] = function (block) { + if (hidNotSupported()) return '# HID blocks require ESP32-S3 with USB HID firmware\n'; + pythonGenerator.definitions_['hid_lib'] = HID_HELPER; + const key = block.getFieldValue('KEY'); + const mod = block.getFieldValue('MOD'); + return `_hid_key_down(${mod}, ${key})\n`; +}; + +pythonGenerator.forBlock['hid_key_up'] = function (block) { + if (hidNotSupported()) return '# HID blocks require ESP32-S3 with USB HID firmware\n'; + pythonGenerator.definitions_['hid_lib'] = HID_HELPER; + return '_hid_key_up()\n'; +}; + +pythonGenerator.forBlock['hid_keyboard_type'] = function (block) { + if (hidNotSupported()) return '# HID blocks require ESP32-S3 with USB HID firmware\n'; + pythonGenerator.definitions_['hid_lib'] = HID_HELPER; + const text = pythonGenerator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + return `_hid_type_string(${text})\n`; +}; + +pythonGenerator.forBlock['hid_mouse_move'] = function (block) { + if (hidNotSupported()) return '# HID blocks require ESP32-S3 with USB HID firmware\n'; + pythonGenerator.definitions_['hid_lib'] = HID_HELPER; + const x = pythonGenerator.valueToCode(block, 'X', Order.NONE) || '0'; + const y = pythonGenerator.valueToCode(block, 'Y', Order.NONE) || '0'; + return `_hid_mouse_move(${x}, ${y})\n`; +}; + +pythonGenerator.forBlock['hid_mouse_click'] = function (block) { + if (hidNotSupported()) return '# HID blocks require ESP32-S3 with USB HID firmware\n'; + pythonGenerator.definitions_['hid_lib'] = HID_HELPER; + const btn = block.getFieldValue('BUTTON'); + return `_hid_mouse_click(${btn})\n`; +}; + +pythonGenerator.forBlock['hid_mouse_scroll'] = function (block) { + if (hidNotSupported()) return '# HID blocks require ESP32-S3 with USB HID firmware\n'; + pythonGenerator.definitions_['hid_lib'] = HID_HELPER; + const delta = pythonGenerator.valueToCode(block, 'DELTA', Order.NONE) || '1'; + return `_hid_mouse_scroll(${delta})\n`; +}; + +pythonGenerator.forBlock['hid_gamepad_button'] = function (block) { + if (hidNotSupported()) return '# HID blocks require ESP32-S3 with USB HID firmware\n'; + pythonGenerator.definitions_['hid_lib'] = HID_HELPER; + const btn = block.getFieldValue('BTN'); + const state = block.getFieldValue('STATE') === '1'; + return `_hid_gamepad_button(${btn}, ${state})\n`; +}; + +pythonGenerator.forBlock['hid_gamepad_axis'] = function (block) { + if (hidNotSupported()) return '# HID blocks require ESP32-S3 with USB HID firmware\n'; + pythonGenerator.definitions_['hid_lib'] = HID_HELPER; + const axis = block.getFieldValue('AXIS'); + const value = pythonGenerator.valueToCode(block, 'VALUE', Order.NONE) || '0'; + return `_hid_gamepad_axis(${axis}, ${value})\n`; +}; diff --git a/src/devices/esp32s3.js b/src/devices/esp32s3.js index a49d29c..668e72e 100644 --- a/src/devices/esp32s3.js +++ b/src/devices/esp32s3.js @@ -9,6 +9,7 @@ import { i2cCategory } from '../blocks/categories/i2c.js'; import { serialPrintCategory } from '../blocks/categories/serialPrint.js'; import { soundCategory } from '../blocks/categories/sound.js'; import { randomCategory } from '../blocks/categories/random.js'; +import { hidCategory } from '../blocks/categories/hid.js'; export const esp32s3 = { id: 'esp32s3', @@ -28,6 +29,7 @@ export const esp32s3 = { wifiCategory, neopixelCategory, i2cCategory, + hidCategory, soundCategory(), serialPrintCategory, randomCategory, diff --git a/vite.config.js b/vite.config.js index 4396fc4..21cebcc 100644 --- a/vite.config.js +++ b/vite.config.js @@ -8,4 +8,22 @@ export default defineConfig({ build: { outDir: 'dist', }, + optimizeDeps: { + // esptool-js uses dynamic import() for chip targets (e.g. esp32s3.js); include them + // so Vite pre-bundles them and the runtime fetch resolves. + include: [ + 'esptool-js', + 'esptool-js/lib/targets/esp32.js', + 'esptool-js/lib/targets/esp32c2.js', + 'esptool-js/lib/targets/esp32c3.js', + 'esptool-js/lib/targets/esp32c5.js', + 'esptool-js/lib/targets/esp32c6.js', + 'esptool-js/lib/targets/esp32c61.js', + 'esptool-js/lib/targets/esp32h2.js', + 'esptool-js/lib/targets/esp32p4.js', + 'esptool-js/lib/targets/esp32s2.js', + 'esptool-js/lib/targets/esp32s3.js', + 'esptool-js/lib/targets/esp8266.js', + ], + }, });