esptool-js

repository·main·Indexed 19 days ago

https://github.com/espressif/esptool-js

A JavaScript implementation of the esptool utility for flashing firmware to Espressif chips via the Web Serial API in modern web browsers. Version 0.6.0 provides capabilities to connect to ESP devices, detect chip types, read/write/erase flash memory, and implement custom reset strategies. It supports TypeScript environments and can be installed via NPM, Yarn, or CDN.

Tokens
13.6K
Snippets
57
Records
61
Agent score
67%

What's inside esptool-js

  1. How custom reset strategies work

    main

    Reset strategies control the DTR (Data Terminal Ready) and RTS (Request To Send) signals to trigger a chip reset.

    Command Syntax: Commands are a pipe-separated string of actions:

    • D<value>: Set DTR to value (0=False, 1=True).
    • R<value>: Set RTS to value (0=False, 1=True).
    • W<ms>: Wait for <ms> milliseconds.

    Example: "D0|R1|W100|D1|R0|W50|D0" sets DTR low, RTS high, waits 100ms, sets DTR high, RTS low, waits 50ms, then sets DTR low.

  2. Install esptool-js

    main

    You can install esptool-js via NPM or Yarn, or use it directly from a CDN.

    NPM

    npm install --save esptool-js

    Yarn

    yarn add --save esptool-js

    CDN Use https://unpkg.com/esptool-js/lib/index.js or the single bundle https://unpkg.com/esptool-js/bundle.js.

  3. Test the TypeScript example locally

    main

    To run the TypeScript example on your local machine for development and testing, use the following commands. This will start a local development server using Parcel, serving the compiled files at http://localhost:1234. Note that Web Serial features require a browser like Chrome or Edge.

    npm install
    npm run dev
  4. Use Esptool-JS in a TypeScript environment

    main

    To use esptool-js in a TypeScript project, you can follow the pattern demonstrated in the TypeScript example, which implements basic usage within a static HTML/JS website using the Web Serial API. The core logic is typically contained in a TypeScript file (e.g., src/index.ts) which is then bundled and referenced by an HTML file (e.g., index.html). This example uses Parcel as the bundler to simplify the build process.

    // The main logic resides in a TypeScript file like src/index.ts
    // which is then bundled and called by index.html
  5. Define a custom reset sequence string

    main

    The CustomReset strategy allows you to provide a custom sequence of commands as a string. Commands are separated by the pipe character (|).

    Command Syntax:

    • D<arg>: Set DTR state. Argument 1 for true, 0 for false.
    • R<arg>: Set RTS state. Argument 1 for true, 0 for false.
    • W<arg>: Wait (delay) for a specified number of milliseconds.

    Example Sequence: "D0|R1|W100|D1|R0|W50|D0" represents: Set DTR false, set RTS true, wait 100ms, set DTR true, set RTS false, wait 50ms, set DTR false.

    import { CustomReset, validateCustomResetStringSequence } from './reset.js';
    
    const sequence = "D0|R1|W100|D1|R0|W50|D0";
    
    // Validate before use
    if (validateCustomResetStringSequence(sequence)) {
      const strategy = new CustomReset(transport, sequence);
      await strategy.reset();
    }
  6. Implement chip reset strategies

    main

    To reset an ESP chip, you can use different ResetStrategy implementations depending on your hardware connection. Each strategy implements a reset() method that performs a sequence of serial control operations (DTR, RTS, and delays) via a Transport instance.

    Available strategies include:

    • ClassicReset: A standard sequence of DTR/RTS toggles and delays.
    • UsbJtagSerialReset: Specifically for USB JTAG serial connections.
    • HardReset: A simplified reset, with an optional usingUsbOtg flag for USB-OTG hardware.
    • CustomReset: Allows you to define a specific sequence using a command string.
    import { ClassicReset, HardReset, UsbJtagSerialReset, CustomReset } from './reset.js';
    
    // Example: Using ClassicReset
    const strategy = new ClassicReset(transport, 100);
    await strategy.reset();
    
    // Example: Using HardReset
    const hardReset = new HardReset(transport, true);
    await hardReset.reset();
  7. Complete Flash Firmware Workflow Example

    main

    This example demonstrates the full lifecycle: requesting a port, creating the transport/loader, connecting, reading a file, flashing, resetting, and disconnecting.

    import {
      ESPLoader,
      Transport,
      LoaderOptions,
      FlashOptions,
      FlashModeValues,
      FlashFreqValues,
      FlashSizeValues,
    } from "esptool-js";
    
    async function flashFirmware() {
      try {
        // 1. Request serial port
        const port = await navigator.serial.requestPort();
        
        // 2. Create transport and loader
        const transport = new Transport(port, true);
        const esploader = new ESPLoader({
          transport,
          baudrate: 115200,
          terminal: {
            clean: () => console.clear(),
            writeLine: (data) => console.log(data),
            write: (data) => console.log(data),
          },
        });
        
        // 3. Connect to device
        const chipName = await esploader.main();
        console.log(`Connected to: ${chipName}`);
        
        // 4. Load firmware (example: from a file input)
        const fileInput = document.getElementById("firmwareFile") as HTMLInputElement;
        const file = fileInput.files[0];
        const firmwareData = new Uint8Array(await file.arrayBuffer());
        
        // 5. Flash firmware
        const flashOptions: FlashOptions = {
          fileArray: [{ data: firmwareData, address: 0x1000 }],
          flashMode: "dio" as FlashModeValues,
          flashFreq: "40m" as FlashFreqValues,
          flashSize: "4MB" as FlashSizeValues,
          eraseAll: false,
          compress: true,
          reportProgress: (fileIndex, written, total) => {
            console.log(`Progress: ${((written / total) * 100).toFixed(1)}%`);
          },
        };
        
        await esploader.writeFlash(flashOptions);
        console.log("Firmware flashed successfully!");
        
        // 6. Reset device
        await esploader.after("hard_reset");
        
        // 7. Disconnect
        await transport.disconnect();
      } catch (error) {
        console.error("Error:", error);
      }
    }
  8. Erase flash memory

    main

    You can erase the entire flash memory using esploader.eraseFlash(). If supported by the hardware, you can also erase specific regions using esploader.eraseRegion(startAddress, size).

    // Erase entire flash
    await esploader.eraseFlash();
  9. Request Serial Port Access

    main

    Use the Web Serial API to request access to a serial port. You can optionally provide filters to narrow down the selection to specific USB vendor and product IDs.

    Note: This requires a browser that supports the Web Serial API (e.g., Chrome or Edge version 89+).

    // Request port access (user will be prompted to select a device)
    const port = await navigator.serial.requestPort();
    
    // Optionally, filter by USB vendor/product IDs
    const portFilters = [
      { usbVendorId: 0x10c4, usbProductId: 0xea60 } // Example: Silicon Labs CP210x
    ];
    const port = await navigator.serial.requestPort({ filters: portFilters });
  10. Connect to an ESP device

    main

    Call esploader.main() to connect to the device and detect the chip. This operation will trigger a device reset to enter bootloader mode.

    try {
      // Connect and detect chip (this will reset the device)
      const chipName = await esploader.main();
      console.log(`Connected to: ${chipName}`);
    } catch (error) {
      console.error("Failed to connect:", error);
    }
  11. Flash firmware to an ESP device

    main

    Use esploader.writeFlash(flashOptions) to write binary data to the flash memory.

    FlashOptions Configuration:

    • fileArray: Array of { data: Uint8Array, address: number } objects.
    • flashMode: One of "qio", "qout", "dio", or "dout" (FlashModeValues).
    • flashFreq: One of "80m", "40m", "26m", etc. (FlashFreqValues).
    • flashSize: One of "256KB", "512KB", "1MB", "2MB", "4MB", etc. (FlashSizeValues).
    • eraseAll: Boolean. If true, erases the entire flash before writing.
    • compress: Boolean. Whether to compress data during transfer.
    • reportProgress: Callback function (fileIndex, written, total) => void.
    • calculateMD5Hash: Optional callback (image: Uint8Array) => string for verification.
    const firmwareData = new Uint8Array(/* your firmware binary data */);
    const firmwareAddress = 0x1000;
    
    const flashOptions: FlashOptions = {
      fileArray: [
        { data: firmwareData, address: firmwareAddress }
      ],
      flashMode: "dio" as FlashModeValues,
      flashFreq: "40m" as FlashFreqValues,
      flashSize: "4MB" as FlashSizeValues,
      eraseAll: false,
      compress: true,
      reportProgress: (fileIndex, written, total) => {
        const percent = (written / total) * 100;
        console.log(`Progress: ${percent.toFixed(1)}%`);
      },
      calculateMD5Hash: (image: Uint8Array) => {
        return "your-md5-hash";
      },
    };
    
    await esploader.writeFlash(flashOptions);
    
    // Optional: Reset the device after flashing
    await esploader.after("hard_reset");