Skip to content

Instantly share code, notes, and snippets.

@xk
Created June 28, 2020 09:39
Show Gist options
  • Save xk/49bd4b4b2a0307d729003488a4a87fb5 to your computer and use it in GitHub Desktop.
Save xk/49bd4b4b2a0307d729003488a4a87fb5 to your computer and use it in GitHub Desktop.
Lowsync patch for easier manual flashing. Replace node_modules/lowsync/build/10.index.js with this. Tested, works with an ESP32-CAM.
#!/usr/bin/env node
require('source-map-support').install();
exports.ids = [10];
exports.modules = {
/***/ "./node_modules/@serialport/parser-delimiter/lib/index.js":
/*!****************************************************************!*\
!*** ./node_modules/@serialport/parser-delimiter/lib/index.js ***!
\****************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
const { Transform } = __webpack_require__(/*! stream */ "stream")
/**
* A transform stream that emits data each time a byte sequence is received.
* @extends Transform
* @summary To use the `Delimiter` parser, provide a delimiter as a string, buffer, or array of bytes. Runs in O(n) time.
* @example
const SerialPort = require('serialport')
const Delimiter = require('@serialport/parser-delimiter')
const port = new SerialPort('/dev/tty-usbserial1')
const parser = port.pipe(new Delimiter({ delimiter: '\n' }))
parser.on('data', console.log)
*/
class DelimiterParser extends Transform {
constructor(options = {}) {
super(options)
if (options.delimiter === undefined) {
throw new TypeError('"delimiter" is not a bufferable object')
}
if (options.delimiter.length === 0) {
throw new TypeError('"delimiter" has a 0 or undefined length')
}
this.includeDelimiter = options.includeDelimiter !== undefined ? options.includeDelimiter : false
this.delimiter = Buffer.from(options.delimiter)
this.buffer = Buffer.alloc(0)
}
_transform(chunk, encoding, cb) {
let data = Buffer.concat([this.buffer, chunk])
let position
while ((position = data.indexOf(this.delimiter)) !== -1) {
this.push(data.slice(0, position + (this.includeDelimiter ? this.delimiter.length : 0)))
data = data.slice(position + this.delimiter.length)
}
this.buffer = data
cb()
}
_flush(cb) {
this.push(this.buffer)
this.buffer = Buffer.alloc(0)
cb()
}
}
module.exports = DelimiterParser
/***/ }),
/***/ "./node_modules/@serialport/parser-readline/lib/index.js":
/*!***************************************************************!*\
!*** ./node_modules/@serialport/parser-readline/lib/index.js ***!
\***************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
const DelimiterParser = __webpack_require__(/*! @serialport/parser-delimiter */ "./node_modules/@serialport/parser-delimiter/lib/index.js")
/**
* A transform stream that emits data after a newline delimiter is received.
* @summary To use the `Readline` parser, provide a delimiter (defaults to `\n`). Data is emitted as string controllable by the `encoding` option (defaults to `utf8`).
* @extends DelimiterParser
* @example
const SerialPort = require('serialport')
const Readline = require('@serialport/parser-readline')
const port = new SerialPort('/dev/tty-usbserial1')
const parser = port.pipe(new Readline({ delimiter: '\r\n' }))
parser.on('data', console.log)
*/
class ReadLineParser extends DelimiterParser {
constructor(options) {
const opts = {
delimiter: Buffer.from('\n', 'utf8'),
encoding: 'utf8',
...options,
}
if (typeof opts.delimiter === 'string') {
opts.delimiter = Buffer.from(opts.delimiter, opts.encoding)
}
super(opts)
}
}
module.exports = ReadLineParser
/***/ }),
/***/ "./src/commands/commands/flash.ts":
/*!****************************************!*\
!*** ./src/commands/commands/flash.ts ***!
\****************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! child_process */ "child_process");
/* harmony import */ var child_process__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(child_process__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var cli_progress__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! cli-progress */ "cli-progress");
/* harmony import */ var cli_progress__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(cli_progress__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var fs_extra__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! fs-extra */ "fs-extra");
/* harmony import */ var fs_extra__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs_extra__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var https__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! https */ "https");
/* harmony import */ var https__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(https__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _config_mainConfigFile__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../config/mainConfigFile */ "./src/config/mainConfigFile.ts");
/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! lodash */ "lodash");
/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_5__);
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! os */ "os");
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(os__WEBPACK_IMPORTED_MODULE_6__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! path */ "path");
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_7__);
/* harmony import */ var _runError__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ../../runError */ "./src/runError.ts");
/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! chalk */ "chalk");
/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_9__);
/* harmony import */ var _build__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./build */ "./src/commands/commands/build.ts");
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
const SerialPort = __webpack_require__(/*! serialport */ "serialport");
const Readline = __webpack_require__(/*! @serialport/parser-readline */ "./node_modules/@serialport/parser-readline/lib/index.js");
function check_wrover(path) {
return new Promise((resolve, reject) => {
const port = new SerialPort(path, { baudRate: 115200 });
const parser = new Readline();
port.on('error', reject);
parser.on('error', reject);
port.pipe(parser);
let failTimer = setTimeout(() => {
port.close(() => { });
reject(new Error('ESP32 not responding, timeout'));
}, 10000);
parser.on('data', (line) => {
line = line.trim();
if (line == "NOT_WROVER") {
clearTimeout(failTimer);
port.close(() => {
resolve(false);
});
}
else if (line | 0) {
let size = line | 0;
clearTimeout(failTimer);
port.close(() => {
resolve(size);
});
}
});
// Trigger reset
port.on('open', () => {
port.set({ 'rts': true, 'dtr': false }, () => {
port.set({ 'rts': false, 'dtr': false });
});
});
});
}
/* harmony default export */ __webpack_exports__["default"] = (function ({ port, init, resetNetwork, pro, proKey, firmwareFile, firmwareConfig, params }) {
return __awaiter(this, void 0, void 0, function* () {
let doneErasing = false;
let length;
let downloaded = 0;
let bar;
let finished = false;
function check() {
if (!bar && length && doneErasing && !finished) {
const bar = new cli_progress__WEBPACK_IMPORTED_MODULE_1__["Bar"]({
format: 'Getting signed data |{bar}| {percentage}%',
stream: process.stdout,
barsize: 30
}, cli_progress__WEBPACK_IMPORTED_MODULE_1__["Presets"].shades_classic);
bar.start(length, downloaded);
}
if (bar && finished && doneErasing) {
bar.stop();
}
}
function setDoneErasing() {
doneErasing = true;
check();
}
function setTotalLength(len) {
length = len;
check();
}
function addLength(len) {
downloaded += len;
bar && bar.update(downloaded);
}
function finish() {
finished = true;
check();
}
function get_signed_data(firmwareFile, firmwareConfig, mac, pro, proKey) {
return __awaiter(this, void 0, void 0, function* () {
let firmware;
if (firmwareFile && firmwareConfig)
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('Only one of --firmware-file=.. and --firmware-config.. may be used.');
else if (firmwareFile)
firmware = yield fs_extra__WEBPACK_IMPORTED_MODULE_2__["readFile"](firmwareFile);
else if (firmwareConfig)
firmware = yield Object(_build__WEBPACK_IMPORTED_MODULE_10__["default"])({
type: 'build',
firmwareConfig
}, {
proKey,
mac
});
else
firmware = yield Object(_build__WEBPACK_IMPORTED_MODULE_10__["default"])({
type: 'build'
}, {
stock: true,
pro: pro ? pro : false,
proKey,
mac
});
if (pro === undefined)
pro = (firmware.readUInt8(8) & 8) ? true : false;
return yield new Promise((resolve, reject) => {
const options = {
hostname: 'neonious.com',
port: 8444,
path: '/api/SignFirmware?mac=' + mac + (pro ? '&pro=1' : '') + (proKey ? '&proKey=' + proKey : ''),
method: 'POST',
headers: {
'Content-Type': 'application/firmware'
}
};
let done = false;
let timeout = setTimeout(() => {
if (!done) {
done = true;
try {
req.abort();
}
catch (e) { }
finish();
reject(new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('timeout trying to reach neonious servers'));
}
}, 120000);
let req = Object(https__WEBPACK_IMPORTED_MODULE_3__["request"])(options, (res) => {
if (res.statusCode == 200)
setTotalLength(parseInt(res.headers['content-length']));
let dat = [];
res.on('data', (d) => {
dat.push(d);
if (res.statusCode == 200)
addLength(d.length);
});
res.on('error', (e) => {
if (!done) {
done = true;
finish();
reject(e);
}
});
res.on('end', () => {
done = true;
clearTimeout(timeout);
if (res.statusCode != 200 && dat.length)
reject(new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('From server: ' + Buffer.concat(dat).toString()));
else if (res.statusCode != 200) {
reject(new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('Cannot get firmware from server'));
}
else {
let data = Buffer.concat(dat);
let final = Buffer.concat([data.slice(0, 0xF000), firmware.slice(0x80, 0x1F0080 - 128), data.slice(0xF000), firmware.slice(0x1F0080)]);
finish();
resolve(final);
}
});
}).on('error', (e) => {
if (!done) {
done = true;
finish();
reject(e);
}
});
req.end(firmware.slice(0, 0x1F0080));
});
});
}
function spawnAsync(writestd, prog, args, opts) {
// Faster output than if we redirect to our stdout
if (writestd) {
if (!opts)
opts = {};
opts.stdio = ['inherit', 'inherit', 'inherit'];
}
const p = Object(child_process__WEBPACK_IMPORTED_MODULE_0__["spawn"])(prog, args, opts);
return new Promise((resolve, reject) => {
p.on('error', reject);
let out = '';
if (p.stdout)
p.stdout.on('data', data => {
if (!writestd)
out += data;
else
process.stdout.write(data); // old
});
if (p.stderr)
p.stderr.on('data', data => {
if (!writestd)
out += data;
else
process.stderr.write(data); // old
});
p.on('close', code => {
resolve({ code, out });
});
});
}
function spawnMultiple(writestd, progs, args, opts) {
return __awaiter(this, void 0, void 0, function* () {
for (const prog of progs) {
try {
return yield spawnAsync(writestd, prog, args, opts);
}
catch (e) {
if (e.code !== 'ENOENT' && e.message.indexOf('ENOENT') === -1) {
throw e;
}
}
}
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"](`Python does not seem to be installed. None of the following programs exist, but at least one of them must exist: ${progs.join(', ')}`);
});
}
function call(cmds, sane_check, get_mac, silent) {
return __awaiter(this, void 0, void 0, function* () {
cmds = Array.isArray(cmds) ? cmds : [cmds];
const progs = [];
const argsAfterCmd = [];
if (os__WEBPACK_IMPORTED_MODULE_6__["platform"]() === 'win32') {
progs.push('py', 'python', 'python3');
}
else {
progs.push('/usr/bin/env');
argsAfterCmd.push('python');
}
const { code, out } = yield spawnMultiple(!(sane_check || get_mac) && !silent, progs, [...argsAfterCmd, 'esptool.py', ...params, ...cmds], {
cwd: path__WEBPACK_IMPORTED_MODULE_7__["join"](__dirname, 'esptool')
});
if (sane_check && code) {
console.log(out);
return false;
}
else if (get_mac) {
let pos = out.indexOf('MAC: ');
let posEnd = out.indexOf(os__WEBPACK_IMPORTED_MODULE_6__["EOL"], pos);
let id = out
.substring(pos + 5, posEnd)
.replace(/:/g, '')
.toUpperCase();
if (pos == -1 || posEnd == -1 || id.length != 12) {
console.log(out);
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('Cannot read MAC address of ESP32 chip. Please check connection!');
}
return id;
}
else {
if (code)
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('esptool exited with exit code ' + code + '. Exiting.');
}
});
}
function erase_flash() {
return __awaiter(this, void 0, void 0, function* () {
yield call('erase_flash');
setDoneErasing();
});
}
let portAny;
portAny = port;
if (!portAny) {
portAny = yield _config_mainConfigFile__WEBPACK_IMPORTED_MODULE_4__["configFile"].getKey('flashPort');
if (!portAny)
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('No port specified. Please use --port=.. to specify the port.');
}
params.push('-p');
params.push(portAny.toString());
// Sane check
if ((yield call('version', true)) === false) {
console.log(chalk__WEBPACK_IMPORTED_MODULE_9___default.a.bgRed('*** esptool cannot be used.'));
console.log('Please check if you have Python installed.');
console.log("If yes, please check if you have pyserial installed (if not try 'pip install pyserial' or 'py -m pip install pyserial' depending on the system).");
process.exit(1);
return;
}
let dir = yield fs_extra__WEBPACK_IMPORTED_MODULE_2__["mkdtemp"](path__WEBPACK_IMPORTED_MODULE_7__["join"](os__WEBPACK_IMPORTED_MODULE_6__["tmpdir"](), 'lowsync-'));
let proSupported = false;
// Get mac address
console.log('*** Step 1/3: Probing ESP32 microcontroller');
let mac = (yield call('read_mac', false, true));
let data;
let lowjsFlags;
try {
let systemSize;
let sig;
if (init) {
// Double check if device is an ESP32-WROVER as people just don't understand that this is important...
console.log(' now checking if it is an ESP32-WROVER... (takes a while)');
let wrover_check_path = path__WEBPACK_IMPORTED_MODULE_7__["join"](__dirname, 'wrover_check_mc');
console.log('███ If flashing manually do enter BOOTLOADER now');
yield call('erase_flash', false, false, true);
console.log('███ If flashing manually do enter BOOTLOADER now');
yield call([
'write_flash',
'0xe000',
wrover_check_path + '/ota_data_initial.bin',
'0x1000',
wrover_check_path + '/bootloader.bin',
'0x8000',
wrover_check_path + '/partitions.bin',
'0x10000',
wrover_check_path + '/wrover_check_mc.bin',
], false, false, true);
console.log('███ If flashing manually do RESET now');
systemSize = yield check_wrover(portAny.toString());
if (!systemSize)
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('ESP32 is not an ESP32-WROVER or at least does not have required 4 MB PSRAM!\nPlease check: https://www.lowjs.org/supported-hardware.html');
if (systemSize >= 9 * 1024 * 1024)
proSupported = true;
}
else {
let lwjs_signature_file = path__WEBPACK_IMPORTED_MODULE_7__["join"](dir, 'sig');
yield call(['read_flash', '0x7000', '9', lwjs_signature_file], false, false, true);
sig = yield fs_extra__WEBPACK_IMPORTED_MODULE_2__["readFile"](lwjs_signature_file);
systemSize = sig.readUInt32LE(4);
if (sig.slice(0, 4).toString() != 'lwjs')
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('Current firmware on microcontroller is not based on low.js, please flash with --init option');
}
if (!pro && !firmwareFile && !firmwareConfig) {
// open browser window here, no not wait and ignore any unhandled promise catch handlers
const opn = __webpack_require__(/*! opn */ "opn");
yield opn('https://www.neonious.com/ThankYou', { wait: false }).catch(lodash__WEBPACK_IMPORTED_MODULE_5__["noop"]);
}
// Get signed data based on MAC address and do flash erase in parallel, if requested
if (init) {
console.log('*** Step 2/3: Erasing flash and ' + (firmwareFile ? 'signing' : 'building') + ' image in parallel');
function reflect(promise) {
return promise.then(function (v) { return { v: v, status: "fulfilled" }; }, function (e) { return { e: e, status: "rejected" }; });
}
console.log('███ If flashing manually do enter BOOTLOADER now');
let erase = erase_flash();
try {
data = (yield Promise.all([get_signed_data(firmwareFile, firmwareConfig, mac, pro, proKey), erase]))[0];
}
catch (e) {
yield reflect(erase);
throw e;
}
}
else {
console.log('*** Step 2/3: ' + (firmwareFile ? 'Signing' : 'Building') + ' image');
setDoneErasing();
data = (yield get_signed_data(firmwareFile, firmwareConfig, mac, pro, proKey));
}
// pro && (!custom || ota support)
let newSize = data.readUInt32LE(0x6004);
lowjsFlags = data.readUInt8(0x6008);
let dataAt4xx = (lowjsFlags & 8) && (!(lowjsFlags & 4) || (lowjsFlags & 16));
if (!newSize) {
newSize = systemSize;
let dataMaxLen = systemSize - (dataAt4xx ? 0x400000 : 0x200000);
if (dataAt4xx && (lowjsFlags & (4 | 16)) == (4 | 16))
dataMaxLen = dataMaxLen / 2;
if (data.length - 0x1FF000 > dataMaxLen)
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('Total used flash space is higher than the space available on the device (' + systemSize + ' bytes)');
data.writeUInt32LE(newSize, 0x6004);
}
if (pro !== undefined && ((pro && !(lowjsFlags & 8)) || (!pro && (lowjsFlags & 8))))
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('--pro flag is not identical with setting of firmware file / firmware config');
if (init) {
if (newSize > systemSize)
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('Total used flash space is higher than the space available on the device (' + systemSize + ' bytes)');
}
else {
if ((newSize && newSize != systemSize)
|| (lowjsFlags & (8 | 4 | 16)) != (sig.readUInt8(8) & (8 | 4 | 16)))
throw new _runError__WEBPACK_IMPORTED_MODULE_8__["RunError"]('Current firmware on microcontroller is not compatible to the one being flashed, check firmware config for differences, or erase all data with the additional parameter --init');
}
data.writeUInt8(resetNetwork ? 1 : 0, 0x1FF000 - 21);
console.log('*** Step 3/3: Flashing firmware');
console.log('███ If flashing manually do enter BOOTLOADER now');
// pro && (!costom || ota support)
let params = ['write_flash'];
let partNo = 1;
function push_parts(at, data) {
return __awaiter(this, void 0, void 0, function* () {
let file = path__WEBPACK_IMPORTED_MODULE_7__["join"](dir, 'part' + partNo++);
yield fs_extra__WEBPACK_IMPORTED_MODULE_2__["writeFile"](file, data);
params.push('' + at);
params.push(file);
});
}
if (init) {
if (dataAt4xx) {
yield push_parts(0x1000, data.slice(0, 0x1FF000));
yield push_parts(0x400000, data.slice(0x1FF000));
yield call(params);
}
else {
yield push_parts(0x1000, data);
yield call(params);
}
}
else {
// skip everything below NVS
if (dataAt4xx) {
yield push_parts(0xE000, data.slice(0xD000, 0x1FF000));
yield push_parts(0x400000, data.slice(0x1FF000));
yield call(params);
}
else {
yield push_parts(0xE000, data.slice(0xD000));
yield call(params);
}
}
}
catch (e) {
try {
yield fs_extra__WEBPACK_IMPORTED_MODULE_2__["remove"](dir);
}
catch (e) { }
throw e;
}
try {
yield fs_extra__WEBPACK_IMPORTED_MODULE_2__["remove"](dir);
}
catch (e) { }
if (init) {
console.log('*** Done, low.js flashed. Please give your device a few seconds to reset to factory state');
console.log('███ If flashing manually do RESET now');
}
else if (resetNetwork)
console.log('*** Done, low.js flashed and network settings resetted to factory state');
else
console.log('*** Done, low.js updated');
if ((init || resetNetwork) && !(lowjsFlags & 32)) {
let passHash = data.slice(0x6000 + 16, 0x6000 + 16 + 12);
let pass = '';
for (let i = 0; i < 12; i++) {
let val = ((passHash.readUInt8(i) / 256) * (26 + 26 + 10)) | 0;
if (val < 26)
pass += String.fromCharCode(val + 'a'.charCodeAt(0));
else if (val < 26 + 26)
pass += String.fromCharCode(val - 26 + 'A'.charCodeAt(0));
else
pass += String.fromCharCode(val - (26 + 26) + '0'.charCodeAt(0));
}
console.log('To communicate with your microcontroller, connect to the Wifi:');
console.log('SSID: low.js@ESP32 ' + mac);
console.log('Password: ' + pass);
console.log('In this Wifi, the microcontroller has the IP 192.168.0.1');
}
if (!pro && !firmwareFile && !firmwareConfig && proSupported)
console.log(chalk__WEBPACK_IMPORTED_MODULE_9___default.a.bgYellow('Note: Your device has enough flash space to support low.js Professional with on-board web-based IDE + debugger, over-the-air updating and native modules. Please check https://www.neonious.com/Store for more information!'));
});
});
/***/ })
};;
//# sourceMappingURL=10.index.js.map
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment