├── .nojekyll ├── .eslintignore ├── .prettierrc ├── examples └── typescript │ ├── .gitignore │ ├── assets │ ├── favicon.ico │ └── esp-logo.png │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ ├── index.html │ └── index.ts ├── .gitignore ├── src ├── error.ts ├── index.ts ├── targets │ ├── stub_flasher │ │ ├── README.md │ │ ├── stub_flasher_32.json │ │ ├── stub_flasher_32c6.json │ │ ├── stub_flasher_32h2.json │ │ ├── stub_flasher_32c3.json │ │ ├── stub_flasher_32s2.json │ │ ├── stub_flasher_32s3.json │ │ └── stub_flasher_8266.json │ ├── rom.ts │ ├── esp32s3.ts │ ├── esp32h2.ts │ ├── esp32c3.ts │ ├── esp32c6.ts │ ├── esp32s2.ts │ ├── esp8266.ts │ └── esp32.ts ├── reset.ts ├── webserial.ts └── esploader.ts ├── .devcontainer └── devcontainer.json ├── .eslintrc ├── tsconfig.json ├── .vscode └── tasks.json ├── .github └── workflows │ ├── new_issues.yml │ ├── issue_comment.yml │ ├── new_prs.yml │ ├── ci.yml │ ├── pages.yml │ └── publish.yml ├── rollup.config.js ├── package.json ├── README.md └── LICENSE /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /examples/typescript/.gitignore: -------------------------------------------------------------------------------- 1 | node/modules 2 | .parcel-cache 3 | dist 4 | package-lock.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | bundle.js 4 | esptool-js-*.tgz 5 | .vscode/settings.json 6 | .parcel-cache 7 | -------------------------------------------------------------------------------- /examples/typescript/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3gg0/esptool-js/main/examples/typescript/assets/favicon.ico -------------------------------------------------------------------------------- /examples/typescript/assets/esp-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3gg0/esptool-js/main/examples/typescript/assets/esp-logo.png -------------------------------------------------------------------------------- /src/error.ts: -------------------------------------------------------------------------------- 1 | class ESPError extends Error {} 2 | 3 | class TimeoutError extends ESPError {} 4 | 5 | export { ESPError, TimeoutError }; 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { IEspLoaderTerminal, ESPLoader, FlashOptions, LoaderOptions } from "./esploader"; 2 | export { Transport } from "./webserial"; 3 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esptool.js", 3 | "image": "mcr.microsoft.com/devcontainers/typescript-node:0-18-bullseye", 4 | 5 | "features": { 6 | "ghcr.io/devcontainers-contrib/features/npm-package:1": { 7 | "package": "http-server" 8 | } 9 | }, 10 | "forwardPorts": [ 5001 ], 11 | "customizations": { 12 | "vscode": { 13 | "settings": { 14 | "task.allowAutomaticTasks": "on" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint", 6 | "prettier" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "prettier" 13 | ], 14 | "rules": { 15 | "no-console": 1, // Means warning 16 | "prettier/prettier": 2 // Means error 17 | } 18 | } -------------------------------------------------------------------------------- /examples/typescript/README.md: -------------------------------------------------------------------------------- 1 | # Using Esptool-JS in a Typescript environment 2 | 3 | This example has example code in `src/index.ts` which is called in the `index.html`. We are using Parcel to do bundle mechanism for the resulting JavaScript for simplicity here. 4 | 5 | ## Testing it locally 6 | 7 | ``` 8 | npm install 9 | npm run dev 10 | ``` 11 | 12 | Then open http://localhost:1234 in Chrome or Edge. The `npm run dev` step will call Parcel which start a local http server serving `index.html` with compiled `index.ts`. -------------------------------------------------------------------------------- /examples/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES2020", 4 | "target": "ES2019", 5 | "declaration": false, 6 | "allowSyntheticDefaultImports": true, 7 | "moduleResolution": "node", 8 | "noUnusedLocals": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "noImplicitReturns": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "lib": ["ES2020", "DOM"], 13 | "importHelpers": true, 14 | "resolveJsonModule": true 15 | }, 16 | "include": ["src/**/*"] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ES2020", 4 | "target": "ES2019", 5 | "declaration": true, 6 | "allowSyntheticDefaultImports": true, 7 | "outDir": "./lib", 8 | "moduleResolution": "node", 9 | "noUnusedLocals": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitReturns": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "lib": ["ES2020", "DOM"], 14 | "importHelpers": true, 15 | "resolveJsonModule": true, 16 | "strict": true 17 | }, 18 | "include": ["src/**/*"] 19 | } 20 | -------------------------------------------------------------------------------- /examples/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "version": "1.0.0", 4 | "description": "This an example of using esptool-js with parcel and typescript", 5 | "source": "src/index.html", 6 | "scripts": { 7 | "dev": "parcel src/index.html", 8 | "build": "npm run clean && parcel build src/index.html --no-optimize --public-url ./", 9 | "clean": "rimraf dist .parcel-cache", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "parcel": "^2.8.3", 16 | "rimraf": "^4.1.2", 17 | "typescript": "^4.9.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/targets/stub_flasher/README.md: -------------------------------------------------------------------------------- 1 | # Flasher stub binaries 2 | 3 | Included here are the flasher stub binaries from [esptool](https://github.com/espressif/esptool) (.py) project. 4 | 5 | The binaries were copied from [v4.6.1 release of esptool](https://github.com/espressif/esptool/tree/v4.6.1/esptool/targets/stub_flasher). 6 | 7 | The source code used to produce these binaries can be found [here](https://github.com/espressif/esptool/tree/v4.6.1/flasher_stub). 8 | 9 | The stub is Copyright 2016 Cesanta Software Limited, 2016-2022 Espressif Systems (Shanghai) Co. Ltd. and is licensed under [GNU General Public License v2.0 or later](https://spdx.org/licenses/GPL-2.0-or-later.html) license. 10 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "build", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | }, 11 | "problemMatcher": [], 12 | "label": "npm: build", 13 | "detail": "npm run clean && tsc && rollup --config" 14 | }, 15 | { 16 | "type": "npm", 17 | "script": "install", 18 | "label": "NPM install", 19 | "runOptions": { 20 | "runOn": "folderOpen" 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "lint", 26 | "problemMatcher": ["$eslint-stylish"], 27 | "label": "npm: lint" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/new_issues.yml: -------------------------------------------------------------------------------- 1 | name: Sync issues to Jira 2 | 3 | # This workflow will be triggered when a new issue is opened 4 | on: issues 5 | 6 | jobs: 7 | sync_issues_to_jira: 8 | name: Sync issues to Jira 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: Sync GitHub issues to Jira project 13 | uses: espressif/github-actions/sync_issues_to_jira@master 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | JIRA_PASS: ${{ secrets.JIRA_PASS }} 17 | JIRA_PROJECT: ESPTOOL 18 | JIRA_COMPONENT: ${{ secrets.JIRA_COMPONENT }} 19 | JIRA_URL: ${{ secrets.JIRA_URL }} 20 | JIRA_USER: ${{ secrets.JIRA_USER }} 21 | -------------------------------------------------------------------------------- /.github/workflows/issue_comment.yml: -------------------------------------------------------------------------------- 1 | name: Sync issue comments to JIRA 2 | 3 | # This workflow will be triggered when new issue comment is created (including PR comments) 4 | on: issue_comment 5 | 6 | jobs: 7 | sync_issue_comments_to_jira: 8 | name: Sync Issue Comments to Jira 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: Sync issue comments to JIRA 13 | uses: espressif/github-actions/sync_issues_to_jira@master 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | JIRA_PASS: ${{ secrets.JIRA_PASS }} 17 | JIRA_PROJECT: ESPTOOL 18 | JIRA_COMPONENT: GitHub 19 | JIRA_URL: ${{ secrets.JIRA_URL }} 20 | JIRA_USER: ${{ secrets.JIRA_USER }} 21 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const resolve = require('@rollup/plugin-node-resolve'); 2 | const babel = require("@rollup/plugin-babel"); 3 | const commonjs = require('@rollup/plugin-commonjs'); 4 | const terser = require('@rollup/plugin-terser'); 5 | const json = require('@rollup/plugin-json'); 6 | 7 | // rollup.config.js 8 | /** 9 | * @type {import('rollup').RollupOptions} 10 | */ 11 | const config = { 12 | input: 'lib/index.js', 13 | output: { 14 | name: "esptooljs", 15 | file: 'bundle.js', 16 | format: 'es', 17 | inlineDynamicImports: true 18 | }, 19 | plugins: [ 20 | resolve(), 21 | commonjs(), 22 | babel({ exclude: 'node_modules/**', babelHelpers: "runtime", skipPreflightCheck: true }), 23 | json({ namedExports: false, preferConst: true }), 24 | terser() 25 | ], 26 | }; 27 | 28 | module.exports.default = config; 29 | -------------------------------------------------------------------------------- /.github/workflows/new_prs.yml: -------------------------------------------------------------------------------- 1 | name: Sync remain PRs to Jira 2 | 3 | # This workflow will be triggered every hour, to sync remaining PRs (i.e. PRs with zero comment) to Jira project 4 | # Note that, PRs can also get synced when new PR comment is created 5 | on: 6 | schedule: 7 | - cron: "0 * * * *" 8 | 9 | jobs: 10 | sync_prs_to_jira: 11 | name: Sync PRs to Jira 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: Sync PRs to Jira project 16 | uses: espressif/github-actions/sync_issues_to_jira@master 17 | with: 18 | cron_job: true 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | JIRA_PASS: ${{ secrets.JIRA_PASS }} 22 | JIRA_PROJECT: ESPTOOL 23 | JIRA_COMPONENT: ${{ secrets.JIRA_COMPONENT }} 24 | JIRA_URL: ${{ secrets.JIRA_URL }} 25 | JIRA_USER: ${{ secrets.JIRA_USER }} 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | ci: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 16 17 | registry-url: 'https://registry.npmjs.org' 18 | - name: Install dependencies and build 🔧 19 | run: npm ci && npm run build 20 | - name: Run lint 21 | run: npm run lint 22 | - name: Run tests 23 | run: npm run test 24 | - name: Package module 25 | run: npm pack 26 | - name: Determine version 27 | id: version 28 | run: | 29 | ESPTOOLJS_VERSION=$(node -p "require('./package.json').version") 30 | echo $ESPTOOLJS_VERSION 31 | echo "version=$ESPTOOLJS_VERSION" >> $GITHUB_OUTPUT 32 | - name: Upload npm package file 33 | uses: actions/upload-artifact@v1 34 | with: 35 | name: esptool-js-${{ steps.version.outputs.version }}.tgz 36 | path: esptool-js-${{ steps.version.outputs.version }}.tgz -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy static content to Pages 2 | 3 | # Run on the main branch or on a manual trigger 4 | on: 5 | push: 6 | branches: ["main"] 7 | workflow_dispatch: 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow one concurrent deployment 16 | concurrency: 17 | group: "pages" 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | deploy: 22 | environment: 23 | name: github-pages 24 | url: ${{ steps.deployment.outputs.page_url }} 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v3 29 | - name: Set up Node 30 | uses: actions/setup-node@v3 31 | with: 32 | node-version: 18 33 | - name: Install Node modules 34 | run: | 35 | npm install 36 | - name: Build project 37 | run: | 38 | npm run build 39 | cd examples/typescript 40 | npm install 41 | npm run build 42 | - name: Setup Pages 43 | uses: actions/configure-pages@v2 44 | - name: Upload artifact 45 | uses: actions/upload-pages-artifact@v1 46 | with: 47 | path: 'examples/typescript/dist' 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v1 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esptool-js", 3 | "version": "0.3.0", 4 | "module": "lib/index.js", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "license": "Apache-2.0", 8 | "files": [ 9 | "lib", 10 | "bundle.js" 11 | ], 12 | "scripts": { 13 | "build": "npm run clean && tsc && rollup --config", 14 | "clean": "rimraf lib bundle.js", 15 | "format": "prettier --write \"src/**/*.ts\"", 16 | "lint": "eslint . --ext .ts", 17 | "lintAndFix": "eslint . --ext .ts --fix", 18 | "prepare": "npm run build", 19 | "test": "echo \"Error: no test specified\"", 20 | "prepublishOnly": "npm run test && npm run lint" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/espressif/esptool-js.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/espressif/esptool-js/issues" 28 | }, 29 | "dependencies": { 30 | "pako": "^2.1.0", 31 | "tslib": "^2.4.1" 32 | }, 33 | "devDependencies": { 34 | "@rollup/plugin-babel": "^6.0.2", 35 | "@rollup/plugin-commonjs": "^23.0.2", 36 | "@rollup/plugin-json": "^6.0.0", 37 | "@rollup/plugin-node-resolve": "^15.0.1", 38 | "@rollup/plugin-terser": "^0.1.0", 39 | "@types/pako": "^2.0.0", 40 | "@types/w3c-web-serial": "^1.0.3", 41 | "@typescript-eslint/eslint-plugin": "^5.43.0", 42 | "@typescript-eslint/parser": "^5.43.0", 43 | "babel-loader": "^9.1.0", 44 | "eslint": "^8.28.0", 45 | "eslint-config-prettier": "^8.5.0", 46 | "eslint-plugin-prettier": "^4.2.1", 47 | "prettier": "^2.7.1", 48 | "rimraf": "^3.0.2", 49 | "rollup": "^3.3.0", 50 | "typescript": "^4.8.4" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/targets/rom.ts: -------------------------------------------------------------------------------- 1 | import { ESPLoader } from "../esploader"; 2 | 3 | export abstract class ROM { 4 | // abstract read_efuse(loader: ESPLoader, offset: number): Promise; //esp32 5 | 6 | // abstract get_pkg_version(loader: ESPLoader): Promise; // not in esp32s3 7 | 8 | // abstract get_chip_revision(loader: ESPLoader): Promise; esp32 9 | 10 | abstract get_chip_description(loader: ESPLoader): Promise; 11 | 12 | abstract get_chip_features(loader: ESPLoader): Promise; 13 | 14 | abstract get_crystal_freq(loader: ESPLoader): Promise; 15 | 16 | abstract _d2h(d: number): string; 17 | 18 | abstract read_mac(loader: ESPLoader): Promise; 19 | 20 | _post_connect?(loader: ESPLoader): Promise; 21 | 22 | get_erase_size(offset: number, size: number) { 23 | return size; 24 | } 25 | 26 | abstract FLASH_SIZES: { [key: string]: number }; 27 | 28 | abstract BOOTLOADER_FLASH_OFFSET: number; 29 | abstract CHIP_NAME: string; 30 | abstract DATA_START: number; 31 | // abstract DR_REG_SYSCON_BASE: number; //esp32 32 | // abstract EFUSE_RD_REG_BASE: number; //esp32 33 | abstract ENTRY: number; 34 | 35 | abstract FLASH_WRITE_SIZE: number; 36 | // abstract IMAGE_CHIP_ID: number; // not in esp8266 37 | abstract ROM_DATA: string; 38 | abstract ROM_TEXT: string; 39 | abstract SPI_MOSI_DLEN_OFFS: number; // not in esp8266 40 | abstract SPI_MISO_DLEN_OFFS: number; // not in esp8266 41 | abstract SPI_REG_BASE: number; 42 | abstract SPI_USR_OFFS: number; 43 | abstract SPI_USR1_OFFS: number; 44 | abstract SPI_USR2_OFFS: number; 45 | abstract SPI_W0_OFFS: number; 46 | abstract UART_CLKDIV_MASK: number; 47 | abstract UART_CLKDIV_REG: number; 48 | abstract UART_DATE_REG_ADDR: number; // not in esp8266 49 | abstract TEXT_START: number; 50 | // abstract XTAL_CLK_DIVIDER: number; //esp32 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v*" 5 | 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: 16 14 | registry-url: "https://registry.npmjs.org" 15 | - name: Install dependencies and build 🔧 16 | run: npm ci && npm run build 17 | - name: Run lint 18 | run: npm run lint 19 | - name: Run tests 20 | run: npm run test 21 | - name: Package module 22 | run: npm pack 23 | - name: Verify tag version is the same as package.json version 24 | id: version 25 | run: | 26 | [ ${GITHUB_REF:11} = $(node -p "require('./package.json').version") ] 27 | echo "version=${GITHUB_REF:11}" >> $GITHUB_OUTPUT 28 | - name: Upload package file 29 | uses: actions/upload-artifact@v1 30 | with: 31 | name: esptool-js-${{ steps.version.outputs.version }}.tgz 32 | path: esptool-js-${{ steps.version.outputs.version }}.tgz 33 | - name: Create Release 34 | id: create_release 35 | uses: actions/create-release@v1.0.0 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | with: 39 | tag_name: ${{ github.ref }} 40 | release_name: Espressif ESPTOOL-JS ${{ github.ref }} 41 | draft: true 42 | body: | 43 | ### Release Highlights 44 | 45 | ### Features & Enhancements 46 | 47 | 48 | 49 | ### Bug Fixes 50 | 51 | 52 | - name: Upload release 53 | id: upload-release-asset 54 | uses: actions/upload-release-asset@v1.0.1 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | with: 58 | upload_url: ${{ steps.create_release.outputs.upload_url }} 59 | asset_path: ./esptool-js-${{ steps.version.outputs.version }}.tgz 60 | asset_name: esptool-js-${{ steps.version.outputs.version }}.tgz 61 | asset_content_type: application/zip 62 | - name: Publish package on NPM 📦 63 | run: npm publish 64 | env: 65 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Javascript implementation of esptool 2 | 3 | This repository contains a Javascript implementation of [esptool](https://github.com/espressif/esptool), a serial flasher utility for Espressif chips. `esptool-js` is based on [Web Serial API](https://wicg.github.io/serial/) and works in Google Chrome and Microsoft Edge [version 89 or later](https://developer.mozilla.org/en-US/docs/Web/API/Serial#browser_compatibility) browsers. 4 | 5 | **NOTE:** Unlike the Python-based esptool, `esptool-js` doesn't implement generation of binary images out of ELF files, and doesn't include companion tools similar to [espefuse.py](https://github.com/espressif/esptool/wiki/espefuse) and [espsecure.py](https://github.com/espressif/esptool/wiki/espsecure). 6 | 7 | ## Usage 8 | 9 | **CDN** 10 | 11 | `https://unpkg.com/esptool-js/lib/index.js?module` 12 | 13 | **NPM** 14 | 15 | `npm install --save esptool-js` 16 | 17 | **Yarn** 18 | 19 | `yarn add --save esptool-js` 20 | 21 | Check an example project [here](./examples/typescript). 22 | 23 | ## Define port filters for device using WebSerial 24 | 25 | ```js 26 | const portFilters: { usbVendorId?: number | undefined, usbProductId?: number | undefined }[] = []; 27 | const device = await navigator.serial.requestPort({ filters: portFilters }); 28 | ``` 29 | 30 | ## Inject a Terminal to use with esptool-js 31 | 32 | ```js 33 | // You can use any JavaScript compatible terminal by wrapping it in a helper object like this: 34 | let espLoaderTerminal = { 35 | clean() { 36 | // Implement the clean function call for your terminal here. 37 | }, 38 | writeLine(data) { 39 | // Implement the writeLine function call for your terminal here. 40 | }, 41 | write(data) { 42 | // Implement the write function call for your terminal here. 43 | }, 44 | }; 45 | ``` 46 | 47 | You can pass this terminal object to `ESPLoader` constructor as shown in the [examples projects](./examples/). 48 | 49 | ## Live demo 50 | 51 | Visit https://espressif.github.io/esptool-js/ to see this tool in action. 52 | 53 | ## Testing it locally 54 | 55 | ```sh 56 | npm install 57 | npm run build 58 | cd examples/typescript 59 | npm install 60 | npm run dev # Run local sever with example code 61 | ``` 62 | 63 | Then open `http://localhost:1234` in a Chrome browser. The `npm run build` step builds the `lib` used in the example `examples/typescript/index.html`. Update this reference as described in [Usage](#usage) section. 64 | 65 | ## License 66 | 67 | The code in this repository is Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd. It is licensed under Apache 2.0 license, as described in [LICENSE](LICENSE) file. 68 | -------------------------------------------------------------------------------- /src/targets/esp32s3.ts: -------------------------------------------------------------------------------- 1 | import { ESPLoader } from "../esploader"; 2 | import { ROM } from "./rom"; 3 | import ESP32S3_STUB from "./stub_flasher/stub_flasher_32s3.json"; 4 | 5 | export class ESP32S3ROM extends ROM { 6 | public CHIP_NAME = "ESP32-S3"; 7 | public IMAGE_CHIP_ID = 9; 8 | public EFUSE_BASE = 0x60007000; 9 | public MAC_EFUSE_REG = this.EFUSE_BASE + 0x044; 10 | public UART_CLKDIV_REG = 0x60000014; 11 | public UART_CLKDIV_MASK = 0xfffff; 12 | public UART_DATE_REG_ADDR = 0x60000080; 13 | 14 | public FLASH_WRITE_SIZE = 0x400; 15 | public BOOTLOADER_FLASH_OFFSET = 0x0; 16 | 17 | public FLASH_SIZES = { 18 | "1MB": 0x00, 19 | "2MB": 0x10, 20 | "4MB": 0x20, 21 | "8MB": 0x30, 22 | "16MB": 0x40, 23 | }; 24 | 25 | public SPI_REG_BASE = 0x60002000; 26 | public SPI_USR_OFFS = 0x18; 27 | public SPI_USR1_OFFS = 0x1c; 28 | public SPI_USR2_OFFS = 0x20; 29 | public SPI_MOSI_DLEN_OFFS = 0x24; 30 | public SPI_MISO_DLEN_OFFS = 0x28; 31 | public SPI_W0_OFFS = 0x58; 32 | 33 | public USB_RAM_BLOCK = 0x800; 34 | public UARTDEV_BUF_NO_USB = 3; 35 | public UARTDEV_BUF_NO = 0x3fcef14c; 36 | 37 | public TEXT_START = ESP32S3_STUB.text_start; 38 | public ENTRY = ESP32S3_STUB.entry; 39 | public DATA_START = ESP32S3_STUB.data_start; 40 | public ROM_DATA = ESP32S3_STUB.data; 41 | public ROM_TEXT = ESP32S3_STUB.text; 42 | 43 | public async get_chip_description(loader: ESPLoader) { 44 | return "ESP32-S3"; 45 | } 46 | public async get_chip_features(loader: ESPLoader) { 47 | return ["Wi-Fi", "BLE"]; 48 | } 49 | public async get_crystal_freq(loader: ESPLoader) { 50 | return 40; 51 | } 52 | public _d2h(d: number) { 53 | const h = (+d).toString(16); 54 | return h.length === 1 ? "0" + h : h; 55 | } 56 | 57 | public async _post_connect(loader: ESPLoader) { 58 | const buf_no = (await loader.read_reg(this.UARTDEV_BUF_NO)) & 0xff; 59 | loader.debug("In _post_connect " + buf_no); 60 | if (buf_no == this.UARTDEV_BUF_NO_USB) { 61 | loader.ESP_RAM_BLOCK = this.USB_RAM_BLOCK; 62 | } 63 | } 64 | 65 | public async read_mac(loader: ESPLoader) { 66 | let mac0 = await loader.read_reg(this.MAC_EFUSE_REG); 67 | mac0 = mac0 >>> 0; 68 | let mac1 = await loader.read_reg(this.MAC_EFUSE_REG + 4); 69 | mac1 = (mac1 >>> 0) & 0x0000ffff; 70 | const mac = new Uint8Array(6); 71 | mac[0] = (mac1 >> 8) & 0xff; 72 | mac[1] = mac1 & 0xff; 73 | mac[2] = (mac0 >> 24) & 0xff; 74 | mac[3] = (mac0 >> 16) & 0xff; 75 | mac[4] = (mac0 >> 8) & 0xff; 76 | mac[5] = mac0 & 0xff; 77 | 78 | return ( 79 | this._d2h(mac[0]) + 80 | ":" + 81 | this._d2h(mac[1]) + 82 | ":" + 83 | this._d2h(mac[2]) + 84 | ":" + 85 | this._d2h(mac[3]) + 86 | ":" + 87 | this._d2h(mac[4]) + 88 | ":" + 89 | this._d2h(mac[5]) 90 | ); 91 | } 92 | 93 | public get_erase_size(offset: number, size: number) { 94 | return size; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/targets/esp32h2.ts: -------------------------------------------------------------------------------- 1 | import { ESPLoader } from "../esploader"; 2 | import { ROM } from "./rom"; 3 | import ESP32H2_STUB from "./stub_flasher/stub_flasher_32h2.json"; 4 | 5 | export class ESP32H2ROM extends ROM { 6 | public CHIP_NAME = "ESP32-H2"; 7 | public IMAGE_CHIP_ID = 16; 8 | public EFUSE_BASE = 0x60008800; 9 | public MAC_EFUSE_REG = this.EFUSE_BASE + 0x044; 10 | public UART_CLKDIV_REG = 0x3ff40014; 11 | public UART_CLKDIV_MASK = 0xfffff; 12 | public UART_DATE_REG_ADDR = 0x6000007c; 13 | 14 | public FLASH_WRITE_SIZE = 0x400; 15 | public BOOTLOADER_FLASH_OFFSET = 0x0; 16 | 17 | public FLASH_SIZES = { 18 | "1MB": 0x00, 19 | "2MB": 0x10, 20 | "4MB": 0x20, 21 | "8MB": 0x30, 22 | "16MB": 0x40, 23 | }; 24 | 25 | public SPI_REG_BASE = 0x60002000; 26 | public SPI_USR_OFFS = 0x18; 27 | public SPI_USR1_OFFS = 0x1c; 28 | public SPI_USR2_OFFS = 0x20; 29 | public SPI_MOSI_DLEN_OFFS = 0x24; 30 | public SPI_MISO_DLEN_OFFS = 0x28; 31 | public SPI_W0_OFFS = 0x58; 32 | 33 | public USB_RAM_BLOCK = 0x800; 34 | public UARTDEV_BUF_NO_USB = 3; 35 | public UARTDEV_BUF_NO = 0x3fcef14c; 36 | 37 | public TEXT_START = ESP32H2_STUB.text_start; 38 | public ENTRY = ESP32H2_STUB.entry; 39 | public DATA_START = ESP32H2_STUB.data_start; 40 | public ROM_DATA = ESP32H2_STUB.data; 41 | public ROM_TEXT = ESP32H2_STUB.text; 42 | 43 | public async get_chip_description(loader: ESPLoader) { 44 | return this.CHIP_NAME; 45 | } 46 | 47 | public async get_chip_features(loader: ESPLoader) { 48 | return ["BLE", "IEEE802.15.4"]; 49 | } 50 | 51 | public async get_crystal_freq(loader: ESPLoader) { 52 | // ESP32H2 XTAL is fixed to 32MHz 53 | return 32; 54 | } 55 | 56 | public _d2h(d: number) { 57 | const h = (+d).toString(16); 58 | return h.length === 1 ? "0" + h : h; 59 | } 60 | 61 | public async _post_connect(loader: ESPLoader) { 62 | const buf_no = (await loader.read_reg(this.UARTDEV_BUF_NO)) & 0xff; 63 | loader.debug("In _post_connect " + buf_no); 64 | if (buf_no == this.UARTDEV_BUF_NO_USB) { 65 | loader.ESP_RAM_BLOCK = this.USB_RAM_BLOCK; 66 | } 67 | } 68 | 69 | public async read_mac(loader: ESPLoader) { 70 | let mac0 = await loader.read_reg(this.MAC_EFUSE_REG); 71 | mac0 = mac0 >>> 0; 72 | let mac1 = await loader.read_reg(this.MAC_EFUSE_REG + 4); 73 | mac1 = (mac1 >>> 0) & 0x0000ffff; 74 | const mac = new Uint8Array(6); 75 | mac[0] = (mac1 >> 8) & 0xff; 76 | mac[1] = mac1 & 0xff; 77 | mac[2] = (mac0 >> 24) & 0xff; 78 | mac[3] = (mac0 >> 16) & 0xff; 79 | mac[4] = (mac0 >> 8) & 0xff; 80 | mac[5] = mac0 & 0xff; 81 | 82 | return ( 83 | this._d2h(mac[0]) + 84 | ":" + 85 | this._d2h(mac[1]) + 86 | ":" + 87 | this._d2h(mac[2]) + 88 | ":" + 89 | this._d2h(mac[3]) + 90 | ":" + 91 | this._d2h(mac[4]) + 92 | ":" + 93 | this._d2h(mac[5]) 94 | ); 95 | } 96 | 97 | public get_erase_size(offset: number, size: number) { 98 | return size; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/targets/esp32c3.ts: -------------------------------------------------------------------------------- 1 | import { ESPLoader } from "../esploader"; 2 | import { ROM } from "./rom"; 3 | import ESP32C3_STUB from "./stub_flasher/stub_flasher_32c3.json"; 4 | 5 | export class ESP32C3ROM extends ROM { 6 | public CHIP_NAME = "ESP32-C3"; 7 | public IMAGE_CHIP_ID = 5; 8 | public EFUSE_BASE = 0x60008800; 9 | public MAC_EFUSE_REG = this.EFUSE_BASE + 0x044; 10 | public UART_CLKDIV_REG = 0x3ff40014; 11 | public UART_CLKDIV_MASK = 0xfffff; 12 | public UART_DATE_REG_ADDR = 0x6000007c; 13 | 14 | public FLASH_WRITE_SIZE = 0x400; 15 | public BOOTLOADER_FLASH_OFFSET = 0; 16 | 17 | public FLASH_SIZES = { 18 | "1MB": 0x00, 19 | "2MB": 0x10, 20 | "4MB": 0x20, 21 | "8MB": 0x30, 22 | "16MB": 0x40, 23 | }; 24 | 25 | public SPI_REG_BASE = 0x60002000; 26 | public SPI_USR_OFFS = 0x18; 27 | public SPI_USR1_OFFS = 0x1c; 28 | public SPI_USR2_OFFS = 0x20; 29 | public SPI_MOSI_DLEN_OFFS = 0x24; 30 | public SPI_MISO_DLEN_OFFS = 0x28; 31 | public SPI_W0_OFFS = 0x58; 32 | 33 | public TEXT_START = ESP32C3_STUB.text_start; 34 | public ENTRY = ESP32C3_STUB.entry; 35 | public DATA_START = ESP32C3_STUB.data_start; 36 | public ROM_DATA = ESP32C3_STUB.data; 37 | public ROM_TEXT = ESP32C3_STUB.text; 38 | 39 | public async get_pkg_version(loader: ESPLoader) { 40 | const num_word = 3; 41 | const block1_addr = this.EFUSE_BASE + 0x044; 42 | const addr = block1_addr + 4 * num_word; 43 | const word3 = await loader.read_reg(addr); 44 | const pkg_version = (word3 >> 21) & 0x07; 45 | return pkg_version; 46 | } 47 | 48 | public async get_chip_revision(loader: ESPLoader) { 49 | const block1_addr = this.EFUSE_BASE + 0x044; 50 | const num_word = 3; 51 | const pos = 18; 52 | const addr = block1_addr + 4 * num_word; 53 | const ret = ((await loader.read_reg(addr)) & (0x7 << pos)) >> pos; 54 | return ret; 55 | } 56 | 57 | public async get_chip_description(loader: ESPLoader) { 58 | let desc: string; 59 | const pkg_ver = await this.get_pkg_version(loader); 60 | if (pkg_ver === 0) { 61 | desc = "ESP32-C3"; 62 | } else { 63 | desc = "unknown ESP32-C3"; 64 | } 65 | const chip_rev = await this.get_chip_revision(loader); 66 | desc += " (revision " + chip_rev + ")"; 67 | return desc; 68 | } 69 | 70 | public async get_chip_features(loader: ESPLoader) { 71 | return ["Wi-Fi"]; 72 | } 73 | 74 | public async get_crystal_freq(loader: ESPLoader) { 75 | return 40; 76 | } 77 | 78 | public _d2h(d: number) { 79 | const h = (+d).toString(16); 80 | return h.length === 1 ? "0" + h : h; 81 | } 82 | 83 | public async read_mac(loader: ESPLoader) { 84 | let mac0 = await loader.read_reg(this.MAC_EFUSE_REG); 85 | mac0 = mac0 >>> 0; 86 | let mac1 = await loader.read_reg(this.MAC_EFUSE_REG + 4); 87 | mac1 = (mac1 >>> 0) & 0x0000ffff; 88 | const mac = new Uint8Array(6); 89 | mac[0] = (mac1 >> 8) & 0xff; 90 | mac[1] = mac1 & 0xff; 91 | mac[2] = (mac0 >> 24) & 0xff; 92 | mac[3] = (mac0 >> 16) & 0xff; 93 | mac[4] = (mac0 >> 8) & 0xff; 94 | mac[5] = mac0 & 0xff; 95 | 96 | return ( 97 | this._d2h(mac[0]) + 98 | ":" + 99 | this._d2h(mac[1]) + 100 | ":" + 101 | this._d2h(mac[2]) + 102 | ":" + 103 | this._d2h(mac[3]) + 104 | ":" + 105 | this._d2h(mac[4]) + 106 | ":" + 107 | this._d2h(mac[5]) 108 | ); 109 | } 110 | 111 | public get_erase_size(offset: number, size: number) { 112 | return size; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/targets/esp32c6.ts: -------------------------------------------------------------------------------- 1 | import { ESPLoader } from "../esploader"; 2 | import { ROM } from "./rom"; 3 | import ESP32C6_STUB from "./stub_flasher/stub_flasher_32c6.json"; 4 | 5 | export class ESP32C6ROM extends ROM { 6 | public CHIP_NAME = "ESP32-C6"; 7 | public IMAGE_CHIP_ID = 13; 8 | public EFUSE_BASE = 0x600b0800; 9 | public MAC_EFUSE_REG = this.EFUSE_BASE + 0x044; 10 | public UART_CLKDIV_REG = 0x3ff40014; 11 | public UART_CLKDIV_MASK = 0xfffff; 12 | public UART_DATE_REG_ADDR = 0x6000007c; 13 | 14 | public FLASH_WRITE_SIZE = 0x400; 15 | public BOOTLOADER_FLASH_OFFSET = 0; 16 | 17 | public FLASH_SIZES = { 18 | "1MB": 0x00, 19 | "2MB": 0x10, 20 | "4MB": 0x20, 21 | "8MB": 0x30, 22 | "16MB": 0x40, 23 | }; 24 | 25 | public SPI_REG_BASE = 0x60002000; 26 | public SPI_USR_OFFS = 0x18; 27 | public SPI_USR1_OFFS = 0x1c; 28 | public SPI_USR2_OFFS = 0x20; 29 | public SPI_MOSI_DLEN_OFFS = 0x24; 30 | public SPI_MISO_DLEN_OFFS = 0x28; 31 | public SPI_W0_OFFS = 0x58; 32 | 33 | public TEXT_START = ESP32C6_STUB.text_start; 34 | public ENTRY = ESP32C6_STUB.entry; 35 | public DATA_START = ESP32C6_STUB.data_start; 36 | public ROM_DATA = ESP32C6_STUB.data; 37 | public ROM_TEXT = ESP32C6_STUB.text; 38 | 39 | public async get_pkg_version(loader: ESPLoader) { 40 | const num_word = 3; 41 | const block1_addr = this.EFUSE_BASE + 0x044; 42 | const addr = block1_addr + 4 * num_word; 43 | const word3 = await loader.read_reg(addr); 44 | const pkg_version = (word3 >> 21) & 0x07; 45 | return pkg_version; 46 | } 47 | 48 | public async get_chip_revision(loader: ESPLoader) { 49 | const block1_addr = this.EFUSE_BASE + 0x044; 50 | const num_word = 3; 51 | const pos = 18; 52 | const addr = block1_addr + 4 * num_word; 53 | const ret = ((await loader.read_reg(addr)) & (0x7 << pos)) >> pos; 54 | return ret; 55 | } 56 | 57 | public async get_chip_description(loader: ESPLoader) { 58 | let desc: string; 59 | const pkg_ver = await this.get_pkg_version(loader); 60 | if (pkg_ver === 0) { 61 | desc = "ESP32-C6"; 62 | } else { 63 | desc = "unknown ESP32-C6"; 64 | } 65 | const chip_rev = await this.get_chip_revision(loader); 66 | desc += " (revision " + chip_rev + ")"; 67 | return desc; 68 | } 69 | 70 | public async get_chip_features(loader: ESPLoader) { 71 | return ["Wi-Fi"]; 72 | } 73 | 74 | public async get_crystal_freq(loader: ESPLoader) { 75 | return 40; 76 | } 77 | 78 | public _d2h(d: number) { 79 | const h = (+d).toString(16); 80 | return h.length === 1 ? "0" + h : h; 81 | } 82 | 83 | public async read_mac(loader: ESPLoader) { 84 | let mac0 = await loader.read_reg(this.MAC_EFUSE_REG); 85 | mac0 = mac0 >>> 0; 86 | let mac1 = await loader.read_reg(this.MAC_EFUSE_REG + 4); 87 | mac1 = (mac1 >>> 0) & 0x0000ffff; 88 | const mac = new Uint8Array(6); 89 | mac[0] = (mac1 >> 8) & 0xff; 90 | mac[1] = mac1 & 0xff; 91 | mac[2] = (mac0 >> 24) & 0xff; 92 | mac[3] = (mac0 >> 16) & 0xff; 93 | mac[4] = (mac0 >> 8) & 0xff; 94 | mac[5] = mac0 & 0xff; 95 | 96 | return ( 97 | this._d2h(mac[0]) + 98 | ":" + 99 | this._d2h(mac[1]) + 100 | ":" + 101 | this._d2h(mac[2]) + 102 | ":" + 103 | this._d2h(mac[3]) + 104 | ":" + 105 | this._d2h(mac[4]) + 106 | ":" + 107 | this._d2h(mac[5]) 108 | ); 109 | } 110 | 111 | public get_erase_size(offset: number, size: number) { 112 | return size; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/targets/esp32s2.ts: -------------------------------------------------------------------------------- 1 | import { ESPLoader } from "../esploader"; 2 | import { ROM } from "./rom"; 3 | import ESP32S2_STUB from "./stub_flasher/stub_flasher_32s2.json"; 4 | 5 | export class ESP32S2ROM extends ROM { 6 | public CHIP_NAME = "ESP32-S2"; 7 | public IMAGE_CHIP_ID = 2; 8 | public MAC_EFUSE_REG = 0x3f41a044; 9 | public EFUSE_BASE = 0x3f41a000; 10 | public UART_CLKDIV_REG = 0x3f400014; 11 | public UART_CLKDIV_MASK = 0xfffff; 12 | public UART_DATE_REG_ADDR = 0x60000078; 13 | 14 | public FLASH_WRITE_SIZE = 0x400; 15 | public BOOTLOADER_FLASH_OFFSET = 0x1000; 16 | 17 | public FLASH_SIZES = { 18 | "1MB": 0x00, 19 | "2MB": 0x10, 20 | "4MB": 0x20, 21 | "8MB": 0x30, 22 | "16MB": 0x40, 23 | }; 24 | 25 | public SPI_REG_BASE = 0x3f402000; 26 | public SPI_USR_OFFS = 0x18; 27 | public SPI_USR1_OFFS = 0x1c; 28 | public SPI_USR2_OFFS = 0x20; 29 | public SPI_W0_OFFS = 0x58; 30 | public SPI_MOSI_DLEN_OFFS = 0x24; 31 | public SPI_MISO_DLEN_OFFS = 0x28; 32 | 33 | public TEXT_START = ESP32S2_STUB.text_start; 34 | public ENTRY = ESP32S2_STUB.entry; 35 | public DATA_START = ESP32S2_STUB.data_start; 36 | public ROM_DATA = ESP32S2_STUB.data; 37 | public ROM_TEXT = ESP32S2_STUB.text; 38 | 39 | public async get_pkg_version(loader: ESPLoader) { 40 | const num_word = 3; 41 | const block1_addr = this.EFUSE_BASE + 0x044; 42 | const addr = block1_addr + 4 * num_word; 43 | const word3 = await loader.read_reg(addr); 44 | const pkg_version = (word3 >> 21) & 0x0f; 45 | return pkg_version; 46 | } 47 | 48 | public async get_chip_description(loader: ESPLoader) { 49 | const chip_desc = ["ESP32-S2", "ESP32-S2FH16", "ESP32-S2FH32"]; 50 | const pkg_ver = await this.get_pkg_version(loader); 51 | if (pkg_ver >= 0 && pkg_ver <= 2) { 52 | return chip_desc[pkg_ver]; 53 | } else { 54 | return "unknown ESP32-S2"; 55 | } 56 | } 57 | 58 | public async get_chip_features(loader: ESPLoader) { 59 | const features = ["Wi-Fi"]; 60 | const pkg_ver = await this.get_pkg_version(loader); 61 | if (pkg_ver == 1) { 62 | features.push("Embedded 2MB Flash"); 63 | } else if (pkg_ver == 2) { 64 | features.push("Embedded 4MB Flash"); 65 | } 66 | const num_word = 4; 67 | const block2_addr = this.EFUSE_BASE + 0x05c; 68 | const addr = block2_addr + 4 * num_word; 69 | const word4 = await loader.read_reg(addr); 70 | const block2_ver = (word4 >> 4) & 0x07; 71 | 72 | if (block2_ver == 1) { 73 | features.push("ADC and temperature sensor calibration in BLK2 of efuse"); 74 | } 75 | return features; 76 | } 77 | 78 | public async get_crystal_freq(loader: ESPLoader) { 79 | return 40; 80 | } 81 | public _d2h(d: number) { 82 | const h = (+d).toString(16); 83 | return h.length === 1 ? "0" + h : h; 84 | } 85 | public async read_mac(loader: ESPLoader) { 86 | let mac0 = await loader.read_reg(this.MAC_EFUSE_REG); 87 | mac0 = mac0 >>> 0; 88 | let mac1 = await loader.read_reg(this.MAC_EFUSE_REG + 4); 89 | mac1 = (mac1 >>> 0) & 0x0000ffff; 90 | const mac = new Uint8Array(6); 91 | mac[0] = (mac1 >> 8) & 0xff; 92 | mac[1] = mac1 & 0xff; 93 | mac[2] = (mac0 >> 24) & 0xff; 94 | mac[3] = (mac0 >> 16) & 0xff; 95 | mac[4] = (mac0 >> 8) & 0xff; 96 | mac[5] = mac0 & 0xff; 97 | 98 | return ( 99 | this._d2h(mac[0]) + 100 | ":" + 101 | this._d2h(mac[1]) + 102 | ":" + 103 | this._d2h(mac[2]) + 104 | ":" + 105 | this._d2h(mac[3]) + 106 | ":" + 107 | this._d2h(mac[4]) + 108 | ":" + 109 | this._d2h(mac[5]) 110 | ); 111 | } 112 | 113 | public get_erase_size(offset: number, size: number) { 114 | return size; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/reset.ts: -------------------------------------------------------------------------------- 1 | import { Transport } from "./webserial"; 2 | 3 | const DEFAULT_RESET_DELAY = 50; 4 | 5 | function sleep(ms: number): Promise { 6 | return new Promise((resolve) => setTimeout(resolve, ms)); 7 | } 8 | 9 | export async function classicReset(transport: Transport, resetDelay = DEFAULT_RESET_DELAY) { 10 | await transport.setDTR(false); 11 | await transport.setRTS(true); 12 | await sleep(100); 13 | await transport.setDTR(true); 14 | await transport.setRTS(false); 15 | await sleep(resetDelay); 16 | await transport.setDTR(false); 17 | } 18 | 19 | export async function usbJTAGSerialReset(transport: Transport) { 20 | await transport.setRTS(false); 21 | await transport.setDTR(false); 22 | await sleep(100); 23 | 24 | await transport.setDTR(true); 25 | await transport.setRTS(false); 26 | await sleep(100); 27 | 28 | await transport.setRTS(true); 29 | await transport.setDTR(false); 30 | await transport.setRTS(true); 31 | 32 | await sleep(100); 33 | await transport.setRTS(false); 34 | await transport.setDTR(false); 35 | } 36 | 37 | export async function hardReset(transport: Transport, usingUsbOtg = false) { 38 | if (usingUsbOtg) { 39 | await sleep(200); 40 | await transport.setRTS(false); 41 | await sleep(200); 42 | } else { 43 | await sleep(100); 44 | await transport.setRTS(false); 45 | } 46 | } 47 | 48 | type CmdsArgsTypes = { 49 | D: boolean; 50 | R: boolean; 51 | W: number; 52 | }; 53 | 54 | export function validateCustomResetStringSequence(seqStr: string): boolean { 55 | const commands: (keyof CmdsArgsTypes)[] = ["D", "R", "W"]; 56 | 57 | const commandsList = seqStr.split("|"); 58 | 59 | for (const cmd of commandsList) { 60 | const code = cmd[0]; 61 | const arg = cmd.slice(1); 62 | 63 | if (!commands.includes(code as keyof CmdsArgsTypes)) { 64 | return false; // Invalid command code 65 | } 66 | 67 | if (code === "D" || code === "R") { 68 | if (arg !== "0" && arg !== "1") { 69 | return false; // Invalid argument for D and R commands 70 | } 71 | } else if (code === "W") { 72 | const delay = parseInt(arg); 73 | if (isNaN(delay) || delay <= 0) { 74 | return false; // Invalid argument for W command 75 | } 76 | } 77 | } 78 | return true; // All commands are valid 79 | } 80 | 81 | /** 82 | * Custom reset strategy defined with a string. 83 | * 84 | * The sequenceString input string consists of individual commands divided by "|". 85 | * 86 | * Commands (e.g. R0) are defined by a code (R) and an argument (0). 87 | * 88 | * The commands are: 89 | * 90 | * D: setDTR - 1=True / 0=False 91 | * 92 | * R: setRTS - 1=True / 0=False 93 | * 94 | * W: Wait (time delay) - positive integer number (miliseconds) 95 | * 96 | * "D0|R1|W100|D1|R0|W50|D0" represents the classic reset strategy 97 | */ 98 | export async function customReset(transport: Transport, sequenceString: string) { 99 | const resetDictionary: { [K in keyof CmdsArgsTypes]: (arg: CmdsArgsTypes[K]) => Promise } = { 100 | D: async (arg: boolean) => await transport.setDTR(arg), 101 | R: async (arg: boolean) => await transport.setRTS(arg), 102 | W: async (delay: number) => await sleep(delay), 103 | }; 104 | try { 105 | const isValidSequence = validateCustomResetStringSequence(sequenceString); 106 | if (!isValidSequence) { 107 | return; 108 | } 109 | const cmds = sequenceString.split("|"); 110 | for (const cmd of cmds) { 111 | const cmdKey = cmd[0]; 112 | const cmdVal = cmd.slice(1); 113 | if (cmdKey === "W") { 114 | await resetDictionary["W"](Number(cmdVal)); 115 | } else if (cmdKey === "D" || cmdKey === "R") { 116 | await resetDictionary[cmdKey as "D" | "R"](cmdVal === "1"); 117 | } 118 | } 119 | } catch (error) { 120 | throw new Error("Invalid custom reset sequence"); 121 | } 122 | } 123 | 124 | export default { classicReset, customReset, hardReset, usbJTAGSerialReset, validateCustomResetStringSequence }; 125 | -------------------------------------------------------------------------------- /src/targets/esp8266.ts: -------------------------------------------------------------------------------- 1 | import { ESPLoader } from "../esploader"; 2 | import { ROM } from "./rom"; 3 | import ESP8266_STUB from "./stub_flasher/stub_flasher_8266.json"; 4 | 5 | export class ESP8266ROM extends ROM { 6 | public CHIP_NAME = "ESP8266"; 7 | public CHIP_DETECT_MAGIC_VALUE = [0xfff0c101]; 8 | public EFUSE_RD_REG_BASE = 0x3ff00050; 9 | public UART_CLKDIV_REG = 0x60000014; 10 | public UART_CLKDIV_MASK = 0xfffff; 11 | public XTAL_CLK_DIVIDER = 2; 12 | 13 | public FLASH_WRITE_SIZE = 0x4000; 14 | 15 | // NOT IMPLEMENTED, SETTING EMPTY VALUE 16 | public BOOTLOADER_FLASH_OFFSET = 0; 17 | public UART_DATE_REG_ADDR = 0; 18 | 19 | public FLASH_SIZES = { 20 | "512KB": 0x00, 21 | "256KB": 0x10, 22 | "1MB": 0x20, 23 | "2MB": 0x30, 24 | "4MB": 0x40, 25 | "2MB-c1": 0x50, 26 | "4MB-c1": 0x60, 27 | "8MB": 0x80, 28 | "16MB": 0x90, 29 | }; 30 | 31 | public SPI_REG_BASE = 0x60000200; 32 | public SPI_USR_OFFS = 0x1c; 33 | public SPI_USR1_OFFS = 0x20; 34 | public SPI_USR2_OFFS = 0x24; 35 | public SPI_MOSI_DLEN_OFFS = 0; // not in esp8266 36 | public SPI_MISO_DLEN_OFFS = 0; // not in esp8266 37 | public SPI_W0_OFFS = 0x40; 38 | 39 | public TEXT_START = ESP8266_STUB.text_start; 40 | public ENTRY = ESP8266_STUB.entry; 41 | public DATA_START = ESP8266_STUB.data_start; 42 | public ROM_DATA = ESP8266_STUB.data; 43 | public ROM_TEXT = ESP8266_STUB.text; 44 | 45 | public async read_efuse(loader: ESPLoader, offset: number) { 46 | const addr = this.EFUSE_RD_REG_BASE + 4 * offset; 47 | loader.debug("Read efuse " + addr); 48 | return await loader.read_reg(addr); 49 | } 50 | 51 | public async get_chip_description(loader: ESPLoader) { 52 | const efuse3 = await this.read_efuse(loader, 2); 53 | const efuse0 = await this.read_efuse(loader, 0); 54 | 55 | const is_8285 = ((efuse0 & (1 << 4)) | (efuse3 & (1 << 16))) != 0; // One or the other efuse bit is set for ESP8285 56 | return is_8285 ? "ESP8285" : "ESP8266EX"; 57 | } 58 | 59 | public get_chip_features = async (loader: ESPLoader) => { 60 | const features = ["WiFi"]; 61 | if ((await this.get_chip_description(loader)) == "ESP8285") features.push("Embedded Flash"); 62 | return features; 63 | }; 64 | 65 | public async get_crystal_freq(loader: ESPLoader) { 66 | const uart_div = (await loader.read_reg(this.UART_CLKDIV_REG)) & this.UART_CLKDIV_MASK; 67 | const ets_xtal = (loader.transport.baudrate * uart_div) / 1000000 / this.XTAL_CLK_DIVIDER; 68 | let norm_xtal; 69 | if (ets_xtal > 33) { 70 | norm_xtal = 40; 71 | } else { 72 | norm_xtal = 26; 73 | } 74 | if (Math.abs(norm_xtal - ets_xtal) > 1) { 75 | loader.info( 76 | "WARNING: Detected crystal freq " + 77 | ets_xtal + 78 | "MHz is quite different to normalized freq " + 79 | norm_xtal + 80 | "MHz. Unsupported crystal in use?", 81 | ); 82 | } 83 | return norm_xtal; 84 | } 85 | 86 | public _d2h(d: number) { 87 | const h = (+d).toString(16); 88 | return h.length === 1 ? "0" + h : h; 89 | } 90 | 91 | public async read_mac(loader: ESPLoader) { 92 | let mac0 = await this.read_efuse(loader, 0); 93 | mac0 = mac0 >>> 0; 94 | let mac1 = await this.read_efuse(loader, 1); 95 | mac1 = mac1 >>> 0; 96 | let mac3 = await this.read_efuse(loader, 3); 97 | mac3 = mac3 >>> 0; 98 | const mac = new Uint8Array(6); 99 | 100 | if (mac3 != 0) { 101 | mac[0] = (mac3 >> 16) & 0xff; 102 | mac[1] = (mac3 >> 8) & 0xff; 103 | mac[2] = mac3 & 0xff; 104 | } else if (((mac1 >> 16) & 0xff) == 0) { 105 | mac[0] = 0x18; 106 | mac[1] = 0xfe; 107 | mac[2] = 0x34; 108 | } else if (((mac1 >> 16) & 0xff) == 1) { 109 | mac[0] = 0xac; 110 | mac[1] = 0xd0; 111 | mac[2] = 0x74; 112 | } else { 113 | loader.error("Unknown OUI"); 114 | } 115 | 116 | mac[3] = (mac1 >> 8) & 0xff; 117 | mac[4] = mac1 & 0xff; 118 | mac[5] = (mac0 >> 24) & 0xff; 119 | 120 | return ( 121 | this._d2h(mac[0]) + 122 | ":" + 123 | this._d2h(mac[1]) + 124 | ":" + 125 | this._d2h(mac[2]) + 126 | ":" + 127 | this._d2h(mac[3]) + 128 | ":" + 129 | this._d2h(mac[4]) + 130 | ":" + 131 | this._d2h(mac[5]) 132 | ); 133 | } 134 | 135 | public get_erase_size(offset: number, size: number) { 136 | return size; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/targets/stub_flasher/stub_flasher_32.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": 1074521560, 3 | "text": "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", 4 | "text_start": 1074520064, 5 | "data": "GOv8P9jnC0Bx6AtA8+wLQO3oC0CP6AtA7egLQEnpC0AG6gtAeOoLQCHqC0CB5wtAo+kLQPjpC0Bn6QtAmuoLQI7pC0Ca6gtAXegLQLPoC0Dt6AtASekLQHfoC0BM6wtAs+wLQKXmC0DX7AtApeYLQKXmC0Cl5gtApeYLQKXmC0Cl5gtApeYLQKXmC0Dz6gtApeYLQM3rC0Cz7AtA", 6 | "data_start": 1073605544 7 | } -------------------------------------------------------------------------------- /examples/typescript/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ESP Tool 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

ESP Tool

20 |

A Serial Flasher utility for Espressif chips

21 | 23 |
24 |
25 |
26 |

Program

27 | 28 | 29 | 35 | 36 | 37 | 38 | 39 |
40 | 41 | 45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
Flash AddressFile
58 | 59 | 60 |
61 | 62 |
63 |
64 |
65 |

Console

66 | 67 | 68 | 69 | 70 | 71 |
72 |
73 |
74 |
75 | 76 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/targets/stub_flasher/stub_flasher_32c6.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": 1082132112, 3 | "text": "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", 4 | "text_start": 1082130432, 5 | "data": "HCuEQEIKgECSCoBA6gqAQI4LgED6C4BAqAuAQA4JgEBKC4BAiguAQP4KgEC+CIBAMguAQL4IgEAcCoBAYgqAQJIKgEDqCoBALgqAQHIJgECiCYBAKgqAQEwOgECSCoBAEg2AQAQOgED+B4BALA6AQP4HgED+B4BA/geAQP4HgED+B4BA/geAQP4HgED+B4BArgyAQP4HgEAwDYBABA6AQA==", 6 | "data_start": 1082469292 7 | } -------------------------------------------------------------------------------- /src/targets/stub_flasher/stub_flasher_32h2.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": 1082132112, 3 | "text": "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", 4 | "text_start": 1082130432, 5 | "data": "EACDQEIKgECSCoBA6gqAQI4LgED6C4BAqAuAQA4JgEBKC4BAiguAQP4KgEC+CIBAMguAQL4IgEAcCoBAYgqAQJIKgEDqCoBALgqAQHIJgECiCYBAKgqAQFIOgECSCoBAEg2AQAoOgED+B4BAMg6AQP4HgED+B4BA/geAQP4HgED+B4BA/geAQP4HgED+B4BArgyAQP4HgEAwDYBACg6AQA==", 6 | "data_start": 1082403756 7 | } -------------------------------------------------------------------------------- /src/targets/stub_flasher/stub_flasher_32c3.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": 1077413532, 3 | "text": "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", 4 | "text_start": 1077411840, 5 | "data": "IGvIP3YKOEDGCjhAHgs4QMILOEAuDDhA3As4QEIJOEB+CzhAvgs4QDILOEDyCDhAZgs4QPIIOEBQCjhAlgo4QMYKOEAeCzhAYgo4QKYJOEDWCThAXgo4QIAOOEDGCjhARg04QDgOOEAyCDhAYA44QDIIOEAyCDhAMgg4QDIIOEAyCDhAMgg4QDIIOEAyCDhA4gw4QDIIOEBkDThAOA44QA==", 6 | "data_start": 1070164912 7 | } -------------------------------------------------------------------------------- /src/targets/stub_flasher/stub_flasher_32s2.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": 1073907696, 3 | "text": "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", 4 | "text_start": 1073905664, 5 | "data": "ZCv9PzaLAkDBiwJAhpACQEqMAkDjiwJASowCQKmMAkByjQJA5Y0CQI2NAkDAigJAC40CQGSNAkDMjAJACI4CQPaMAkAIjgJAr4sCQA6MAkBKjAJAqYwCQMeLAkACiwJAx44CQD2QAkDYiQJAZZACQNiJAkDYiQJA2IkCQNiJAkDYiQJA2IkCQNiJAkDYiQJAZI4CQNiJAkBZjwJAPZACQA==", 6 | "data_start": 1073622012 7 | } -------------------------------------------------------------------------------- /src/targets/esp32.ts: -------------------------------------------------------------------------------- 1 | import { ESPLoader } from "../esploader"; 2 | import { ROM } from "./rom"; 3 | import ESP32_STUB from "./stub_flasher/stub_flasher_32.json"; 4 | 5 | export class ESP32ROM extends ROM { 6 | public CHIP_NAME = "ESP32"; 7 | public IMAGE_CHIP_ID = 0; 8 | public EFUSE_RD_REG_BASE = 0x3ff5a000; 9 | public DR_REG_SYSCON_BASE = 0x3ff66000; 10 | public UART_CLKDIV_REG = 0x3ff40014; 11 | public UART_CLKDIV_MASK = 0xfffff; 12 | public UART_DATE_REG_ADDR = 0x60000078; 13 | public XTAL_CLK_DIVIDER = 1; 14 | 15 | public FLASH_SIZES: { [key: string]: number } = { 16 | "1MB": 0x00, 17 | "2MB": 0x10, 18 | "4MB": 0x20, 19 | "8MB": 0x30, 20 | "16MB": 0x40, 21 | }; 22 | 23 | public FLASH_WRITE_SIZE = 0x400; 24 | public BOOTLOADER_FLASH_OFFSET = 0x1000; 25 | 26 | public SPI_REG_BASE = 0x3ff42000; 27 | public SPI_USR_OFFS = 0x1c; 28 | public SPI_USR1_OFFS = 0x20; 29 | public SPI_USR2_OFFS = 0x24; 30 | public SPI_W0_OFFS = 0x80; 31 | public SPI_MOSI_DLEN_OFFS = 0x28; 32 | public SPI_MISO_DLEN_OFFS = 0x2c; 33 | 34 | public TEXT_START = ESP32_STUB.text_start; 35 | public ENTRY = ESP32_STUB.entry; 36 | public DATA_START = ESP32_STUB.data_start; 37 | public ROM_DATA = ESP32_STUB.data; 38 | public ROM_TEXT = ESP32_STUB.text; 39 | 40 | public async read_efuse(loader: ESPLoader, offset: number) { 41 | const addr = this.EFUSE_RD_REG_BASE + 4 * offset; 42 | loader.debug("Read efuse " + addr); 43 | return await loader.read_reg(addr); 44 | } 45 | 46 | public async get_pkg_version(loader: ESPLoader) { 47 | const word3 = await this.read_efuse(loader, 3); 48 | let pkg_version = (word3 >> 9) & 0x07; 49 | pkg_version += ((word3 >> 2) & 0x1) << 3; 50 | return pkg_version; 51 | } 52 | 53 | public async get_chip_revision(loader: ESPLoader) { 54 | const word3 = await this.read_efuse(loader, 3); 55 | const word5 = await this.read_efuse(loader, 5); 56 | const apb_ctl_date = await loader.read_reg(this.DR_REG_SYSCON_BASE + 0x7c); 57 | 58 | const rev_bit0 = (word3 >> 15) & 0x1; 59 | const rev_bit1 = (word5 >> 20) & 0x1; 60 | const rev_bit2 = (apb_ctl_date >> 31) & 0x1; 61 | if (rev_bit0 != 0) { 62 | if (rev_bit1 != 0) { 63 | if (rev_bit2 != 0) { 64 | return 3; 65 | } else { 66 | return 2; 67 | } 68 | } else { 69 | return 1; 70 | } 71 | } 72 | return 0; 73 | } 74 | 75 | public async get_chip_description(loader: ESPLoader) { 76 | const chip_desc = [ 77 | "ESP32-D0WDQ6", 78 | "ESP32-D0WD", 79 | "ESP32-D2WD", 80 | "", 81 | "ESP32-U4WDH", 82 | "ESP32-PICO-D4", 83 | "ESP32-PICO-V3-02", 84 | ]; 85 | let chip_name = ""; 86 | const pkg_version = await this.get_pkg_version(loader); 87 | const chip_revision = await this.get_chip_revision(loader); 88 | const rev3 = chip_revision == 3; 89 | const single_core = (await this.read_efuse(loader, 3)) & (1 << 0); 90 | 91 | if (single_core != 0) { 92 | chip_desc[0] = "ESP32-S0WDQ6"; 93 | chip_desc[1] = "ESP32-S0WD"; 94 | } 95 | if (rev3) { 96 | chip_desc[5] = "ESP32-PICO-V3"; 97 | } 98 | if (pkg_version >= 0 && pkg_version <= 6) { 99 | chip_name = chip_desc[pkg_version]; 100 | } else { 101 | chip_name = "Unknown ESP32"; 102 | } 103 | 104 | if (rev3 && (pkg_version === 0 || pkg_version === 1)) { 105 | chip_name += "-V3"; 106 | } 107 | return chip_name + " (revision " + chip_revision + ")"; 108 | } 109 | 110 | public async get_chip_features(loader: ESPLoader) { 111 | const features = ["Wi-Fi"]; 112 | const word3 = await this.read_efuse(loader, 3); 113 | 114 | const chip_ver_dis_bt = word3 & (1 << 1); 115 | if (chip_ver_dis_bt === 0) { 116 | features.push(" BT"); 117 | } 118 | 119 | const chip_ver_dis_app_cpu = word3 & (1 << 0); 120 | if (chip_ver_dis_app_cpu !== 0) { 121 | features.push(" Single Core"); 122 | } else { 123 | features.push(" Dual Core"); 124 | } 125 | 126 | const chip_cpu_freq_rated = word3 & (1 << 13); 127 | if (chip_cpu_freq_rated !== 0) { 128 | const chip_cpu_freq_low = word3 & (1 << 12); 129 | if (chip_cpu_freq_low !== 0) { 130 | features.push(" 160MHz"); 131 | } else { 132 | features.push(" 240MHz"); 133 | } 134 | } 135 | 136 | const pkg_version = await this.get_pkg_version(loader); 137 | if ([2, 4, 5, 6].indexOf(pkg_version) !== -1) { 138 | features.push(" Embedded Flash"); 139 | } 140 | 141 | if (pkg_version === 6) { 142 | features.push(" Embedded PSRAM"); 143 | } 144 | 145 | const word4 = await this.read_efuse(loader, 4); 146 | const adc_vref = (word4 >> 8) & 0x1f; 147 | if (adc_vref !== 0) { 148 | features.push(" VRef calibration in efuse"); 149 | } 150 | 151 | const blk3_part_res = (word3 >> 14) & 0x1; 152 | if (blk3_part_res !== 0) { 153 | features.push(" BLK3 partially reserved"); 154 | } 155 | 156 | const word6 = await this.read_efuse(loader, 6); 157 | const coding_scheme = word6 & 0x3; 158 | const coding_scheme_arr = ["None", "3/4", "Repeat (UNSUPPORTED)", "Invalid"]; 159 | features.push(" Coding Scheme " + coding_scheme_arr[coding_scheme]); 160 | 161 | return features; 162 | } 163 | 164 | public async get_crystal_freq(loader: ESPLoader) { 165 | const uart_div = (await loader.read_reg(this.UART_CLKDIV_REG)) & this.UART_CLKDIV_MASK; 166 | const ets_xtal = (loader.transport.baudrate * uart_div) / 1000000 / this.XTAL_CLK_DIVIDER; 167 | let norm_xtal; 168 | if (ets_xtal > 33) { 169 | norm_xtal = 40; 170 | } else { 171 | norm_xtal = 26; 172 | } 173 | if (Math.abs(norm_xtal - ets_xtal) > 1) { 174 | loader.info("WARNING: Unsupported crystal in use"); 175 | } 176 | return norm_xtal; 177 | } 178 | 179 | public _d2h(d: number) { 180 | const h = (+d).toString(16); 181 | return h.length === 1 ? "0" + h : h; 182 | } 183 | 184 | public async read_mac(loader: ESPLoader) { 185 | let mac0 = await this.read_efuse(loader, 1); 186 | mac0 = mac0 >>> 0; 187 | let mac1 = await this.read_efuse(loader, 2); 188 | mac1 = mac1 >>> 0; 189 | const mac = new Uint8Array(6); 190 | mac[0] = (mac1 >> 8) & 0xff; 191 | mac[1] = mac1 & 0xff; 192 | mac[2] = (mac0 >> 24) & 0xff; 193 | mac[3] = (mac0 >> 16) & 0xff; 194 | mac[4] = (mac0 >> 8) & 0xff; 195 | mac[5] = mac0 & 0xff; 196 | 197 | return ( 198 | this._d2h(mac[0]) + 199 | ":" + 200 | this._d2h(mac[1]) + 201 | ":" + 202 | this._d2h(mac[2]) + 203 | ":" + 204 | this._d2h(mac[3]) + 205 | ":" + 206 | this._d2h(mac[4]) + 207 | ":" + 208 | this._d2h(mac[5]) 209 | ); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/webserial.ts: -------------------------------------------------------------------------------- 1 | class Transport { 2 | public slip_reader_enabled = false; 3 | public left_over = new Uint8Array(0); 4 | public baudrate = 0; 5 | 6 | constructor(public device: SerialPort) {} 7 | 8 | get_info() { 9 | const info = this.device.getInfo(); 10 | return info.usbVendorId && info.usbProductId 11 | ? `WebSerial VendorID 0x${info.usbVendorId.toString(16)} ProductID 0x${info.usbProductId.toString(16)}` 12 | : ""; 13 | } 14 | 15 | get_pid() { 16 | return this.device.getInfo().usbProductId; 17 | } 18 | 19 | slip_writer(data: Uint8Array) { 20 | let count_esc = 0; 21 | let i = 0, 22 | j = 0; 23 | 24 | for (i = 0; i < data.length; i++) { 25 | if (data[i] === 0xc0 || data[i] === 0xdb) { 26 | count_esc++; 27 | } 28 | } 29 | const out_data = new Uint8Array(2 + count_esc + data.length); 30 | out_data[0] = 0xc0; 31 | j = 1; 32 | for (i = 0; i < data.length; i++, j++) { 33 | if (data[i] === 0xc0) { 34 | out_data[j++] = 0xdb; 35 | out_data[j] = 0xdc; 36 | continue; 37 | } 38 | if (data[i] === 0xdb) { 39 | out_data[j++] = 0xdb; 40 | out_data[j] = 0xdd; 41 | continue; 42 | } 43 | 44 | out_data[j] = data[i]; 45 | } 46 | out_data[j] = 0xc0; 47 | return out_data; 48 | } 49 | 50 | async write(data: Uint8Array) { 51 | const out_data = this.slip_writer(data); 52 | 53 | if (this.device.writable) { 54 | const writer = this.device.writable.getWriter(); 55 | await writer.write(out_data); 56 | writer.releaseLock(); 57 | } 58 | } 59 | 60 | _appendBuffer(buffer1: ArrayBuffer, buffer2: ArrayBuffer) { 61 | const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); 62 | tmp.set(new Uint8Array(buffer1), 0); 63 | tmp.set(new Uint8Array(buffer2), buffer1.byteLength); 64 | return tmp.buffer; 65 | } 66 | 67 | /* this function expects complete packet (hence reader reads for atleast 8 bytes. This function is 68 | * stateless and returns the first wellformed packet only after replacing escape sequence */ 69 | slip_reader(data: Uint8Array) { 70 | let i = 0; 71 | let data_start = 0, 72 | data_end = 0; 73 | let state = "init"; 74 | while (i < data.length) { 75 | if (state === "init" && data[i] == 0xc0) { 76 | data_start = i + 1; 77 | state = "valid_data"; 78 | i++; 79 | continue; 80 | } 81 | if (state === "valid_data" && data[i] == 0xc0) { 82 | data_end = i - 1; 83 | state = "packet_complete"; 84 | break; 85 | } 86 | i++; 87 | } 88 | if (state !== "packet_complete") { 89 | this.left_over = data; 90 | return new Uint8Array(0); 91 | } 92 | 93 | this.left_over = data.slice(data_end + 2); 94 | const temp_pkt = new Uint8Array(data_end - data_start + 1); 95 | let j = 0; 96 | for (i = data_start; i <= data_end; i++, j++) { 97 | if (data[i] === 0xdb && data[i + 1] === 0xdc) { 98 | temp_pkt[j] = 0xc0; 99 | i++; 100 | continue; 101 | } 102 | if (data[i] === 0xdb && data[i + 1] === 0xdd) { 103 | temp_pkt[j] = 0xdb; 104 | i++; 105 | continue; 106 | } 107 | temp_pkt[j] = data[i]; 108 | } 109 | const packet = temp_pkt.slice(0, j); /* Remove unused bytes due to escape seq */ 110 | return packet; 111 | } 112 | 113 | async read(timeout = 0, min_data = 12) { 114 | let t; 115 | let packet = this.left_over; 116 | this.left_over = new Uint8Array(0); 117 | if (this.slip_reader_enabled) { 118 | const val_final = this.slip_reader(packet); 119 | if (val_final.length > 0) { 120 | return val_final; 121 | } 122 | packet = this.left_over; 123 | this.left_over = new Uint8Array(0); 124 | } 125 | if (this.device.readable == null) { 126 | return this.left_over; 127 | } 128 | 129 | const reader = this.device.readable.getReader(); 130 | try { 131 | if (timeout > 0) { 132 | t = setTimeout(function () { 133 | reader.cancel(); 134 | }, timeout); 135 | } 136 | do { 137 | const { value, done } = await reader.read(); 138 | if (done) { 139 | this.left_over = packet; 140 | throw new Error("Timeout"); 141 | } 142 | const p = new Uint8Array(this._appendBuffer(packet.buffer, value.buffer)); 143 | packet = p; 144 | } while (packet.length < min_data); 145 | } finally { 146 | if (timeout > 0) { 147 | clearTimeout(t); 148 | } 149 | reader.releaseLock(); 150 | } 151 | if (this.slip_reader_enabled) { 152 | return this.slip_reader(packet); 153 | } 154 | return packet; 155 | } 156 | 157 | async rawRead(timeout = 0) { 158 | if (this.left_over.length != 0) { 159 | const p = this.left_over; 160 | this.left_over = new Uint8Array(0); 161 | return p; 162 | } 163 | if (!this.device.readable) { 164 | return this.left_over; 165 | } 166 | const reader = this.device.readable.getReader(); 167 | let t; 168 | try { 169 | if (timeout > 0) { 170 | t = setTimeout(function () { 171 | reader.cancel(); 172 | }, timeout); 173 | } 174 | const { value, done } = await reader.read(); 175 | if (done) { 176 | throw new Error("Timeout"); 177 | } 178 | return value; 179 | } finally { 180 | if (timeout > 0) { 181 | clearTimeout(t); 182 | } 183 | reader.releaseLock(); 184 | } 185 | } 186 | 187 | _DTR_state = false; 188 | async setRTS(state: boolean) { 189 | await this.device.setSignals({ requestToSend: state }); 190 | // # Work-around for adapters on Windows using the usbser.sys driver: 191 | // # generate a dummy change to DTR so that the set-control-line-state 192 | // # request is sent with the updated RTS state and the same DTR state 193 | // Referenced to esptool.py 194 | await this.setDTR(this._DTR_state); 195 | } 196 | 197 | async setDTR(state: boolean) { 198 | this._DTR_state = state; 199 | await this.device.setSignals({ dataTerminalReady: state }); 200 | } 201 | 202 | async connect(baud = 115200) { 203 | await this.device.open({ baudRate: baud }); 204 | this.baudrate = baud; 205 | this.left_over = new Uint8Array(0); 206 | } 207 | 208 | async sleep(ms: number) { 209 | return new Promise((resolve) => setTimeout(resolve, ms)); 210 | } 211 | 212 | async waitForUnlock(timeout: number) { 213 | while ( 214 | (this.device.readable && this.device.readable.locked) || 215 | (this.device.writable && this.device.writable.locked) 216 | ) { 217 | await this.sleep(timeout); 218 | } 219 | } 220 | 221 | async disconnect() { 222 | await this.waitForUnlock(400); 223 | await this.device.close(); 224 | } 225 | } 226 | 227 | export { Transport }; 228 | -------------------------------------------------------------------------------- /src/targets/stub_flasher/stub_flasher_32s3.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": 1077381696, 3 | "text": "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", 4 | "text_start": 1077379072, 5 | "data": "ZCvKP8qNN0CvjjdAcJM3QDqPN0DPjjdAOo83QJmPN0BmkDdA2ZA3QIGQN0BVjTdA/I83QFiQN0C8jzdA+5A3QOaPN0D7kDdAnY43QPqON0A6jzdAmY83QLWON0CWjTdAvJE3QDaTN0ByjDdAVpM3QHKMN0ByjDdAcow3QHKMN0ByjDdAcow3QHKMN0ByjDdAVpE3QHKMN0BRkjdANpM3QAQInwAAAAAAAAAYAQQIBQAAAAAAAAAIAQQIBgAAAAAAAAAAAQQIIQAAAAAAIAAAEQQI3AAAAAAAIAAAEQQIDAAAAAAAIAAAAQQIEgAAAAAAIAAAESAoDAAQAQAA", 6 | "data_start": 1070279668 7 | } -------------------------------------------------------------------------------- /examples/typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | const baudrates = document.getElementById("baudrates") as HTMLSelectElement; 2 | const connectButton = document.getElementById("connectButton") as HTMLButtonElement; 3 | const disconnectButton = document.getElementById("disconnectButton") as HTMLButtonElement; 4 | const resetButton = document.getElementById("resetButton") as HTMLButtonElement; 5 | const consoleStartButton = document.getElementById("consoleStartButton") as HTMLButtonElement; 6 | const consoleStopButton = document.getElementById("consoleStopButton") as HTMLButtonElement; 7 | const eraseButton = document.getElementById("eraseButton") as HTMLButtonElement; 8 | const addFileButton = document.getElementById("addFile") as HTMLButtonElement; 9 | const programButton = document.getElementById("programButton"); 10 | const filesDiv = document.getElementById("files"); 11 | const terminal = document.getElementById("terminal"); 12 | const programDiv = document.getElementById("program"); 13 | const consoleDiv = document.getElementById("console"); 14 | const lblBaudrate = document.getElementById("lblBaudrate"); 15 | const lblConsoleFor = document.getElementById("lblConsoleFor"); 16 | const lblConnTo = document.getElementById("lblConnTo"); 17 | const table = document.getElementById("fileTable") as HTMLTableElement; 18 | const alertDiv = document.getElementById("alertDiv"); 19 | 20 | // This is a frontend example of Esptool-JS using local bundle file 21 | // To optimize use a CDN hosted version like 22 | // https://unpkg.com/esptool-js@0.2.0/bundle.js 23 | import { ESPLoader, FlashOptions, LoaderOptions, Transport } from "../../../lib"; 24 | 25 | declare let Terminal; // Terminal is imported in HTML script 26 | declare let CryptoJS; // CryptoJS is imported in HTML script 27 | 28 | const term = new Terminal({ cols: 120, rows: 40 }); 29 | term.open(terminal); 30 | 31 | let device = null; 32 | let transport: Transport; 33 | let chip: string = null; 34 | let esploader: ESPLoader; 35 | 36 | disconnectButton.style.display = "none"; 37 | eraseButton.style.display = "none"; 38 | consoleStopButton.style.display = "none"; 39 | filesDiv.style.display = "none"; 40 | 41 | function handleFileSelect(evt) { 42 | const file = evt.target.files[0]; 43 | 44 | if (!file) return; 45 | 46 | const reader = new FileReader(); 47 | 48 | reader.onload = (ev: ProgressEvent) => { 49 | evt.target.data = ev.target.result; 50 | }; 51 | 52 | reader.readAsBinaryString(file); 53 | } 54 | 55 | const espLoaderTerminal = { 56 | clean() { 57 | term.clear(); 58 | }, 59 | writeLine(data) { 60 | term.writeln(data); 61 | }, 62 | write(data) { 63 | term.write(data); 64 | }, 65 | }; 66 | 67 | connectButton.onclick = async () => { 68 | if (device === null) { 69 | device = await navigator.serial.requestPort({}); 70 | transport = new Transport(device); 71 | } 72 | 73 | try { 74 | const flashOptions = { 75 | transport, 76 | baudrate: parseInt(baudrates.value), 77 | terminal: espLoaderTerminal, 78 | } as LoaderOptions; 79 | esploader = new ESPLoader(flashOptions); 80 | 81 | chip = await esploader.main_fn(); 82 | 83 | // Temporarily broken 84 | // await esploader.flash_id(); 85 | } catch (e) { 86 | console.error(e); 87 | term.writeln(`Error: ${e.message}`); 88 | } 89 | 90 | console.log("Settings done for :" + chip); 91 | lblBaudrate.style.display = "none"; 92 | lblConnTo.innerHTML = "Connected to device: " + chip; 93 | lblConnTo.style.display = "block"; 94 | baudrates.style.display = "none"; 95 | connectButton.style.display = "none"; 96 | disconnectButton.style.display = "initial"; 97 | eraseButton.style.display = "initial"; 98 | filesDiv.style.display = "initial"; 99 | consoleDiv.style.display = "none"; 100 | }; 101 | 102 | resetButton.onclick = async () => { 103 | if (device === null) { 104 | device = await navigator.serial.requestPort({}); 105 | transport = new Transport(device); 106 | } 107 | 108 | await transport.setDTR(false); 109 | await new Promise((resolve) => setTimeout(resolve, 100)); 110 | await transport.setDTR(true); 111 | }; 112 | 113 | eraseButton.onclick = async () => { 114 | eraseButton.disabled = true; 115 | try { 116 | await esploader.erase_flash(); 117 | } catch (e) { 118 | console.error(e); 119 | term.writeln(`Error: ${e.message}`); 120 | } finally { 121 | eraseButton.disabled = false; 122 | } 123 | }; 124 | 125 | addFileButton.onclick = () => { 126 | const rowCount = table.rows.length; 127 | const row = table.insertRow(rowCount); 128 | 129 | //Column 1 - Offset 130 | const cell1 = row.insertCell(0); 131 | const element1 = document.createElement("input"); 132 | element1.type = "text"; 133 | element1.id = "offset" + rowCount; 134 | element1.value = "0x1000"; 135 | cell1.appendChild(element1); 136 | 137 | // Column 2 - File selector 138 | const cell2 = row.insertCell(1); 139 | const element2 = document.createElement("input"); 140 | element2.type = "file"; 141 | element2.id = "selectFile" + rowCount; 142 | element2.name = "selected_File" + rowCount; 143 | element2.addEventListener("change", handleFileSelect, false); 144 | cell2.appendChild(element2); 145 | 146 | // Column 3 - Progress 147 | const cell3 = row.insertCell(2); 148 | cell3.classList.add("progress-cell"); 149 | cell3.style.display = "none"; 150 | cell3.innerHTML = ``; 151 | 152 | // Column 4 - Remove File 153 | const cell4 = row.insertCell(3); 154 | cell4.classList.add("action-cell"); 155 | if (rowCount > 1) { 156 | const element4 = document.createElement("input"); 157 | element4.type = "button"; 158 | const btnName = "button" + rowCount; 159 | element4.name = btnName; 160 | element4.setAttribute("class", "btn"); 161 | element4.setAttribute("value", "Remove"); // or element1.value = "button"; 162 | element4.onclick = function () { 163 | removeRow(row); 164 | }; 165 | cell4.appendChild(element4); 166 | } 167 | }; 168 | 169 | function removeRow(row) { 170 | const rowIndex = Array.from(table.rows).indexOf(row); 171 | table.deleteRow(rowIndex); 172 | } 173 | 174 | // to be called on disconnect - remove any stale references of older connections if any 175 | function cleanUp() { 176 | device = null; 177 | transport = null; 178 | chip = null; 179 | } 180 | 181 | disconnectButton.onclick = async () => { 182 | if (transport) await transport.disconnect(); 183 | 184 | term.clear(); 185 | baudrates.style.display = "initial"; 186 | connectButton.style.display = "initial"; 187 | disconnectButton.style.display = "none"; 188 | eraseButton.style.display = "none"; 189 | lblConnTo.style.display = "none"; 190 | filesDiv.style.display = "none"; 191 | alertDiv.style.display = "none"; 192 | consoleDiv.style.display = "initial"; 193 | cleanUp(); 194 | }; 195 | 196 | let isConsoleClosed = false; 197 | consoleStartButton.onclick = async () => { 198 | if (device === null) { 199 | device = await navigator.serial.requestPort({}); 200 | transport = new Transport(device); 201 | } 202 | lblConsoleFor.style.display = "block"; 203 | consoleStartButton.style.display = "none"; 204 | consoleStopButton.style.display = "initial"; 205 | programDiv.style.display = "none"; 206 | 207 | await transport.connect(); 208 | isConsoleClosed = false; 209 | 210 | while (true && !isConsoleClosed) { 211 | const val = await transport.rawRead(); 212 | if (typeof val !== "undefined") { 213 | term.write(val); 214 | } else { 215 | break; 216 | } 217 | } 218 | console.log("quitting console"); 219 | }; 220 | 221 | consoleStopButton.onclick = async () => { 222 | isConsoleClosed = true; 223 | await transport.disconnect(); 224 | await transport.waitForUnlock(1500); 225 | term.clear(); 226 | consoleStartButton.style.display = "initial"; 227 | consoleStopButton.style.display = "none"; 228 | programDiv.style.display = "initial"; 229 | }; 230 | 231 | function validate_program_inputs() { 232 | const offsetArr = []; 233 | const rowCount = table.rows.length; 234 | let row; 235 | let offset = 0; 236 | let fileData = null; 237 | 238 | // check for mandatory fields 239 | for (let index = 1; index < rowCount; index++) { 240 | row = table.rows[index]; 241 | 242 | //offset fields checks 243 | const offSetObj = row.cells[0].childNodes[0]; 244 | offset = parseInt(offSetObj.value); 245 | 246 | // Non-numeric or blank offset 247 | if (Number.isNaN(offset)) return "Offset field in row " + index + " is not a valid address!"; 248 | // Repeated offset used 249 | else if (offsetArr.includes(offset)) return "Offset field in row " + index + " is already in use!"; 250 | else offsetArr.push(offset); 251 | 252 | const fileObj = row.cells[1].childNodes[0]; 253 | fileData = fileObj.data; 254 | if (fileData == null) return "No file selected for row " + index + "!"; 255 | } 256 | return "success"; 257 | } 258 | 259 | programButton.onclick = async () => { 260 | const alertMsg = document.getElementById("alertmsg"); 261 | const err = validate_program_inputs(); 262 | 263 | if (err != "success") { 264 | alertMsg.innerHTML = "" + err + ""; 265 | alertDiv.style.display = "block"; 266 | return; 267 | } 268 | 269 | // Hide error message 270 | alertDiv.style.display = "none"; 271 | 272 | const fileArray = []; 273 | const progressBars = []; 274 | 275 | for (let index = 1; index < table.rows.length; index++) { 276 | const row = table.rows[index]; 277 | 278 | const offSetObj = row.cells[0].childNodes[0] as HTMLInputElement; 279 | const offset = parseInt(offSetObj.value); 280 | 281 | const fileObj = row.cells[1].childNodes[0] as ChildNode & { data: string }; 282 | const progressBar = row.cells[2].childNodes[0]; 283 | 284 | progressBar.textContent = "0"; 285 | progressBars.push(progressBar); 286 | 287 | row.cells[2].style.display = "initial"; 288 | row.cells[3].style.display = "none"; 289 | 290 | fileArray.push({ data: fileObj.data, address: offset }); 291 | } 292 | 293 | try { 294 | const flashOptions: FlashOptions = { 295 | fileArray: fileArray, 296 | flashSize: "keep", 297 | eraseAll: false, 298 | compress: true, 299 | reportProgress: (fileIndex, written, total) => { 300 | progressBars[fileIndex].value = (written / total) * 100; 301 | }, 302 | calculateMD5Hash: (image) => CryptoJS.MD5(CryptoJS.enc.Latin1.parse(image)), 303 | } as FlashOptions; 304 | await esploader.write_flash(flashOptions); 305 | } catch (e) { 306 | console.error(e); 307 | term.writeln(`Error: ${e.message}`); 308 | } finally { 309 | // Hide progress bars and show erase buttons 310 | for (let index = 1; index < table.rows.length; index++) { 311 | table.rows[index].cells[2].style.display = "none"; 312 | table.rows[index].cells[3].style.display = "initial"; 313 | } 314 | } 315 | }; 316 | 317 | addFileButton.onclick(this); 318 | -------------------------------------------------------------------------------- /src/targets/stub_flasher/stub_flasher_8266.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": 1074843652, 3 | "text": "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", 4 | "text_start": 1074843648, 5 | "data": "CIH+PwUFBAACAwcAAwMLALnXEEDv1xBAHdgQQLrYEEBo5xBAHtkQQHTZEEDA2RBAaOcQQILaEED/2hBAwNsQQGjnEEBo5xBAWNwQQGjnEEA33xBAAOAQQDvgEEBo5xBAaOcQQNfgEEBo5xBAv+EQQGXiEECj4xBAY+QQQDTlEEBo5xBAaOcQQGjnEEBo5xBAYuYQQGjnEEBX5xBAkN0QQI/YEECm5RBAq9oQQPzZEEBo5xBA7OYQQDHnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQGjnEEBo5xBAaOcQQCLaEEBf2hBAvuUQQAEAAAACAAAAAwAAAAQAAAAFAAAABwAAAAkAAAANAAAAEQAAABkAAAAhAAAAMQAAAEEAAABhAAAAgQAAAMEAAAABAQAAgQEAAAECAAABAwAAAQQAAAEGAAABCAAAAQwAAAEQAAABGAAAASAAAAEwAAABQAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAgAAAAIAAAADAAAAAwAAAAQAAAAEAAAABQAAAAUAAAAGAAAABgAAAAcAAAAHAAAACAAAAAgAAAAJAAAACQAAAAoAAAAKAAAACwAAAAsAAAAMAAAADAAAAA0AAAANAAAAAAAAAAAAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAANAAAADwAAABEAAAATAAAAFwAAABsAAAAfAAAAIwAAACsAAAAzAAAAOwAAAEMAAABTAAAAYwAAAHMAAACDAAAAowAAAMMAAADjAAAAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAgAAAAIAAAACAAAAAgAAAAMAAAADAAAAAwAAAAMAAAAEAAAABAAAAAQAAAAEAAAABQAAAAUAAAAFAAAABQAAAAAAAAAAAAAAAAAAABAREgAIBwkGCgULBAwDDQIOAQ8AAQEAAAEAAAAEAAAA", 6 | "data_start": 1073720488 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2019 Espressif Systems (Shanghai) CO LTD 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /src/esploader.ts: -------------------------------------------------------------------------------- 1 | import { ESPError } from "./error"; 2 | import { Data, deflate, Inflate } from "pako"; 3 | import { Transport } from "./webserial"; 4 | import { ROM } from "./targets/rom"; 5 | import { customReset, usbJTAGSerialReset } from "./reset"; 6 | 7 | export interface FlashOptions { 8 | fileArray: { data: string; address: number }[]; 9 | flashSize: string; 10 | flashMode: string; 11 | flashFreq: string; 12 | eraseAll: boolean; 13 | compress: boolean; 14 | reportProgress?: (fileIndex: number, written: number, total: number) => void; 15 | calculateMD5Hash?: (image: string) => string; 16 | } 17 | 18 | export interface LoaderOptions { 19 | transport: Transport; 20 | port: SerialPort; 21 | baudrate: number; 22 | terminal?: IEspLoaderTerminal; 23 | romBaudrate: number; 24 | debugLogging?: boolean; 25 | } 26 | 27 | type FlashReadCallback = ((packet: Uint8Array, progress: number, totalSize: number) => void) | null; 28 | 29 | async function magic2Chip(magic: number): Promise { 30 | switch (magic) { 31 | case 0x00f01d83: { 32 | const { ESP32ROM } = await import("./targets/esp32"); 33 | return new ESP32ROM(); 34 | } 35 | case 0x6921506f: 36 | case 0x1b31506f: { 37 | const { ESP32C3ROM } = await import("./targets/esp32c3"); 38 | return new ESP32C3ROM(); 39 | } 40 | case 0x2ce0806f: { 41 | const { ESP32C6ROM } = await import("./targets/esp32c6"); 42 | return new ESP32C6ROM(); 43 | } 44 | case 0xd7b73e80: { 45 | const { ESP32H2ROM } = await import("./targets/esp32h2"); 46 | return new ESP32H2ROM(); 47 | } 48 | case 0x09: { 49 | const { ESP32S3ROM } = await import("./targets/esp32s3"); 50 | return new ESP32S3ROM(); 51 | } 52 | case 0x000007c6: { 53 | const { ESP32S2ROM } = await import("./targets/esp32s2"); 54 | return new ESP32S2ROM(); 55 | } 56 | case 0xfff0c101: { 57 | const { ESP8266ROM } = await import("./targets/esp8266"); 58 | return new ESP8266ROM(); 59 | } 60 | default: 61 | return null; 62 | } 63 | } 64 | 65 | export interface IEspLoaderTerminal { 66 | clean: () => void; 67 | writeLine: (data: string) => void; 68 | write: (data: string) => void; 69 | } 70 | 71 | export class ESPLoader { 72 | ESP_RAM_BLOCK = 0x1800; 73 | ESP_FLASH_BEGIN = 0x02; 74 | ESP_FLASH_DATA = 0x03; 75 | ESP_FLASH_END = 0x04; 76 | ESP_MEM_BEGIN = 0x05; 77 | ESP_MEM_END = 0x06; 78 | ESP_MEM_DATA = 0x07; 79 | ESP_WRITE_REG = 0x09; 80 | ESP_READ_REG = 0x0a; 81 | 82 | ESP_SPI_ATTACH = 0x0d; 83 | ESP_CHANGE_BAUDRATE = 0x0f; 84 | ESP_FLASH_DEFL_BEGIN = 0x10; 85 | ESP_FLASH_DEFL_DATA = 0x11; 86 | ESP_FLASH_DEFL_END = 0x12; 87 | ESP_SPI_FLASH_MD5 = 0x13; 88 | 89 | // Only Stub supported commands 90 | ESP_ERASE_FLASH = 0xd0; 91 | ESP_ERASE_REGION = 0xd1; 92 | ESP_READ_FLASH = 0xd2; 93 | ESP_RUN_USER_CODE = 0xd3; 94 | 95 | ESP_IMAGE_MAGIC = 0xe9; 96 | ESP_CHECKSUM_MAGIC = 0xef; 97 | 98 | // Response code(s) sent by ROM 99 | ROM_INVALID_RECV_MSG = 0x05; // response if an invalid message is received 100 | 101 | ERASE_REGION_TIMEOUT_PER_MB = 30000; 102 | ERASE_WRITE_TIMEOUT_PER_MB = 40000; 103 | MD5_TIMEOUT_PER_MB = 8000; 104 | CHIP_ERASE_TIMEOUT = 120000; 105 | FLASH_READ_TIMEOUT = 100000; 106 | MAX_TIMEOUT = this.CHIP_ERASE_TIMEOUT * 2; 107 | 108 | CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000; 109 | 110 | DETECTED_FLASH_SIZES: { [key: number]: string } = { 111 | 0x12: "256KB", 112 | 0x13: "512KB", 113 | 0x14: "1MB", 114 | 0x15: "2MB", 115 | 0x16: "4MB", 116 | 0x17: "8MB", 117 | 0x18: "16MB", 118 | }; 119 | 120 | DETECTED_FLASH_SIZES_NUM: { [key: number]: number } = { 121 | 0x12: 256, 122 | 0x13: 512, 123 | 0x14: 1024, 124 | 0x15: 2048, 125 | 0x16: 4096, 126 | 0x17: 8192, 127 | 0x18: 16384, 128 | }; 129 | 130 | USB_JTAG_SERIAL_PID = 0x1001; 131 | 132 | chip!: ROM; 133 | IS_STUB: boolean; 134 | FLASH_WRITE_SIZE: number; 135 | 136 | public transport: Transport; 137 | private baudrate: number; 138 | private terminal?: IEspLoaderTerminal; 139 | private romBaudrate = 115200; 140 | private debugLogging = false; 141 | 142 | constructor(options: LoaderOptions) { 143 | this.IS_STUB = false; 144 | this.FLASH_WRITE_SIZE = 0x4000; 145 | 146 | this.transport = options.transport; 147 | this.baudrate = options.baudrate; 148 | if (options.romBaudrate) { 149 | this.romBaudrate = options.romBaudrate; 150 | } 151 | if (options.terminal) { 152 | this.terminal = options.terminal; 153 | this.terminal.clean(); 154 | } 155 | if (options.debugLogging) { 156 | this.debugLogging = options.debugLogging; 157 | } 158 | if (options.port) { 159 | this.transport = new Transport(options.port); 160 | } 161 | 162 | this.info("esptool.js"); 163 | this.info("Serial port " + this.transport.get_info()); 164 | } 165 | 166 | _sleep(ms: number) { 167 | return new Promise((resolve) => setTimeout(resolve, ms)); 168 | } 169 | 170 | write(str: string, withNewline = true) { 171 | if (this.terminal) { 172 | if (withNewline) { 173 | this.terminal.writeLine(str); 174 | } else { 175 | this.terminal.write(str); 176 | } 177 | } else { 178 | // eslint-disable-next-line no-console 179 | console.log(str); 180 | } 181 | } 182 | 183 | error(str: string, withNewline = true) { 184 | this.write(`Error: ${str}`, withNewline); 185 | } 186 | 187 | info(str: string, withNewline = true) { 188 | this.write(str, withNewline); 189 | } 190 | 191 | debug(str: string, withNewline = true) { 192 | if (this.debugLogging) { 193 | this.write(`Debug: ${str}`, withNewline); 194 | } 195 | } 196 | 197 | _short_to_bytearray(i: number) { 198 | return new Uint8Array([i & 0xff, (i >> 8) & 0xff]); 199 | } 200 | 201 | _int_to_bytearray(i: number): Uint8Array { 202 | return new Uint8Array([i & 0xff, (i >> 8) & 0xff, (i >> 16) & 0xff, (i >> 24) & 0xff]); 203 | } 204 | 205 | _bytearray_to_short(i: number, j: number) { 206 | return i | (j >> 8); 207 | } 208 | 209 | _bytearray_to_int(i: number, j: number, k: number, l: number) { 210 | return i | (j << 8) | (k << 16) | (l << 24); 211 | } 212 | 213 | _appendBuffer(buffer1: ArrayBuffer, buffer2: ArrayBuffer) { 214 | const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); 215 | tmp.set(new Uint8Array(buffer1), 0); 216 | tmp.set(new Uint8Array(buffer2), buffer1.byteLength); 217 | return tmp.buffer; 218 | } 219 | 220 | _appendArray(arr1: Uint8Array, arr2: Uint8Array) { 221 | const c = new Uint8Array(arr1.length + arr2.length); 222 | c.set(arr1, 0); 223 | c.set(arr2, arr1.length); 224 | return c; 225 | } 226 | 227 | ui8ToBstr(u8Array: Uint8Array) { 228 | let b_str = ""; 229 | for (let i = 0; i < u8Array.length; i++) { 230 | b_str += String.fromCharCode(u8Array[i]); 231 | } 232 | return b_str; 233 | } 234 | 235 | bstrToUi8(bStr: string) { 236 | const u8_array = new Uint8Array(bStr.length); 237 | for (let i = 0; i < bStr.length; i++) { 238 | u8_array[i] = bStr.charCodeAt(i); 239 | } 240 | return u8_array; 241 | } 242 | 243 | async flush_input() { 244 | try { 245 | await this.transport.rawRead(200); 246 | } catch (e) { 247 | this.error((e as Error).message); 248 | } 249 | } 250 | 251 | async read_packet(op: number | null = null, timeout = 3000): Promise<[number, Uint8Array]> { 252 | // Check up-to next 100 packets for valid response packet 253 | for (let i = 0; i < 100; i++) { 254 | const p = await this.transport.read(timeout); 255 | const resp = p[0]; 256 | const op_ret = p[1]; 257 | const val = this._bytearray_to_int(p[4], p[5], p[6], p[7]); 258 | const data = p.slice(8); 259 | if (resp == 1) { 260 | if (op == null || op_ret == op) { 261 | return [val, data]; 262 | } else if (data[0] != 0 && data[1] == this.ROM_INVALID_RECV_MSG) { 263 | await this.flush_input(); 264 | throw new ESPError("unsupported command error"); 265 | } 266 | } 267 | } 268 | throw new ESPError("invalid response"); 269 | } 270 | 271 | async command( 272 | op: number | null = null, 273 | data: Uint8Array = new Uint8Array(0), 274 | chk = 0, 275 | waitResponse = true, 276 | timeout = 3000, 277 | ): Promise<[number, Uint8Array]> { 278 | if (op != null) { 279 | const pkt = new Uint8Array(8 + data.length); 280 | pkt[0] = 0x00; 281 | pkt[1] = op; 282 | pkt[2] = this._short_to_bytearray(data.length)[0]; 283 | pkt[3] = this._short_to_bytearray(data.length)[1]; 284 | pkt[4] = this._int_to_bytearray(chk)[0]; 285 | pkt[5] = this._int_to_bytearray(chk)[1]; 286 | pkt[6] = this._int_to_bytearray(chk)[2]; 287 | pkt[7] = this._int_to_bytearray(chk)[3]; 288 | 289 | let i; 290 | for (i = 0; i < data.length; i++) { 291 | pkt[8 + i] = data[i]; 292 | } 293 | await this.transport.write(pkt); 294 | } 295 | 296 | if (!waitResponse) { 297 | return [0, new Uint8Array(0)]; 298 | } 299 | 300 | return this.read_packet(op, timeout); 301 | } 302 | 303 | async read_reg(addr: number, timeout = 3000) { 304 | const pkt = this._int_to_bytearray(addr); 305 | const val = await this.command(this.ESP_READ_REG, pkt, undefined, undefined, timeout); 306 | return val[0]; 307 | } 308 | 309 | async write_reg(addr: number, value: number, mask = 0xffffffff, delay_us = 0, delay_after_us = 0) { 310 | let pkt = this._appendArray(this._int_to_bytearray(addr), this._int_to_bytearray(value)); 311 | pkt = this._appendArray(pkt, this._int_to_bytearray(mask)); 312 | pkt = this._appendArray(pkt, this._int_to_bytearray(delay_us)); 313 | 314 | if (delay_after_us > 0) { 315 | pkt = this._appendArray(pkt, this._int_to_bytearray(this.chip.UART_DATE_REG_ADDR)); 316 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 317 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 318 | pkt = this._appendArray(pkt, this._int_to_bytearray(delay_after_us)); 319 | } 320 | 321 | await this.check_command("write target memory", this.ESP_WRITE_REG, pkt); 322 | } 323 | 324 | async sync() { 325 | this.debug("Sync"); 326 | const cmd = new Uint8Array(36); 327 | let i; 328 | cmd[0] = 0x07; 329 | cmd[1] = 0x07; 330 | cmd[2] = 0x12; 331 | cmd[3] = 0x20; 332 | for (i = 0; i < 32; i++) { 333 | cmd[4 + i] = 0x55; 334 | } 335 | 336 | try { 337 | const resp = await this.command(0x08, cmd, undefined, undefined, 100); 338 | return resp; 339 | } catch (e) { 340 | this.debug("Sync err " + e); 341 | throw e; 342 | } 343 | } 344 | 345 | async _connect_attempt(mode = "default_reset", esp32r0_delay = false) { 346 | this.debug("_connect_attempt " + mode + " " + esp32r0_delay); 347 | if (mode !== "no_reset") { 348 | if (this.transport.get_pid() === this.USB_JTAG_SERIAL_PID) { 349 | // Custom reset sequence, which is required when the device 350 | // is connecting via its USB-JTAG-Serial peripheral 351 | await usbJTAGSerialReset(this.transport); 352 | } else { 353 | const strSequence = esp32r0_delay ? "D0|R1|W100|W2000|D1|R0|W50|D0" : "D0|R1|W100|D1|R0|W50|D0"; 354 | await customReset(this.transport, strSequence); 355 | } 356 | } 357 | let i = 0; 358 | let keepReading = true; 359 | while (keepReading) { 360 | try { 361 | const res = await this.transport.read(1000); 362 | i += res.length; 363 | } catch (e) { 364 | this.debug((e as Error).message); 365 | if (e instanceof Error) { 366 | keepReading = false; 367 | break; 368 | } 369 | } 370 | await this._sleep(50); 371 | } 372 | this.transport.slip_reader_enabled = true; 373 | i = 7; 374 | while (i--) { 375 | try { 376 | const resp = await this.sync(); 377 | this.debug(resp[0].toString()); 378 | return "success"; 379 | } catch (error) { 380 | if (error instanceof Error) { 381 | if (esp32r0_delay) { 382 | this.info("_", false); 383 | } else { 384 | this.info(".", false); 385 | } 386 | } 387 | } 388 | await this._sleep(50); 389 | } 390 | return "error"; 391 | } 392 | 393 | async connect(mode = "default_reset", attempts = 7, detecting = false) { 394 | let i; 395 | let resp; 396 | this.info("Connecting...", false); 397 | await this.transport.connect(this.romBaudrate); 398 | for (i = 0; i < attempts; i++) { 399 | resp = await this._connect_attempt(mode, false); 400 | if (resp === "success") { 401 | break; 402 | } 403 | resp = await this._connect_attempt(mode, true); 404 | if (resp === "success") { 405 | break; 406 | } 407 | } 408 | if (resp !== "success") { 409 | throw new ESPError("Failed to connect with the device"); 410 | } 411 | this.info("\n\r", false); 412 | 413 | if (!detecting) { 414 | const chip_magic_value = (await this.read_reg(0x40001000)) >>> 0; 415 | this.debug("Chip Magic " + chip_magic_value.toString(16)); 416 | const chip = await magic2Chip(chip_magic_value); 417 | if (this.chip === null) { 418 | throw new ESPError(`Unexpected CHIP magic value ${chip_magic_value}. Failed to autodetect chip type.`); 419 | } else { 420 | this.chip = chip as ROM; 421 | } 422 | } 423 | } 424 | 425 | async detect_chip(mode = "default_reset") { 426 | await this.connect(mode); 427 | this.info("Detecting chip type... ", false); 428 | if (this.chip != null) { 429 | this.info(this.chip.CHIP_NAME); 430 | } else { 431 | this.info("unknown!"); 432 | } 433 | } 434 | 435 | async check_command( 436 | op_description = "", 437 | op: number | null = null, 438 | data: Uint8Array = new Uint8Array(0), 439 | chk = 0, 440 | timeout = 3000, 441 | ) { 442 | this.debug("check_command " + op_description); 443 | const resp = await this.command(op, data, chk, undefined, timeout); 444 | if (resp[1].length > 4) { 445 | return resp[1]; 446 | } else { 447 | return resp[0]; 448 | } 449 | } 450 | 451 | async mem_begin(size: number, blocks: number, blocksize: number, offset: number) { 452 | /* XXX: Add check to ensure that STUB is not getting overwritten */ 453 | this.debug("mem_begin " + size + " " + blocks + " " + blocksize + " " + offset.toString(16)); 454 | let pkt = this._appendArray(this._int_to_bytearray(size), this._int_to_bytearray(blocks)); 455 | pkt = this._appendArray(pkt, this._int_to_bytearray(blocksize)); 456 | pkt = this._appendArray(pkt, this._int_to_bytearray(offset)); 457 | await this.check_command("enter RAM download mode", this.ESP_MEM_BEGIN, pkt); 458 | } 459 | 460 | checksum = function (data: Uint8Array) { 461 | let i; 462 | let chk = 0xef; 463 | 464 | for (i = 0; i < data.length; i++) { 465 | chk ^= data[i]; 466 | } 467 | return chk; 468 | }; 469 | 470 | async mem_block(buffer: Uint8Array, seq: number) { 471 | let pkt = this._appendArray(this._int_to_bytearray(buffer.length), this._int_to_bytearray(seq)); 472 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 473 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 474 | pkt = this._appendArray(pkt, buffer); 475 | const checksum = this.checksum(buffer); 476 | await this.check_command("write to target RAM", this.ESP_MEM_DATA, pkt, checksum); 477 | } 478 | 479 | async mem_finish(entrypoint: number) { 480 | const is_entry = entrypoint === 0 ? 1 : 0; 481 | const pkt = this._appendArray(this._int_to_bytearray(is_entry), this._int_to_bytearray(entrypoint)); 482 | await this.check_command("leave RAM download mode", this.ESP_MEM_END, pkt, undefined, 50); // XXX: handle non-stub with diff timeout 483 | } 484 | 485 | async flash_spi_attach(hspi_arg: number) { 486 | const pkt = this._int_to_bytearray(hspi_arg); 487 | await this.check_command("configure SPI flash pins", this.ESP_SPI_ATTACH, pkt); 488 | } 489 | 490 | timeout_per_mb = function (seconds_per_mb: number, size_bytes: number) { 491 | const result = seconds_per_mb * (size_bytes / 1000000); 492 | if (result < 3000) { 493 | return 3000; 494 | } else { 495 | return result; 496 | } 497 | }; 498 | 499 | async flash_begin(size: number, offset: number) { 500 | const num_blocks = Math.floor((size + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE); 501 | const erase_size = this.chip.get_erase_size(offset, size); 502 | 503 | const d = new Date(); 504 | const t1 = d.getTime(); 505 | 506 | let timeout = 3000; 507 | if (this.IS_STUB == false) { 508 | timeout = this.timeout_per_mb(this.ERASE_REGION_TIMEOUT_PER_MB, size); 509 | } 510 | 511 | this.debug( 512 | "flash begin " + erase_size + " " + num_blocks + " " + this.FLASH_WRITE_SIZE + " " + offset + " " + size, 513 | ); 514 | let pkt = this._appendArray(this._int_to_bytearray(erase_size), this._int_to_bytearray(num_blocks)); 515 | pkt = this._appendArray(pkt, this._int_to_bytearray(this.FLASH_WRITE_SIZE)); 516 | pkt = this._appendArray(pkt, this._int_to_bytearray(offset)); 517 | if (this.IS_STUB == false) { 518 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); // XXX: Support encrypted 519 | } 520 | 521 | await this.check_command("enter Flash download mode", this.ESP_FLASH_BEGIN, pkt, undefined, timeout); 522 | 523 | const t2 = d.getTime(); 524 | if (size != 0 && this.IS_STUB == false) { 525 | this.info("Took " + (t2 - t1) / 1000 + "." + ((t2 - t1) % 1000) + "s to erase flash block"); 526 | } 527 | return num_blocks; 528 | } 529 | 530 | async flash_defl_begin(size: number, compsize: number, offset: number) { 531 | const num_blocks = Math.floor((compsize + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE); 532 | const erase_blocks = Math.floor((size + this.FLASH_WRITE_SIZE - 1) / this.FLASH_WRITE_SIZE); 533 | 534 | const d = new Date(); 535 | const t1 = d.getTime(); 536 | 537 | let write_size, timeout; 538 | if (this.IS_STUB) { 539 | write_size = size; 540 | timeout = 3000; 541 | } else { 542 | write_size = erase_blocks * this.FLASH_WRITE_SIZE; 543 | timeout = this.timeout_per_mb(this.ERASE_REGION_TIMEOUT_PER_MB, write_size); 544 | } 545 | this.info("Compressed " + size + " bytes to " + compsize + "..."); 546 | 547 | let pkt = this._appendArray(this._int_to_bytearray(write_size), this._int_to_bytearray(num_blocks)); 548 | pkt = this._appendArray(pkt, this._int_to_bytearray(this.FLASH_WRITE_SIZE)); 549 | pkt = this._appendArray(pkt, this._int_to_bytearray(offset)); 550 | 551 | if ( 552 | (this.chip.CHIP_NAME === "ESP32-S2" || 553 | this.chip.CHIP_NAME === "ESP32-S3" || 554 | this.chip.CHIP_NAME === "ESP32-C3") && 555 | this.IS_STUB === false 556 | ) { 557 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 558 | } 559 | await this.check_command("enter compressed flash mode", this.ESP_FLASH_DEFL_BEGIN, pkt, undefined, timeout); 560 | const t2 = d.getTime(); 561 | if (size != 0 && this.IS_STUB === false) { 562 | this.info("Took " + (t2 - t1) / 1000 + "." + ((t2 - t1) % 1000) + "s to erase flash block"); 563 | } 564 | return num_blocks; 565 | } 566 | 567 | async flash_block(data: Uint8Array, seq: number, timeout: number) { 568 | let pkt = this._appendArray(this._int_to_bytearray(data.length), this._int_to_bytearray(seq)); 569 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 570 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 571 | pkt = this._appendArray(pkt, data); 572 | 573 | const checksum = this.checksum(data); 574 | 575 | await this.check_command("write to target Flash after seq " + seq, this.ESP_FLASH_DATA, pkt, checksum, timeout); 576 | } 577 | 578 | async flash_defl_block(data: Uint8Array, seq: number, timeout: number) { 579 | let pkt = this._appendArray(this._int_to_bytearray(data.length), this._int_to_bytearray(seq)); 580 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 581 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 582 | pkt = this._appendArray(pkt, data); 583 | 584 | const checksum = this.checksum(data); 585 | this.debug("flash_defl_block " + data[0].toString(16) + " " + data[1].toString(16)); 586 | 587 | await this.check_command( 588 | "write compressed data to flash after seq " + seq, 589 | this.ESP_FLASH_DEFL_DATA, 590 | pkt, 591 | checksum, 592 | timeout, 593 | ); 594 | } 595 | 596 | async flash_finish(reboot = false) { 597 | const val = reboot ? 0 : 1; 598 | const pkt = this._int_to_bytearray(val); 599 | 600 | await this.check_command("leave Flash mode", this.ESP_FLASH_END, pkt); 601 | } 602 | 603 | async flash_defl_finish(reboot = false) { 604 | const val = reboot ? 0 : 1; 605 | const pkt = this._int_to_bytearray(val); 606 | 607 | await this.check_command("leave compressed flash mode", this.ESP_FLASH_DEFL_END, pkt); 608 | } 609 | 610 | async run_spiflash_command(spiflash_command: number, data: Uint8Array, read_bits: number) { 611 | // SPI_USR register flags 612 | const SPI_USR_COMMAND = 1 << 31; 613 | const SPI_USR_MISO = 1 << 28; 614 | const SPI_USR_MOSI = 1 << 27; 615 | 616 | // SPI registers, base address differs ESP32* vs 8266 617 | const base = this.chip.SPI_REG_BASE; 618 | const SPI_CMD_REG = base + 0x00; 619 | const SPI_USR_REG = base + this.chip.SPI_USR_OFFS; 620 | const SPI_USR1_REG = base + this.chip.SPI_USR1_OFFS; 621 | const SPI_USR2_REG = base + this.chip.SPI_USR2_OFFS; 622 | const SPI_W0_REG = base + this.chip.SPI_W0_OFFS; 623 | 624 | let set_data_lengths; 625 | if (this.chip.SPI_MOSI_DLEN_OFFS != null) { 626 | set_data_lengths = async (mosi_bits: number, miso_bits: number) => { 627 | const SPI_MOSI_DLEN_REG = base + this.chip.SPI_MOSI_DLEN_OFFS; 628 | const SPI_MISO_DLEN_REG = base + this.chip.SPI_MISO_DLEN_OFFS; 629 | if (mosi_bits > 0) { 630 | await this.write_reg(SPI_MOSI_DLEN_REG, mosi_bits - 1); 631 | } 632 | if (miso_bits > 0) { 633 | await this.write_reg(SPI_MISO_DLEN_REG, miso_bits - 1); 634 | } 635 | }; 636 | } else { 637 | set_data_lengths = async (mosi_bits: number, miso_bits: number) => { 638 | const SPI_DATA_LEN_REG = SPI_USR1_REG; 639 | const SPI_MOSI_BITLEN_S = 17; 640 | const SPI_MISO_BITLEN_S = 8; 641 | const mosi_mask = mosi_bits === 0 ? 0 : mosi_bits - 1; 642 | const miso_mask = miso_bits === 0 ? 0 : miso_bits - 1; 643 | const val = (miso_mask << SPI_MISO_BITLEN_S) | (mosi_mask << SPI_MOSI_BITLEN_S); 644 | await this.write_reg(SPI_DATA_LEN_REG, val); 645 | }; 646 | } 647 | 648 | const SPI_CMD_USR = 1 << 18; 649 | const SPI_USR2_COMMAND_LEN_SHIFT = 28; 650 | if (read_bits > 32) { 651 | throw new ESPError("Reading more than 32 bits back from a SPI flash operation is unsupported"); 652 | } 653 | if (data.length > 64) { 654 | throw new ESPError("Writing more than 64 bytes of data with one SPI command is unsupported"); 655 | } 656 | 657 | const data_bits = data.length * 8; 658 | const old_spi_usr = await this.read_reg(SPI_USR_REG); 659 | const old_spi_usr2 = await this.read_reg(SPI_USR2_REG); 660 | let flags = SPI_USR_COMMAND; 661 | let i; 662 | if (read_bits > 0) { 663 | flags |= SPI_USR_MISO; 664 | } 665 | if (data_bits > 0) { 666 | flags |= SPI_USR_MOSI; 667 | } 668 | await set_data_lengths(data_bits, read_bits); 669 | await this.write_reg(SPI_USR_REG, flags); 670 | let val = (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflash_command; 671 | await this.write_reg(SPI_USR2_REG, val); 672 | if (data_bits == 0) { 673 | await this.write_reg(SPI_W0_REG, 0); 674 | } else { 675 | if (data.length % 4 != 0) { 676 | const padding = new Uint8Array(data.length % 4); 677 | data = this._appendArray(data, padding); 678 | } 679 | let next_reg = SPI_W0_REG; 680 | for (i = 0; i < data.length - 4; i += 4) { 681 | val = this._bytearray_to_int(data[i], data[i + 1], data[i + 2], data[i + 3]); 682 | await this.write_reg(next_reg, val); 683 | next_reg += 4; 684 | } 685 | } 686 | await this.write_reg(SPI_CMD_REG, SPI_CMD_USR); 687 | for (i = 0; i < 10; i++) { 688 | val = (await this.read_reg(SPI_CMD_REG)) & SPI_CMD_USR; 689 | if (val == 0) { 690 | break; 691 | } 692 | } 693 | if (i === 10) { 694 | throw new ESPError("SPI command did not complete in time"); 695 | } 696 | const stat = await this.read_reg(SPI_W0_REG); 697 | await this.write_reg(SPI_USR_REG, old_spi_usr); 698 | await this.write_reg(SPI_USR2_REG, old_spi_usr2); 699 | return stat; 700 | } 701 | 702 | async read_flash_id() { 703 | const SPIFLASH_RDID = 0x9f; 704 | const pkt = new Uint8Array(0); 705 | return await this.run_spiflash_command(SPIFLASH_RDID, pkt, 24); 706 | } 707 | 708 | async erase_flash() { 709 | this.info("Erasing flash (this may take a while)..."); 710 | let d = new Date(); 711 | const t1 = d.getTime(); 712 | const ret = await this.check_command( 713 | "erase flash", 714 | this.ESP_ERASE_FLASH, 715 | undefined, 716 | undefined, 717 | this.CHIP_ERASE_TIMEOUT, 718 | ); 719 | d = new Date(); 720 | const t2 = d.getTime(); 721 | this.info("Chip erase completed successfully in " + (t2 - t1) / 1000 + "s"); 722 | return ret; 723 | } 724 | 725 | toHex(buffer: number | Uint8Array) { 726 | return Array.prototype.map.call(buffer, (x) => ("00" + x.toString(16)).slice(-2)).join(""); 727 | } 728 | 729 | async flash_md5sum(addr: number, size: number) { 730 | const timeout = this.timeout_per_mb(this.MD5_TIMEOUT_PER_MB, size); 731 | let pkt = this._appendArray(this._int_to_bytearray(addr), this._int_to_bytearray(size)); 732 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 733 | pkt = this._appendArray(pkt, this._int_to_bytearray(0)); 734 | 735 | let res = await this.check_command("calculate md5sum", this.ESP_SPI_FLASH_MD5, pkt, undefined, timeout); 736 | if (res instanceof Uint8Array && res.length > 16) { 737 | res = res.slice(0, 16); 738 | } 739 | const strmd5 = this.toHex(res); 740 | return strmd5; 741 | } 742 | 743 | 744 | async read_flash(addr: number, size: number, onPacketReceived: FlashReadCallback = null) { 745 | let pkt = this._appendArray(this._int_to_bytearray(addr), this._int_to_bytearray(size)); 746 | pkt = this._appendArray(pkt, this._int_to_bytearray(0x1000)); 747 | pkt = this._appendArray(pkt, this._int_to_bytearray(1024)); 748 | 749 | const res = await this.check_command("read flash", this.ESP_READ_FLASH, pkt); 750 | 751 | if (res != 0) { 752 | throw new ESPError("Failed to read memory: " + res); 753 | } 754 | 755 | let resp = new Uint8Array(0); 756 | while (resp.length < size) { 757 | const packet = await this.transport.read(this.FLASH_READ_TIMEOUT); 758 | 759 | if (packet instanceof Uint8Array) { 760 | if (packet.length > 0) { 761 | resp = this._appendArray(resp, packet); 762 | await this.transport.write(this._int_to_bytearray(resp.length)); 763 | 764 | if (onPacketReceived) { 765 | onPacketReceived(packet, resp.length, size); 766 | } 767 | } else { 768 | } 769 | } else { 770 | throw new ESPError("Failed to read memory: " + packet); 771 | } 772 | } 773 | 774 | return resp; 775 | } 776 | 777 | async run_stub() { 778 | this.info("Uploading stub..."); 779 | 780 | let decoded = atob(this.chip.ROM_TEXT); 781 | let chardata = decoded.split("").map(function (x) { 782 | return x.charCodeAt(0); 783 | }); 784 | const text = new Uint8Array(chardata); 785 | 786 | decoded = atob(this.chip.ROM_DATA); 787 | chardata = decoded.split("").map(function (x) { 788 | return x.charCodeAt(0); 789 | }); 790 | const data = new Uint8Array(chardata); 791 | 792 | let blocks = Math.floor((text.length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK); 793 | let i; 794 | 795 | await this.mem_begin(text.length, blocks, this.ESP_RAM_BLOCK, this.chip.TEXT_START); 796 | for (i = 0; i < blocks; i++) { 797 | const from_offs = i * this.ESP_RAM_BLOCK; 798 | const to_offs = from_offs + this.ESP_RAM_BLOCK; 799 | await this.mem_block(text.slice(from_offs, to_offs), i); 800 | } 801 | 802 | blocks = Math.floor((data.length + this.ESP_RAM_BLOCK - 1) / this.ESP_RAM_BLOCK); 803 | await this.mem_begin(data.length, blocks, this.ESP_RAM_BLOCK, this.chip.DATA_START); 804 | for (i = 0; i < blocks; i++) { 805 | const from_offs = i * this.ESP_RAM_BLOCK; 806 | const to_offs = from_offs + this.ESP_RAM_BLOCK; 807 | await this.mem_block(data.slice(from_offs, to_offs), i); 808 | } 809 | 810 | this.info("Running stub..."); 811 | await this.mem_finish(this.chip.ENTRY); 812 | 813 | // Check up-to next 100 packets to see if stub is running 814 | for (let i = 0; i < 100; i++) { 815 | const res = await this.transport.read(1000, 6); 816 | if (res[0] === 79 && res[1] === 72 && res[2] === 65 && res[3] === 73) { 817 | this.info("Stub running..."); 818 | this.IS_STUB = true; 819 | this.FLASH_WRITE_SIZE = 0x4000; 820 | return this.chip; 821 | } 822 | } 823 | throw new ESPError("Failed to start stub. Unexpected response"); 824 | } 825 | 826 | async change_baud() { 827 | this.info("Changing baudrate to " + this.baudrate); 828 | const second_arg = this.IS_STUB ? this.transport.baudrate : 0; 829 | const pkt = this._appendArray(this._int_to_bytearray(this.baudrate), this._int_to_bytearray(second_arg)); 830 | const resp = await this.command(this.ESP_CHANGE_BAUDRATE, pkt); 831 | this.debug(resp[0].toString()); 832 | this.info("Changed"); 833 | await this.transport.disconnect(); 834 | await this._sleep(50); 835 | await this.transport.connect(this.baudrate); 836 | 837 | /* original code seemed absolutely unreliable. use retries and less sleep */ 838 | try { 839 | let i = 64; 840 | while (i--) { 841 | try { 842 | await this.sync(); 843 | break; 844 | } catch (error) { 845 | } 846 | await this._sleep(10); 847 | } 848 | } catch (e) { 849 | this.debug((e as Error).message); 850 | } 851 | } 852 | 853 | async main_fn(mode = "default_reset") { 854 | await this.detect_chip(mode); 855 | 856 | const chip = await this.chip.get_chip_description(this); 857 | this.info("Chip is " + chip); 858 | this.info("Features: " + (await this.chip.get_chip_features(this))); 859 | this.info("Crystal is " + (await this.chip.get_crystal_freq(this)) + "MHz"); 860 | this.info("MAC: " + (await this.chip.read_mac(this))); 861 | await this.chip.read_mac(this); 862 | 863 | if (typeof this.chip._post_connect != "undefined") { 864 | await this.chip._post_connect(this); 865 | } 866 | 867 | await this.run_stub(); 868 | 869 | if (this.romBaudrate !== this.baudrate) { 870 | await this.change_baud(); 871 | } 872 | return chip; 873 | } 874 | 875 | flash_size_bytes = function (flash_size: string) { 876 | let flash_size_b = -1; 877 | if (flash_size.indexOf("KB") !== -1) { 878 | flash_size_b = parseInt(flash_size.slice(0, flash_size.indexOf("KB"))) * 1024; 879 | } else if (flash_size.indexOf("MB") !== -1) { 880 | flash_size_b = parseInt(flash_size.slice(0, flash_size.indexOf("MB"))) * 1024 * 1024; 881 | } 882 | return flash_size_b; 883 | }; 884 | 885 | parse_flash_size_arg(flsz: string) { 886 | if (typeof this.chip.FLASH_SIZES[flsz] === "undefined") { 887 | throw new ESPError( 888 | "Flash size " + flsz + " is not supported by this chip type. Supported sizes: " + this.chip.FLASH_SIZES, 889 | ); 890 | } 891 | return this.chip.FLASH_SIZES[flsz]; 892 | } 893 | 894 | _update_image_flash_params( 895 | image: string, 896 | address: number, 897 | flash_size: string, 898 | flash_mode: string, 899 | flash_freq: string, 900 | ) { 901 | this.debug("_update_image_flash_params " + flash_size + " " + flash_mode + " " + flash_freq); 902 | if (image.length < 8) { 903 | return image; 904 | } 905 | if (address != this.chip.BOOTLOADER_FLASH_OFFSET) { 906 | return image; 907 | } 908 | if (flash_size === "keep" && flash_mode === "keep" && flash_freq === "keep") { 909 | this.info("Not changing the image"); 910 | return image; 911 | } 912 | 913 | const magic = parseInt(image[0]); 914 | let a_flash_mode = parseInt(image[2]); 915 | const flash_size_freq = parseInt(image[3]); 916 | if (magic !== this.ESP_IMAGE_MAGIC) { 917 | this.info( 918 | "Warning: Image file at 0x" + 919 | address.toString(16) + 920 | " doesn't look like an image file, so not changing any flash settings.", 921 | ); 922 | return image; 923 | } 924 | 925 | /* XXX: Yet to implement actual image verification */ 926 | 927 | if (flash_mode !== "keep") { 928 | const flash_modes: { [key: string]: number } = { qio: 0, qout: 1, dio: 2, dout: 3 }; 929 | a_flash_mode = flash_modes[flash_mode]; 930 | } 931 | let a_flash_freq = flash_size_freq & 0x0f; 932 | if (flash_freq !== "keep") { 933 | const flash_freqs: { [key: string]: number } = { "40m": 0, "26m": 1, "20m": 2, "80m": 0xf }; 934 | a_flash_freq = flash_freqs[flash_freq]; 935 | } 936 | let a_flash_size = flash_size_freq & 0xf0; 937 | if (flash_size !== "keep") { 938 | a_flash_size = this.parse_flash_size_arg(flash_size); 939 | } 940 | 941 | const flash_params = (a_flash_mode << 8) | (a_flash_freq + a_flash_size); 942 | this.info("Flash params set to " + flash_params.toString(16)); 943 | if (parseInt(image[2]) !== a_flash_mode << 8) { 944 | image = image.substring(0, 2) + (a_flash_mode << 8).toString() + image.substring(2 + 1); 945 | } 946 | if (parseInt(image[3]) !== a_flash_freq + a_flash_size) { 947 | image = image.substring(0, 3) + (a_flash_freq + a_flash_size).toString() + image.substring(3 + 1); 948 | } 949 | return image; 950 | } 951 | 952 | async write_flash(options: FlashOptions) { 953 | this.debug("EspLoader program"); 954 | if (options.flashSize !== "keep") { 955 | const flash_end = this.flash_size_bytes(options.flashSize); 956 | for (let i = 0; i < options.fileArray.length; i++) { 957 | if (options.fileArray[i].data.length + options.fileArray[i].address > flash_end) { 958 | throw new ESPError(`File ${i + 1} doesn't fit in the available flash`); 959 | } 960 | } 961 | } 962 | 963 | if (this.IS_STUB === true && options.eraseAll === true) { 964 | await this.erase_flash(); 965 | } 966 | let image: string, address: number; 967 | for (let i = 0; i < options.fileArray.length; i++) { 968 | this.debug("Data Length " + options.fileArray[i].data.length); 969 | image = options.fileArray[i].data; 970 | const reminder = options.fileArray[i].data.length % 4; 971 | if (reminder > 0) image += "\xff\xff\xff\xff".substring(4 - reminder); 972 | address = options.fileArray[i].address; 973 | this.debug("Image Length " + image.length); 974 | if (image.length === 0) { 975 | this.debug("Warning: File is empty"); 976 | continue; 977 | } 978 | image = this._update_image_flash_params(image, address, options.flashSize, options.flashMode, options.flashFreq); 979 | let calcmd5: string | null = null; 980 | if (options.calculateMD5Hash) { 981 | calcmd5 = options.calculateMD5Hash(image); 982 | this.debug("Image MD5 " + calcmd5); 983 | } 984 | const uncsize = image.length; 985 | let blocks: number; 986 | if (options.compress) { 987 | const uncimage = this.bstrToUi8(image); 988 | image = this.ui8ToBstr(deflate(uncimage, { level: 9 })); 989 | blocks = await this.flash_defl_begin(uncsize, image.length, address); 990 | } else { 991 | blocks = await this.flash_begin(uncsize, address); 992 | } 993 | let seq = 0; 994 | let bytes_sent = 0; 995 | const totalBytes = image.length; 996 | if (options.reportProgress) options.reportProgress(i, 0, totalBytes); 997 | 998 | let d = new Date(); 999 | const t1 = d.getTime(); 1000 | 1001 | let timeout = 5000; 1002 | // Create a decompressor to keep track of the size of uncompressed data 1003 | // to be written in each chunk. 1004 | const inflate = new Inflate({ chunkSize: 1 }); 1005 | let total_len_uncompressed = 0; 1006 | inflate.onData = function (chunk: Data): void { 1007 | total_len_uncompressed += chunk.byteLength; 1008 | }; 1009 | while (image.length > 0) { 1010 | this.debug("Write loop " + address + " " + seq + " " + blocks); 1011 | this.info( 1012 | "Writing at 0x" + 1013 | (address + total_len_uncompressed).toString(16) + 1014 | "... (" + 1015 | Math.floor((100 * (seq + 1)) / blocks) + 1016 | "%)", 1017 | ); 1018 | const block = this.bstrToUi8(image.slice(0, this.FLASH_WRITE_SIZE)); 1019 | 1020 | if (options.compress) { 1021 | const len_uncompressed_previous = total_len_uncompressed; 1022 | inflate.push(block, false); 1023 | const block_uncompressed = total_len_uncompressed - len_uncompressed_previous; 1024 | let block_timeout = 3000; 1025 | if (this.timeout_per_mb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed) > 3000) { 1026 | block_timeout = this.timeout_per_mb(this.ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed); 1027 | } 1028 | if (this.IS_STUB === false) { 1029 | // ROM code writes block to flash before ACKing 1030 | timeout = block_timeout; 1031 | } 1032 | await this.flash_defl_block(block, seq, timeout); 1033 | if (this.IS_STUB) { 1034 | // Stub ACKs when block is received, then writes to flash while receiving the block after it 1035 | timeout = block_timeout; 1036 | } 1037 | } else { 1038 | throw new ESPError("Yet to handle Non Compressed writes"); 1039 | } 1040 | bytes_sent += block.length; 1041 | image = image.slice(this.FLASH_WRITE_SIZE, image.length); 1042 | seq++; 1043 | if (options.reportProgress) options.reportProgress(i, bytes_sent, totalBytes); 1044 | } 1045 | if (this.IS_STUB) { 1046 | await this.read_reg(this.CHIP_DETECT_MAGIC_REG_ADDR, timeout); 1047 | } 1048 | d = new Date(); 1049 | const t = d.getTime() - t1; 1050 | if (options.compress) { 1051 | this.info( 1052 | "Wrote " + 1053 | uncsize + 1054 | " bytes (" + 1055 | bytes_sent + 1056 | " compressed) at 0x" + 1057 | address.toString(16) + 1058 | " in " + 1059 | t / 1000 + 1060 | " seconds.", 1061 | ); 1062 | } 1063 | if (calcmd5) { 1064 | const res = await this.flash_md5sum(address, uncsize); 1065 | if (new String(res).valueOf() != new String(calcmd5).valueOf()) { 1066 | this.info("File md5: " + calcmd5); 1067 | this.info("Flash md5: " + res); 1068 | throw new ESPError("MD5 of file does not match data in flash!"); 1069 | } else { 1070 | this.info("Hash of data verified."); 1071 | } 1072 | } 1073 | } 1074 | this.info("Leaving..."); 1075 | 1076 | if (this.IS_STUB) { 1077 | await this.flash_begin(0, 0); 1078 | if (options.compress) { 1079 | await this.flash_defl_finish(); 1080 | } else { 1081 | await this.flash_finish(); 1082 | } 1083 | } 1084 | } 1085 | 1086 | async flash_id() { 1087 | this.debug("flash_id"); 1088 | const flashid = await this.read_flash_id(); 1089 | this.info("Manufacturer: " + (flashid & 0xff).toString(16)); 1090 | const flid_lowbyte = (flashid >> 16) & 0xff; 1091 | this.info("Device: " + ((flashid >> 8) & 0xff).toString(16) + flid_lowbyte.toString(16)); 1092 | this.info("Detected flash size: " + this.DETECTED_FLASH_SIZES[flid_lowbyte]); 1093 | } 1094 | 1095 | async get_flash_size() { 1096 | this.debug("flash_id"); 1097 | const flashid = await this.read_flash_id(); 1098 | const flid_lowbyte = (flashid >> 16) & 0xff; 1099 | return this.DETECTED_FLASH_SIZES_NUM[flid_lowbyte]; 1100 | } 1101 | 1102 | async hard_reset() { 1103 | await this.transport.setRTS(true); // EN->LOW 1104 | await this._sleep(100); 1105 | await this.transport.setRTS(false); 1106 | } 1107 | 1108 | async soft_reset() { 1109 | if (!this.IS_STUB) { 1110 | // "run user code" is as close to a soft reset as we can do 1111 | await this.flash_begin(0, 0); 1112 | await this.flash_finish(false); 1113 | } else if (this.chip.CHIP_NAME != "ESP8266") { 1114 | throw new ESPError("Soft resetting is currently only supported on ESP8266"); 1115 | } else { 1116 | // running user code from stub loader requires some hacks 1117 | // in the stub loader 1118 | await this.command(this.ESP_RUN_USER_CODE, undefined, undefined, false); 1119 | } 1120 | } 1121 | } 1122 | --------------------------------------------------------------------------------