Convert to NPM package
parent
d9704c87bd
commit
869acb7a1b
@ -0,0 +1,12 @@
|
||||
# Basic dependabot.yml file with
|
||||
# minimum configuration for two package managers
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# Enable version updates for npm
|
||||
- package-ecosystem: "npm"
|
||||
# Look for `package.json` and `lock` files in the `root` directory
|
||||
directory: "/"
|
||||
# Check the npm registry for updates every day (weekdays)
|
||||
schedule:
|
||||
interval: "weekly"
|
@ -0,0 +1,4 @@
|
||||
template: |
|
||||
## What's Changed
|
||||
|
||||
$CHANGES
|
@ -0,0 +1,24 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: script/build
|
||||
- run: yarn prettier --check src
|
@ -0,0 +1,22 @@
|
||||
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
|
||||
|
||||
name: Node.js Package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish-npm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
@ -0,0 +1,14 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
@ -1 +1,3 @@
|
||||
*.DS_Store
|
||||
dist
|
||||
node_modules
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "adafruit-webserial-esptool",
|
||||
"version": "1.0.0",
|
||||
"description": "Browser-based ESPTool using WebSerial",
|
||||
"main": "dist/index.js",
|
||||
"repository": "https://github.com/adafruit/Adafruit_WebSerial_ESPTool",
|
||||
"author": "Adafruit",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepublishOnly": "script/build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"@rollup/plugin-typescript": "^8.2.1",
|
||||
"@types/w3c-web-serial": "^1.0.1",
|
||||
"@types/web-bluetooth": "^0.0.9",
|
||||
"prettier": "^2.3.0",
|
||||
"rollup": "^2.48.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"serve": "^11.3.2",
|
||||
"typescript": "^4.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material/mwc-button": "^0.21.0",
|
||||
"@material/mwc-circular-progress": "^0.21.0",
|
||||
"@material/mwc-dialog": "^0.21.0",
|
||||
"@material/mwc-textfield": "^0.21.0",
|
||||
"lit": "^2.0.0-rc.2",
|
||||
"tslib": "^2.2.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import json from "@rollup/plugin-json";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
|
||||
const config = {
|
||||
input: "dist/index.js",
|
||||
output: {
|
||||
dir: "dist/web",
|
||||
format: "module",
|
||||
},
|
||||
// preserveEntrySignatures: false,
|
||||
plugins: [nodeResolve(), json()],
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
config.plugins.push(
|
||||
terser({
|
||||
ecma: 2019,
|
||||
toplevel: true,
|
||||
output: {
|
||||
comments: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default config;
|
@ -0,0 +1,8 @@
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
rm -rf dist
|
||||
NODE_ENV=production yarn tsc
|
||||
NODE_ENV=production yarn rollup -c
|
@ -0,0 +1,17 @@
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
rm -rf dist
|
||||
|
||||
# Quit all background tasks when script exits
|
||||
trap "kill 0" EXIT
|
||||
|
||||
# Run tsc once as rollup expects those files
|
||||
tsc || true
|
||||
|
||||
yarn serve &
|
||||
yarn tsc --watch &
|
||||
yarn rollup -c --watch &
|
||||
wait
|
@ -0,0 +1,85 @@
|
||||
import { toByteArray } from "./util";
|
||||
|
||||
export interface Logger {
|
||||
log(msg: string, ...args: any[]): void;
|
||||
error(msg: string, ...args: any[]): void;
|
||||
debug(msg: string, ...args: any[]): void;
|
||||
}
|
||||
export const baudRates = [921600, 115200, 230400, 460800];
|
||||
export const flashSizes = {
|
||||
"512KB": 0x00,
|
||||
"256KB": 0x10,
|
||||
"1MB": 0x20,
|
||||
"2MB": 0x30,
|
||||
"4MB": 0x40,
|
||||
"2MB-c1": 0x50,
|
||||
"4MB-c1": 0x60,
|
||||
"8MB": 0x80,
|
||||
"16MB": 0x90,
|
||||
};
|
||||
|
||||
export const FLASH_WRITE_SIZE = 0x200;
|
||||
export const ESP32S2_FLASH_WRITE_SIZE = 0x400;
|
||||
export const FLASH_SECTOR_SIZE = 0x1000; // Flash sector size, minimum unit of erase.
|
||||
export const ESP_ROM_BAUD = 115200;
|
||||
|
||||
export const SYNC_PACKET = toByteArray(
|
||||
"\x07\x07\x12 UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU"
|
||||
);
|
||||
export const CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000;
|
||||
export const CHIP_FAMILY_ESP8266 = 0x8266;
|
||||
export const CHIP_FAMILY_ESP32 = 0x32;
|
||||
export const CHIP_FAMILY_ESP32S2 = 0x3252;
|
||||
export type ChipFamily =
|
||||
| typeof CHIP_FAMILY_ESP8266
|
||||
| typeof CHIP_FAMILY_ESP32
|
||||
| typeof CHIP_FAMILY_ESP32S2;
|
||||
|
||||
export const ESP32_DATAREGVALUE = 0x15122500;
|
||||
export const ESP8266_DATAREGVALUE = 0x00062000;
|
||||
export const ESP32S2_DATAREGVALUE = 0x500;
|
||||
|
||||
// Commands supported by ESP8266 ROM bootloader
|
||||
export const ESP_FLASH_BEGIN = 0x02;
|
||||
export const ESP_FLASH_DATA = 0x03;
|
||||
export const ESP_FLASH_END = 0x04;
|
||||
export const ESP_MEM_BEGIN = 0x05;
|
||||
export const ESP_MEM_END = 0x06;
|
||||
export const ESP_MEM_DATA = 0x07;
|
||||
export const ESP_SYNC = 0x08;
|
||||
export const ESP_WRITE_REG = 0x09;
|
||||
export const ESP_READ_REG = 0x0a;
|
||||
|
||||
export const ESP_ERASE_FLASH = 0xd0;
|
||||
export const ESP_ERASE_REGION = 0xd1;
|
||||
|
||||
export const ESP_SPI_SET_PARAMS = 0x0b;
|
||||
export const ESP_SPI_ATTACH = 0x0d;
|
||||
export const ESP_CHANGE_BAUDRATE = 0x0f;
|
||||
export const ESP_SPI_FLASH_MD5 = 0x13;
|
||||
export const ESP_CHECKSUM_MAGIC = 0xef;
|
||||
|
||||
export const ROM_INVALID_RECV_MSG = 0x05;
|
||||
|
||||
export const USB_RAM_BLOCK = 0x800;
|
||||
export const ESP_RAM_BLOCK = 0x1800;
|
||||
|
||||
// Timeouts
|
||||
export const DEFAULT_TIMEOUT = 3000;
|
||||
export const CHIP_ERASE_TIMEOUT = 600000; // timeout for full chip erase in ms
|
||||
export const MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2; // longest any command can run in ms
|
||||
export const SYNC_TIMEOUT = 100; // timeout for syncing with bootloader in ms
|
||||
export const ERASE_REGION_TIMEOUT_PER_MB = 30000; // timeout (per megabyte) for erasing a region in ms
|
||||
export const MEM_END_ROM_TIMEOUT = 50;
|
||||
|
||||
/**
|
||||
* @name timeoutPerMb
|
||||
* Scales timeouts which are size-specific
|
||||
*/
|
||||
export const timeoutPerMb = (secondsPerMb: number, sizeBytes: number) => {
|
||||
let result = Math.floor(secondsPerMb * (sizeBytes / 0x1e6));
|
||||
if (result < DEFAULT_TIMEOUT) {
|
||||
return DEFAULT_TIMEOUT;
|
||||
}
|
||||
return result;
|
||||
};
|
@ -0,0 +1,836 @@
|
||||
import {
|
||||
CHIP_FAMILY_ESP32,
|
||||
CHIP_FAMILY_ESP32S2,
|
||||
CHIP_FAMILY_ESP8266,
|
||||
MAX_TIMEOUT,
|
||||
Logger,
|
||||
DEFAULT_TIMEOUT,
|
||||
ERASE_REGION_TIMEOUT_PER_MB,
|
||||
ESP32S2_DATAREGVALUE,
|
||||
ESP32S2_FLASH_WRITE_SIZE,
|
||||
ESP32_DATAREGVALUE,
|
||||
ESP8266_DATAREGVALUE,
|
||||
ESP_CHANGE_BAUDRATE,
|
||||
ESP_CHECKSUM_MAGIC,
|
||||
ESP_FLASH_BEGIN,
|
||||
ESP_FLASH_DATA,
|
||||
ESP_FLASH_END,
|
||||
ESP_MEM_BEGIN,
|
||||
ESP_MEM_DATA,
|
||||
ESP_MEM_END,
|
||||
ESP_READ_REG,
|
||||
ESP_SPI_ATTACH,
|
||||
ESP_SPI_SET_PARAMS,
|
||||
ESP_SYNC,
|
||||
FLASH_SECTOR_SIZE,
|
||||
FLASH_WRITE_SIZE,
|
||||
MEM_END_ROM_TIMEOUT,
|
||||
ROM_INVALID_RECV_MSG,
|
||||
SYNC_PACKET,
|
||||
SYNC_TIMEOUT,
|
||||
USB_RAM_BLOCK,
|
||||
ChipFamily,
|
||||
ESP_ERASE_FLASH,
|
||||
CHIP_ERASE_TIMEOUT,
|
||||
timeoutPerMb,
|
||||
} from "./const";
|
||||
import { getStubCode } from "./stubs";
|
||||
import { pack, sleep, slipEncode, toHex, unpack } from "./util";
|
||||
|
||||
export class ESPLoader extends EventTarget {
|
||||
chipFamily!: ChipFamily;
|
||||
chipName: string | null = null;
|
||||
_efuses = new Array(4).fill(0);
|
||||
_flashsize = 4 * 1024 * 1024;
|
||||
debug = false;
|
||||
IS_STUB = false;
|
||||
connected = true;
|
||||
|
||||
__inputBuffer?: number[];
|
||||
private _reader?: ReadableStreamDefaultReader<Uint8Array>;
|
||||
|
||||
constructor(
|
||||
public port: SerialPort,
|
||||
public logger: Logger,
|
||||
private _parent?: ESPLoader
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private get _inputBuffer(): number[] {
|
||||
return this._parent ? this._parent._inputBuffer : this.__inputBuffer!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name chipType
|
||||
* ESP32 or ESP8266 based on which chip type we're talking to
|
||||
*/
|
||||
async initialize() {
|
||||
await this.softReset();
|
||||
|
||||
if (!this._parent) {
|
||||
this.__inputBuffer = [];
|
||||
// Don't await this promise so it doesn't block rest of method.
|
||||
this.readLoop();
|
||||
}
|
||||
await this.sync();
|
||||
|
||||
// Determine chip family
|
||||
let datareg = await this.readRegister(0x60000078);
|
||||
if (datareg == ESP32_DATAREGVALUE) {
|
||||
this.chipFamily = CHIP_FAMILY_ESP32;
|
||||
} else if (datareg == ESP8266_DATAREGVALUE) {
|
||||
this.chipFamily = CHIP_FAMILY_ESP8266;
|
||||
} else if (datareg == ESP32S2_DATAREGVALUE) {
|
||||
this.chipFamily = CHIP_FAMILY_ESP32S2;
|
||||
} else {
|
||||
throw "Unknown Chip.";
|
||||
}
|
||||
|
||||
// Read the OTP data for this chip and store into this.efuses array
|
||||
let baseAddr: number;
|
||||
if (this.chipFamily == CHIP_FAMILY_ESP8266) {
|
||||
baseAddr = 0x3ff00050;
|
||||
} else if (this.chipFamily == CHIP_FAMILY_ESP32) {
|
||||
baseAddr = 0x6001a000;
|
||||
} else if (this.chipFamily == CHIP_FAMILY_ESP32S2) {
|
||||
baseAddr = 0x6001a000;
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this._efuses[i] = await this.readRegister(baseAddr! + 4 * i);
|
||||
}
|
||||
|
||||
// The specific name of the chip, e.g. ESP8266EX, to the best
|
||||
// of our ability to determine without a stub bootloader.
|
||||
if (this.chipFamily == CHIP_FAMILY_ESP32) {
|
||||
this.chipName = "ESP32";
|
||||
}
|
||||
if (this.chipFamily == CHIP_FAMILY_ESP32S2) {
|
||||
this.chipName = "ESP32-S2";
|
||||
}
|
||||
if (this.chipFamily == CHIP_FAMILY_ESP8266) {
|
||||
if (this._efuses[0] & (1 << 4) || this._efuses[2] & (1 << 16)) {
|
||||
this.chipName = "ESP8285";
|
||||
}
|
||||
this.chipName = "ESP8266EX";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name readLoop
|
||||
* Reads data from the input stream and places it in the inputBuffer
|
||||
*/
|
||||
async readLoop() {
|
||||
this._reader = this.port.readable!.getReader();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await this._reader.read();
|
||||
if (done) {
|
||||
this._reader.releaseLock();
|
||||
break;
|
||||
}
|
||||
if (!value || value.length === 0) {
|
||||
continue;
|
||||
}
|
||||
this._inputBuffer.push(...Array.from(value));
|
||||
}
|
||||
} catch (err) {
|
||||
// Disconnected!
|
||||
this.connected = false;
|
||||
this.dispatchEvent(new Event("disconnect"));
|
||||
}
|
||||
}
|
||||
|
||||
async softReset() {
|
||||
this.logger.log("Try soft reset.");
|
||||
await this.port.setSignals({
|
||||
dataTerminalReady: false,
|
||||
requestToSend: true,
|
||||
});
|
||||
await this.port.setSignals({
|
||||
dataTerminalReady: true,
|
||||
requestToSend: false,
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name macAddr
|
||||
* The MAC address burned into the OTP memory of the ESP chip
|
||||
*/
|
||||
macAddr() {
|
||||
let macAddr = new Array(6).fill(0);
|
||||
let mac0 = this._efuses[0];
|
||||
let mac1 = this._efuses[1];
|
||||
let mac2 = this._efuses[2];
|
||||
let mac3 = this._efuses[3];
|
||||
let oui;
|
||||
if (this.chipFamily == CHIP_FAMILY_ESP8266) {
|
||||
if (mac3 != 0) {
|
||||
oui = [(mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff];
|
||||
} else if (((mac1 >> 16) & 0xff) == 0) {
|
||||
oui = [0x18, 0xfe, 0x34];
|
||||
} else if (((mac1 >> 16) & 0xff) == 1) {
|
||||
oui = [0xac, 0xd0, 0x74];
|
||||
} else {
|
||||
throw "Couldnt determine OUI";
|
||||
}
|
||||
|
||||
macAddr[0] = oui[0];
|
||||
macAddr[1] = oui[1];
|
||||
macAddr[2] = oui[2];
|
||||
macAddr[3] = (mac1 >> 8) & 0xff;
|
||||
macAddr[4] = mac1 & 0xff;
|
||||
macAddr[5] = (mac0 >> 24) & 0xff;
|
||||
} else if (this.chipFamily == CHIP_FAMILY_ESP32) {
|
||||
macAddr[0] = (mac2 >> 8) & 0xff;
|
||||
macAddr[1] = mac2 & 0xff;
|
||||
macAddr[2] = (mac1 >> 24) & 0xff;
|
||||
macAddr[3] = (mac1 >> 16) & 0xff;
|
||||
macAddr[4] = (mac1 >> 8) & 0xff;
|
||||
macAddr[5] = mac1 & 0xff;
|
||||
} else if (this.chipFamily == CHIP_FAMILY_ESP32S2) {
|
||||
macAddr[0] = (mac2 >> 8) & 0xff;
|
||||
macAddr[1] = mac2 & 0xff;
|
||||
macAddr[2] = (mac1 >> 24) & 0xff;
|
||||
macAddr[3] = (mac1 >> 16) & 0xff;
|
||||
macAddr[4] = (mac1 >> 8) & 0xff;
|
||||
macAddr[5] = mac1 & 0xff;
|
||||
} else {
|
||||
throw "Unknown chip family";
|
||||
}
|
||||
return macAddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name readRegister
|
||||
* Read a register within the ESP chip RAM, returns a 4-element list
|
||||
*/
|
||||
async readRegister(reg: number) {
|
||||
if (this.debug) {
|
||||
this.logger.debug("Reading Register", reg);
|
||||
}
|
||||
let packet = pack("I", reg);
|
||||
let register = (await this.checkCommand(ESP_READ_REG, packet))[0];
|
||||
return unpack("I", register!)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @name checkCommand
|
||||
* Send a command packet, check that the command succeeded and
|
||||
* return a tuple with the value and data.
|
||||
* See the ESP Serial Protocol for more details on what value/data are
|
||||
*/
|
||||
async checkCommand(
|
||||
opcode: number,
|
||||
buffer: number[],
|
||||
checksum = 0,
|
||||
timeout = DEFAULT_TIMEOUT
|
||||
) {
|
||||
timeout = Math.min(timeout, MAX_TIMEOUT);
|
||||
await this.sendCommand(opcode, buffer, checksum);
|
||||
let [value, data] = await this.getResponse(opcode, timeout);
|
||||
|
||||
if (data === null) {
|
||||
throw "Didn't get enough status bytes";
|
||||
}
|
||||
|
||||
let statusLen = 0;
|
||||
|
||||
if (this.IS_STUB || this.chipFamily == CHIP_FAMILY_ESP8266) {
|
||||
statusLen = 2;
|
||||
} else if (
|
||||
[CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2].includes(this.chipFamily)
|
||||
) {
|
||||
statusLen = 4;
|
||||
} else {
|
||||
if ([2, 4].includes(data.length)) {
|
||||
statusLen = data.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.length < statusLen) {
|
||||
throw "Didn't get enough status bytes";
|
||||
}
|
||||
let status = data.slice(-statusLen, data.length);
|
||||
data = data.slice(0, -statusLen);
|
||||
if (this.debug) {
|
||||
this.logger.debug("status", status);
|
||||
this.logger.debug("value", value);
|
||||
this.logger.debug("data", data);
|
||||
}
|
||||
if (status[0] == 1) {
|
||||
if (status[1] == ROM_INVALID_RECV_MSG) {
|
||||
throw "Invalid (unsupported) command " + toHex(opcode);
|
||||
} else {
|
||||
throw "Command failure error code " + toHex(status[1]);
|
||||
}
|
||||
}
|
||||
return [value, data];
|
||||
}
|
||||
|
||||
/**
|
||||
* @name sendCommand
|
||||
* Send a slip-encoded, checksummed command over the UART,
|
||||
* does not check response
|
||||
*/
|
||||
async sendCommand(opcode: number, buffer: number[], checksum = 0) {
|
||||
//debugMsg("Running Send Command");
|
||||
this._inputBuffer.length = 0; // Reset input buffer
|
||||
let packet = [0xc0, 0x00]; // direction
|
||||
packet.push(opcode);
|
||||
packet = packet.concat(pack("H", buffer.length));
|
||||
packet = packet.concat(slipEncode(pack("I", checksum)));
|
||||
packet = packet.concat(slipEncode(buffer));
|
||||
packet.push(0xc0);
|
||||
if (this.debug) {
|
||||
this.logger.debug(
|
||||
"Writing " +
|
||||
packet.length +
|
||||
" byte" +
|
||||
(packet.length == 1 ? "" : "s") +
|
||||
":",
|
||||
packet
|
||||
);
|
||||
}
|
||||
await this.writeToStream(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name getResponse
|
||||
* Read response data and decodes the slip packet, then parses
|
||||
* out the value/data and returns as a tuple of (value, data) where
|
||||
* each is a list of bytes
|
||||
*/
|
||||
async getResponse(opcode: number, timeout = DEFAULT_TIMEOUT) {
|
||||
let reply: number[] = [];
|
||||
let packetLength = 0;
|
||||
let escapedByte = false;
|
||||
let stamp = Date.now();
|
||||
while (Date.now() - stamp < timeout) {
|
||||
if (this._inputBuffer.length > 0) {
|
||||
let c = this._inputBuffer.shift()!;
|
||||
if (c == 0xdb) {
|
||||
escapedByte = true;
|
||||
} else if (escapedByte) {
|
||||
if (c == 0xdd) {
|
||||
reply.push(0xdc);
|
||||
} else if (c == 0xdc) {
|
||||
reply.push(0xc0);
|
||||
} else {
|
||||
reply = reply.concat([0xdb, c]);
|
||||
}
|
||||
escapedByte = false;
|
||||
} else {
|
||||
reply.push(c);
|
||||
}
|
||||
} else {
|
||||
await sleep(10);
|
||||
}
|
||||
if (reply.length > 0 && reply[0] != 0xc0) {
|
||||
// packets must start with 0xC0
|
||||
reply.shift();
|
||||
}
|
||||
if (reply.length > 1 && reply[1] != 0x01) {
|
||||
reply.shift();
|
||||
}
|
||||
if (reply.length > 2 && reply[2] != opcode) {
|
||||
reply.shift();
|
||||
}
|
||||
if (reply.length > 4) {
|
||||
// get the length
|
||||
packetLength = reply[3] + (reply[4] << 8);
|
||||
}
|
||||
if (reply.length == packetLength + 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we have a complete packet. If not, we timed out.
|
||||
if (reply.length != packetLength + 10) {
|
||||
this.logger.log("Timed out after " + timeout + " milliseconds");
|
||||
return [null, null];
|
||||
}
|
||||
if (this.debug) {
|
||||
this.logger.debug(
|
||||
"Reading " +
|
||||
reply.length +
|
||||
" byte" +
|
||||
(reply.length == 1 ? "" : "s") +
|
||||
":",
|
||||
reply
|
||||
);
|
||||
}
|
||||
let value = reply.slice(5, 9);
|
||||
let data = reply.slice(9, -1);
|
||||
if (this.debug) {
|
||||
this.logger.debug("value:", value, "data:", data);
|
||||
}
|
||||
return [value, data];
|
||||
}
|
||||
|
||||
/**
|
||||
* @name read
|
||||
* Read response data and decodes the slip packet.
|
||||
* Keeps reading until we hit the timeout or get
|
||||
* a packet closing byte
|
||||
*/
|
||||
async readBuffer(timeout = DEFAULT_TIMEOUT) {
|
||||
let reply: number[] = [];
|
||||
// let packetLength = 0;
|
||||
let escapedByte = false;
|
||||
let stamp = Date.now();
|
||||
while (Date.now() - stamp < timeout) {
|
||||
if (this._inputBuffer.length > 0) {
|
||||
let c = this._inputBuffer.shift()!;
|
||||
if (c == 0xdb) {
|
||||
escapedByte = true;
|
||||
} else if (escapedByte) {
|
||||
if (c == 0xdd) {
|
||||
reply.push(0xdc);
|
||||
} else if (c == 0xdc) {
|
||||
reply.push(0xc0);
|
||||
} else {
|
||||
reply = reply.concat([0xdb, c]);
|
||||
}
|
||||
escapedByte = false;
|
||||
} else {
|
||||
reply.push(c);
|
||||
}
|
||||
} else {
|
||||
await sleep(10);
|
||||
}
|
||||
if (reply.length > 0 && reply[0] != 0xc0) {
|
||||
// packets must start with 0xC0
|
||||
reply.shift();
|
||||
}
|
||||
if (reply.length > 1 && reply[reply.length - 1] == 0xc0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we have a complete packet. If not, we timed out.
|
||||
if (reply.length < 2) {
|
||||
this.logger.log("Timed out after " + timeout + " milliseconds");
|
||||
return null;
|
||||
}
|
||||
if (this.debug) {
|
||||
this.logger.debug(
|
||||
"Reading " +
|
||||
reply.length +
|
||||
" byte" +
|
||||
(reply.length == 1 ? "" : "s") +
|
||||
":",
|
||||
reply
|
||||
);
|
||||
}
|
||||
let data = reply.slice(1, -1);
|
||||
if (this.debug) {
|
||||
this.logger.debug("data:", data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name checksum
|
||||
* Calculate checksum of a blob, as it is defined by the ROM
|
||||
*/
|
||||
checksum(data: number[], state = ESP_CHECKSUM_MAGIC) {
|
||||
for (let b of data) {
|
||||
state ^= b;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
async setBaudrate(baud: number) {
|
||||
if (this.chipFamily == CHIP_FAMILY_ESP8266) {
|
||||
this.logger.log("Baud rate can only change on ESP32 and ESP32-S2");
|
||||
} else {
|
||||
this.logger.log("Attempting to change baud rate to " + baud + "...");
|
||||
try {
|
||||
let buffer = pack("<II", baud, 0);
|
||||
await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
|
||||
// this.port.baudRate = baud;
|
||||
await sleep(50);
|
||||
await this.checkCommand(ESP_CHANGE_BAUDRATE, buffer);
|
||||
this.logger.log("Changed baud rate to " + baud);
|
||||
} catch (e) {
|
||||
throw (
|
||||
"Unable to change the baud rate, please try setting the connection speed from " +
|
||||
baud +
|
||||
" to 115200 and reconnecting."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name sync
|
||||
* Put into ROM bootload mode & attempt to synchronize with the
|
||||
* ESP ROM bootloader, we will retry a few times
|
||||
*/
|
||||
async sync() {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let response = await this._sync();
|
||||
if (response) {
|
||||
await sleep(100);
|
||||
return true;
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
|
||||
throw "Couldn't sync to ESP. Try resetting.";
|
||||
}
|
||||
|
||||
/**
|
||||
* @name _sync
|
||||
* Perform a soft-sync using AT sync packets, does not perform
|
||||
* any hardware resetting
|
||||
*/
|
||||
async _sync() {
|
||||
await this.sendCommand(ESP_SYNC, SYNC_PACKET);
|
||||
for (let i = 0; i < 8; i++) {
|
||||
let [_reply, data] = await this.getResponse(ESP_SYNC, SYNC_TIMEOUT);
|
||||
if (data === null) {
|
||||
continue;
|
||||
}
|
||||
if (data.length > 1 && data[0] == 0 && data[1] == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name getFlashWriteSize
|
||||
* Get the Flash write size based on the chip
|
||||
*/
|
||||
getFlashWriteSize() {
|
||||
if (this.chipFamily == CHIP_FAMILY_ESP32S2) {
|
||||
return ESP32S2_FLASH_WRITE_SIZE;
|
||||
}
|
||||
return FLASH_WRITE_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name flashData
|
||||
* Program a full, uncompressed binary file into SPI Flash at
|
||||
* a given offset. If an ESP32 and md5 string is passed in, will also
|
||||
* verify memory. ESP8266 does not have checksum memory verification in
|
||||
* ROM
|
||||
*/
|
||||
async flashData(
|
||||
binaryData: ArrayBuffer,
|
||||
updateProgress: (percentage: number) => void,
|
||||
offset = 0,
|
||||
part = 0
|
||||
) {
|
||||
let filesize = binaryData.byteLength;
|
||||
this.logger.log("\nWriting data with filesize:" + filesize);
|
||||
let blocks = await this.flashBegin(filesize, offset);
|
||||
let block = [];
|
||||
let seq = 0;
|
||||
// let written = 0;
|
||||
// let address = offset;
|
||||
let position = 0;
|
||||
let stamp = Date.now();
|
||||
let flashWriteSize = this.getFlashWriteSize();
|
||||
|
||||
while (filesize - position > 0) {
|
||||
let percentage = Math.floor((100 * (seq + 1)) / blocks);
|
||||
/*logMsg(
|
||||
"Writing at " + toHex(address + seq * flashWriteSize, 8) + "... (" + percentage + " %)"
|
||||
);*/
|
||||
updateProgress(percentage);
|
||||
if (filesize - position >= flashWriteSize) {
|
||||
block = Array.from(
|
||||
new Uint8Array(binaryData, position, flashWriteSize)
|
||||
);
|
||||
} else {
|
||||
// Pad the last block
|
||||
block = Array.from(
|
||||
new Uint8Array(binaryData, position, filesize - position)
|
||||
);
|
||||
block = block.concat(
|
||||
new Array(flashWriteSize - block.length).fill(0xff)
|
||||
);
|
||||
}
|
||||
await this.flashBlock(block, seq, 2000);
|
||||
seq += 1;
|
||||
// written += block.length;
|
||||
position += flashWriteSize;
|
||||
}
|
||||
this.logger.log(
|
||||
"Took " + (Date.now() - stamp) + "ms to write " + filesize + " bytes"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name flashBlock
|
||||
* Send one block of data to program into SPI Flash memory
|
||||
*/
|
||||
async flashBlock(data: number[], seq: number, timeout = 100) {
|
||||
await this.checkCommand(
|
||||
ESP_FLASH_DATA,
|
||||
pack("<IIII", data.length, seq, 0, 0).concat(data),
|
||||
this.checksum(data),
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name flashBegin
|
||||
* Prepare for flashing by attaching SPI chip and erasing the
|
||||
* number of blocks requred.
|
||||
*/
|
||||
async flashBegin(size = 0, offset = 0, encrypted = false) {
|
||||
let eraseSize;
|
||||
let buffer;
|
||||
let flashWriteSize = this.getFlashWriteSize();
|
||||
if ([CHIP_FAMILY_ESP32, CHIP_FAMILY_ESP32S2].includes(this.chipFamily)) {
|
||||
await this.checkCommand(ESP_SPI_ATTACH, new Array(8).fill(0));
|
||||
}
|
||||
if (this.chipFamily == CHIP_FAMILY_ESP32) {
|
||||
// We are hardcoded for 4MB flash on ESP32
|
||||
buffer = pack("<IIIIII", 0, this._flashsize, 0x10000, 4096, 256, 0xffff);
|
||||
await this.checkCommand(ESP_SPI_SET_PARAMS, buffer);
|
||||
}
|
||||
let numBlocks = Math.floor((size + flashWriteSize - 1) / flashWriteSize);
|
||||
if (this.chipFamily == CHIP_FAMILY_ESP8266) {
|
||||
eraseSize = this.getEraseSize(offset, size);
|
||||
} else {
|
||||
eraseSize = size;
|
||||
}
|
||||
|
||||
let timeout;
|
||||
if (this.IS_STUB) {
|
||||
timeout = DEFAULT_TIMEOUT;
|
||||
} else {
|
||||
timeout = timeoutPerMb(ERASE_REGION_TIMEOUT_PER_MB, size);
|
||||
}
|
||||
|
||||
let stamp = Date.now();
|
||||
buffer = pack("<IIII", eraseSize, numBlocks, flashWriteSize, offset);
|
||||
if (this.chipFamily == CHIP_FAMILY_ESP32S2) {
|
||||
buffer = buffer.concat(pack("<I", encrypted ? 1 : 0));
|
||||
}
|
||||
this.logger.log(
|
||||
"Erase size " +
|
||||
eraseSize +
|
||||
", blocks " +
|
||||
numBlocks +
|
||||
", block size " +
|
||||
flashWriteSize +
|
||||
", offset " +
|
||||
toHex(offset, 4) +
|
||||
", encrypted " +
|
||||
(encrypted ? "yes" : "no")
|
||||
);
|
||||
await this.checkCommand(ESP_FLASH_BEGIN, buffer, 0, timeout);
|
||||
if (size != 0 && !this.IS_STUB) {
|
||||
this.logger.log(
|
||||
"Took " + (Date.now() - stamp) + "ms to erase " + numBlocks + " bytes"
|
||||
);
|
||||
}
|
||||
return numBlocks;
|
||||
}
|
||||
|
||||
async flashFinish() {
|
||||
let buffer = pack("<I", 1);
|
||||
await this.checkCommand(ESP_FLASH_END, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name getEraseSize
|
||||
* Calculate an erase size given a specific size in bytes.
|
||||
* Provides a workaround for the bootloader erase bug on ESP8266.
|
||||
*/
|
||||
getEraseSize(offset: number, size: number) {
|
||||
let sectorsPerBlock = 16;
|
||||
let sectorSize = FLASH_SECTOR_SIZE;
|
||||
let numSectors = Math.floor((size + sectorSize - 1) / sectorSize);
|
||||
let startSector = Math.floor(offset / sectorSize);
|
||||
|
||||
let headSectors = sectorsPerBlock - (startSector % sectorsPerBlock);
|
||||
if (numSectors < headSectors) {
|
||||
headSectors = numSectors;
|
||||
}
|
||||
|
||||
if (numSectors < 2 * headSectors) {
|
||||
return Math.floor(((numSectors + 1) / 2) * sectorSize);
|
||||
}
|
||||
|
||||
return (numSectors - headSectors) * sectorSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name memBegin (592)
|
||||
* Start downloading an application image to RAM
|
||||
*/
|
||||
async memBegin(
|
||||
size: number,
|
||||
blocks: number,
|
||||
blocksize: number,
|
||||
offset: number
|
||||
) {
|
||||
return await this.checkCommand(
|
||||
ESP_MEM_BEGIN,
|
||||
pack("<IIII", size, blocks, blocksize, offset)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name memBlock (609)
|
||||
* Send a block of an image to RAM
|
||||
*/
|
||||
async memBlock(data: number[], seq: number) {
|
||||
return await this.checkCommand(
|
||||
ESP_MEM_DATA,
|
||||
pack("<IIII", data.length, seq, 0, 0).concat(data),
|
||||
this.checksum(data)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name memFinish (615)
|
||||
* Leave download mode and run the application
|
||||
*
|
||||
* Sending ESP_MEM_END usually sends a correct response back, however sometimes
|
||||
* (with ROM loader) the executed code may reset the UART or change the baud rate
|
||||
* before the transmit FIFO is empty. So in these cases we set a short timeout and
|
||||
* ignore errors.
|
||||
*/
|
||||
async memFinish(entrypoint = 0) {
|
||||
let timeout = this.IS_STUB ? DEFAULT_TIMEOUT : MEM_END_ROM_TIMEOUT;
|
||||
let data = pack("<II", entrypoint == 0 ? 1 : 0, entrypoint);
|
||||
// try {
|
||||
return await this.checkCommand(ESP_MEM_END, data, 0, timeout);
|
||||
// } catch (err) {
|
||||
// console.error("Error in memFinish", err);
|
||||
// if (this.IS_STUB) {
|
||||
// // raise
|
||||
// }
|
||||
// // pass
|
||||
// }
|
||||
}
|
||||
|
||||
// ESPTool Line 706
|
||||
async runStub(): Promise<EspStubLoader> {
|
||||
const stub = await getStubCode(this.chipFamily);
|
||||
|
||||
// We're transferring over USB, right?
|
||||
let ramBlock = USB_RAM_BLOCK;
|
||||
|
||||
// Upload
|
||||
this.logger.log("Uploading stub...");
|
||||
for (let field of ["text", "data"]) {
|
||||
if (Object.keys(stub).includes(field)) {
|
||||
let offset = stub[field + "_start"];
|
||||
let length = stub[field].length;
|
||||
let blocks = Math.floor((length + ramBlock - 1) / ramBlock);
|
||||
await this.memBegin(length, blocks, ramBlock, offset);
|
||||
for (let seq of Array(blocks).keys()) {
|
||||
let fromOffs = seq * ramBlock;
|
||||
let toOffs = fromOffs + ramBlock;
|
||||
if (toOffs > length) {
|
||||
toOffs = length;
|
||||
}
|
||||
await this.memBlock(stub[field].slice(fromOffs, toOffs), seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.logger.log("Running stub...");
|
||||
await this.memFinish(stub["entry"]);
|
||||
|
||||
const p = await this.readBuffer(100);
|
||||
const pChar = String.fromCharCode(...p!);
|
||||
|
||||
if (pChar != "OHAI") {
|
||||
throw "Failed to start stub. Unexpected response: " + pChar;
|
||||
}
|
||||
this.logger.log("Stub is now running...");
|
||||
const espStubLoader = new EspStubLoader(this.port, this.logger, this);
|
||||
return espStubLoader;
|
||||
}
|
||||
|
||||
async writeToStream(data: number[]) {
|
||||
const writer = this.port.writable!.getWriter();
|
||||
await writer.write(new Uint8Array(data));
|
||||
try {
|
||||
writer.releaseLock();
|
||||
} catch (err) {
|
||||
console.error("Ignoring release lock error", err);
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
if (this._parent) {
|
||||
await this._parent.disconnect();
|
||||
return;
|
||||
}
|
||||
if (this._reader) {
|
||||
await this._reader.cancel();
|
||||
}
|
||||
await this.port.writable!.getWriter().close();
|
||||
await this.port.close();
|
||||
}
|
||||
}
|
||||
|
||||
class EspStubLoader extends ESPLoader {
|
||||
/*
|
||||
The Stubloader has commands that run on the uploaded Stub Code in RAM
|
||||
rather than built in commands.
|
||||
*/
|
||||
IS_STUB = true;
|
||||
|
||||
/**
|
||||
* @name memBegin (592)
|
||||
* Start downloading an application image to RAM
|
||||
*/
|
||||
async memBegin(
|
||||
size: number,
|
||||
blocks: number,
|
||||
blocksize: number,
|
||||
offset: number
|
||||
): Promise<any> {
|
||||
let stub = await getStubCode(this.chipFamily);
|
||||
let load_start = offset;
|
||||
let load_end = offset + size;
|
||||
console.log(load_start, load_end);
|
||||
console.log(
|
||||
stub.data_start,
|
||||
stub.data.length,
|
||||
stub.text_start,
|
||||
stub.text.length
|
||||
);
|
||||
for (let [start, end] of [
|
||||
[stub.data_start, stub.data_start + stub.data.length],
|
||||
[stub.text_start, stub.text_start + stub.text.length],
|
||||
]) {
|
||||
if (load_start < end && load_end > start) {
|
||||
throw (
|
||||
"Software loader is resident at " +
|
||||
toHex(start, 8) +
|
||||
"-" +
|
||||
toHex(end, 8) +
|
||||
". " +
|
||||
"Can't load binary at overlapping address range " +
|
||||
toHex(load_start, 8) +
|
||||
"-" +
|
||||
toHex(load_end, 8) +
|
||||
". " +
|
||||
"Try changing the binary loading address."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name getEraseSize
|
||||
* depending on flash chip model the erase may take this long (maybe longer!)
|
||||
*/
|
||||
async eraseFlash() {
|
||||
await this.checkCommand(ESP_ERASE_FLASH, [], 0, CHIP_ERASE_TIMEOUT);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { ESP_ROM_BAUD, Logger } from "./const";
|
||||
import { ESPLoader } from "./esp_loader";
|
||||
import { formatMacAddr } from "./util";
|
||||
|
||||
export const connect = async (logger: Logger) => {
|
||||
// - Request a port and open a connection.
|
||||
const port = await navigator.serial.requestPort();
|
||||
|
||||
logger.log("Connecting...");
|
||||
await port.open({ baudRate: ESP_ROM_BAUD });
|
||||
|
||||
logger.log("Connected successfully.");
|
||||
|
||||
const esploader = new ESPLoader(port, logger);
|
||||
await esploader.initialize();
|
||||
|
||||
logger.log("Connected to " + esploader.chipName);
|
||||
logger.log("MAC Address: " + formatMacAddr(esploader.macAddr()));
|
||||
|
||||
return esploader;
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"text": "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",
|
||||
"text_start": 1074520064,
|
||||
"entry": 1074521496,
|
||||
"data": "CMD8Pw==",
|
||||
"data_start": 1073605544
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,42 @@
|
||||
import {
|
||||
ChipFamily,
|
||||
CHIP_FAMILY_ESP32,
|
||||
CHIP_FAMILY_ESP32S2,
|
||||
CHIP_FAMILY_ESP8266,
|
||||
} from "../const";
|
||||
import { toByteArray } from "../util";
|
||||
|
||||
interface LoadedStub {
|
||||
text: string;
|
||||
data: string;
|
||||
text_start: number;
|
||||
entry: number;
|
||||
data_start: number;
|
||||
}
|
||||
|
||||
interface Stub {
|
||||
text: number[];
|
||||
data: number[];
|
||||
text_start: number;
|
||||
entry: number;
|
||||
data_start: number;
|
||||
}
|
||||
|
||||
export const getStubCode = async (chipFamily: ChipFamily): Promise<Stub> => {
|
||||
let stubcode!: LoadedStub;
|
||||
|
||||
if (chipFamily == CHIP_FAMILY_ESP32) {
|
||||
stubcode = await import("./esp32.json");
|
||||
} else if (chipFamily == CHIP_FAMILY_ESP32S2) {
|
||||
stubcode = await import("./esp32s2.json");
|
||||
} else if (chipFamily == CHIP_FAMILY_ESP8266) {
|
||||
stubcode = await import("./esp8266.json");
|
||||
}
|
||||
|
||||
// Base64 decode the text and data
|
||||
return {
|
||||
...stubcode,
|
||||
text: toByteArray(atob(stubcode.text)),
|
||||
data: toByteArray(atob(stubcode.data)),
|
||||
};
|
||||
};
|
@ -0,0 +1,112 @@
|
||||
/**
|
||||
* @name slipEncode
|
||||
* Take an array buffer and return back a new array where
|
||||
* 0xdb is replaced with 0xdb 0xdd and 0xc0 is replaced with 0xdb 0xdc
|
||||
*/
|
||||
export const slipEncode = (buffer: number[]): number[] => {
|
||||
let encoded: number[] = [];
|
||||
for (let byte of buffer) {
|
||||
if (byte == 0xdb) {
|
||||
encoded = encoded.concat([0xdb, 0xdd]);
|
||||
} else if (byte == 0xc0) {
|
||||
encoded = encoded.concat([0xdb, 0xdc]);
|
||||
} else {
|
||||
encoded.push(byte);
|
||||
}
|
||||
}
|
||||
return encoded;
|
||||
};
|
||||
|
||||
/**
|
||||
* @name toByteArray
|
||||
* Convert a string to a byte array
|
||||
*/
|
||||
export const toByteArray = (str: string): number[] => {
|
||||
let byteArray: number[] = [];
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let charcode = str.charCodeAt(i);
|
||||
if (charcode <= 0xff) {
|
||||
byteArray.push(charcode);
|
||||
}
|
||||
}
|
||||
return byteArray;
|
||||
};
|
||||
|
||||
export const pack = (format: string, ...data: number[]) => {
|
||||
// let format = args[0];
|
||||
let pointer = 0;
|
||||
// let data = args.slice(1);
|
||||
if (format.replace(/[<>]/, "").length != data.length) {
|
||||
throw new Error("Pack format to Argument count mismatch");
|
||||
}
|
||||
let bytes: number[] = [];
|
||||
let littleEndian = true;
|
||||
|
||||
const pushBytes = (value: number, byteCount: number) => {
|
||||
for (let i = 0; i < byteCount; i++) {
|
||||
if (littleEndian) {
|
||||
bytes.push((value >> (i * 8)) & 0xff);
|
||||
} else {
|
||||
bytes.push((value >> ((byteCount - i) * 8)) & 0xff);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < format.length; i++) {
|
||||
if (format[i] == "<") {
|
||||
littleEndian = true;
|
||||
} else if (format[i] == ">") {
|
||||
littleEndian = false;
|
||||
} else if (format[i] == "B") {
|
||||
pushBytes(data[pointer], 1);
|
||||
pointer++;
|
||||
} else if (format[i] == "H") {
|
||||
pushBytes(data[pointer], 2);
|
||||
pointer++;
|
||||
} else if (format[i] == "I") {
|
||||
pushBytes(data[pointer], 4);
|
||||
pointer++;
|
||||
} else {
|
||||
throw new Error(`Unhandled character "${format[i]}" in pack format`);
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
};
|
||||
|
||||
export const unpack = (format: string, bytes: number[]) => {
|
||||
let pointer = 0;
|
||||
let data = [];
|
||||
for (let c of format) {
|
||||
if (c == "B") {
|
||||
data.push(bytes[pointer] & 0xff);
|
||||
pointer += 1;
|
||||
} else if (c == "H") {
|
||||
data.push((bytes[pointer] & 0xff) | ((bytes[pointer + 1] & 0xff) << 8));
|
||||
pointer += 2;
|
||||
} else if (c == "I") {
|
||||
data.push(
|
||||
(bytes[pointer] & 0xff) |
|
||||
((bytes[pointer + 1] & 0xff) << 8) |
|
||||
((bytes[pointer + 2] & 0xff) << 16) |
|
||||
((bytes[pointer + 3] & 0xff) << 24)
|
||||
);
|
||||
pointer += 4;
|
||||
} else {
|
||||
throw new Error(`Unhandled character "${c}" in unpack format`);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const toHex = (value: number, size = 2) => {
|
||||
return "0x" + value.toString(16).toUpperCase().padStart(size, "0");
|
||||
};
|
||||
|
||||
export const sleep = (ms: number) =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
export const formatMacAddr = (macAddr: number[]) =>
|
||||
macAddr
|
||||
.map((value) => value.toString(16).toUpperCase().padStart(2, "0"))
|
||||
.join(":");
|
@ -1 +0,0 @@
|
||||
{"text": "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", "text_start": 1074520064, "entry": 1074521496, "data": "CMD8Pw==", "data_start": 1073605544}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2019", "dom"],
|
||||
"target": "es2019",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"experimentalDecorators": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
},
|
||||
"include": ["src/*"]
|
||||
}
|
Loading…
Reference in New Issue