123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- "use strict";
- const SerialPort = require("serialport").SerialPort;
- const slip = require("./streams/slip");
- // ../esptool.py --port /dev/cu.SLAB_USBtoUART --baud 115200 \
- // write_flash --flash_freq 80m --flash_mode qio --flash_size 32m \
- // 0x0000 "boot_v1.4(b1).bin" 0x1000 espruino_esp8266_user1.bin \
- // 0x3FC000 esp_init_data_default.bin 0x3FE000 blank.bin
- const commands = {
- CMD0: 0x00,
- CMD1: 0x01,
- FLASH_DOWNLOAD_BEGIN: 0x02,
- FLASH_DOWNLOAD_DATA: 0x03,
- FLASH_DOWNLOAD_DONE: 0x04,
- RAM_DOWNLOAD_BEGIN: 0x05,
- RAM_DOWNLOAD_END: 0x06,
- RAM_DOWNLOAD_DATA: 0x07,
- SYNC_FRAME: 0x08,
- WRITE_REGISTER: 0x09,
- READ_REGISTER: 0x0A,
- SET_FLASH_PARAMS: 0x0B,
- NO_COMMAND: 0xFF
- };
- function commandToKey(command) {
- // value to key
- return Object.keys(commands).find((key) => commands[key] === command);
- }
- const SYNC_FRAME = new Buffer([0x07, 0x07, 0x12, 0x20,
- 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
- 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
- 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
- 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55]);
- var debug = function() {};
- function delay(time) {
- return new Promise((resolve) => {
- debug("Delaying for", time);
- setTimeout(resolve, time);
- });
- }
- function repeatPromise(times, callback) {
- let chain = Promise.resolve();
- for (let i = 0; i < times; i++) {
- chain = chain.then(() => callback());
- }
- return chain;
- }
- class EspBoard {
- constructor(port) {
- this.port = port;
- }
- portSet(options) {
- return new Promise((resolve, reject) => {
- debug("Setting port", options);
- this.port.set(options, (err, result) => {
- if (err) {
- reject(err);
- }
- resolve(result);
- });
- });
- }
- resetIntoBootLoader() {
- // RTS - Request To Send
- // DTR - Data Terminal Ready
- debug("Resetting board");
- return this.portSet({rts: true, dtr:false})
- .then(() => delay(5))
- .then(() => this.portSet({rts: false, dtr: true}))
- .then(() => delay(50))
- .then(() => this.portSet({rts: false, dtr: false}));
- }
- }
- class RomComm {
- constructor(config) {
- if (config.debug) {
- debug = config.debug;
- }
- this._port = new SerialPort(config.portName, {
- baudRate: config.baudRate,
- parity: 'none',
- stopBits: 1,
- xon: false,
- xoff: false,
- rtscts: false,
- dsrdtr: false
- }, false);
- this.bindPort();
- var BoardFactory = config.BoardFactory ? config.BoardFactory : EspBoard;
- this.board = new BoardFactory(this._port);
- this.isOpen = false;
- this.config = config;
- this.isInBootLoader = false;
- }
- bindPort() {
- this._port.on('error', (error) => debug("PORT ERROR", error));
- this.in = new slip.SlipDecoder({debug: debug});
- this.out = new slip.SlipEncoder({debug: debug});
- this._port.pipe(this.in);
- this.out.pipe(this._port);
- this.in.on("data", (data) => {
- if (data.length < 8) {
- debug("Missing header");
- return;
- }
- let headerBytes = data.slice(0, 8);
- let header = this.headerPacketFrom(headerBytes);
- if (header.direction != 0x01) {
- debug("Invaid direction", header.direction);
- return;
- }
- let commandName = commandToKey(header.command);
- let body = data.slice(8, header.size);
- debug("Emitting", commandName, body);
- this.in.emit(commandName, body);
- });
- }
- open() {
- return new Promise((resolve, reject) => {
- this._port.open((error) => {
- debug("Opening port...", this._port);
- if (error) {
- reject(error);
- } else {
- resolve();
- }
- });
- }).then(() => this.connect());
- }
- connectStreamVersion() {
- return this.board.resetIntoBootLoader()
- .then(() => this.sync());
- }
- close() {
- this._port.close();
- this.isOpen = false;
- }
- calculateChecksum(data) {
- // Magic Checksum starts with 0xEF
- var result = 0xEF;
- for (var i = 0; i < data.length; i++) {
- result ^= data[i];
- }
- return result;
- }
- sync(ignoreResponse) {
- debug("Syncing");
- return this.sendCommand(commands.SYNC_FRAME, SYNC_FRAME, ignoreResponse)
- .then((response) => {
- if (!ignoreResponse) {
- debug("Sync response completed!", response);
- this.isInBootLoader = true;
- }
- });
- }
- connect() {
- return repeatPromise(5, () => this._connectAttempt())
- .then(() => this.sync())
- .then(() => this.isInBootLoader);
- }
- _connectAttempt() {
- return this.board.resetIntoBootLoader()
- .then(() => delay(100))
- // And a 5x loop here
- .then(() => repeatPromise(5, () => this._flushAndSync()));
- }
- _flushAndSync() {
- return new Promise((resolve, reject) => {
- this._port.flush((error) => {
- if (error) {
- reject(error);
- }
- debug("Port flushed");
- resolve();
- });
- }).then(() => this.sync(true));
- }
- headerPacketFor(command, data) {
- // https://github.com/igrr/esptool-ck/blob/master/espcomm/espcomm.h#L49
- let buf = new ArrayBuffer(8);
- let dv = new DataView(buf);
- dv.setUint8(0, 0x00);
- dv.setUint8(1, command);
- dv.setUint16(2, data.byteLength, true);
- dv.setUint32(4, this.calculateChecksum(data), true);
- return new Buffer(buf);
- }
- headerPacketFrom(buffer) {
- let header = {};
- header.direction = buffer.readUInt8(0);
- header.command = buffer.readUInt8(1);
- header.size = buffer.readUInt16LE(2);
- header.checksum = buffer.readUInt32LE(4);
- return header;
- }
- // https://github.com/themadinventor/esptool/blob/master/esptool.py#L108
- // https://github.com/igrr/esptool-ck/blob/master/espcomm/espcomm.c#L103
- sendCommand(command, data, ignoreResponse) {
- return new Promise((resolve, reject) => {
- if (command != commands.NO_COMMAND) {
- let sendHeader = this.headerPacketFor(command, data);
- let message = Buffer.concat([sendHeader, data], sendHeader.length + data.length);
- this.out.write(message, 'buffer', (err, res) => {
- delay(5).then(() => {
- this._port.drain((drainErr, results) => {
- debug("Draining", drainErr, results);
- if (ignoreResponse) {
- resolve("Response was ignored");
- }
- });
- });
- });
- }
- if (!ignoreResponse) {
- let commandName = commandToKey(command);
- if (this.in.listeners(commandName).length === 0) {
- debug("Listening once", commandName);
- this.in.once(commandName, (response) => {
- resolve(response);
- });
- } else {
- debug("Someone is already awaiting", commandName);
- }
- }
- });
- }
- }
- module.exports = RomComm;
|