Skip to content

Commit

Permalink
Feature: Add minimal launchpad. (#30)
Browse files Browse the repository at this point in the history
Co-authored-by: Rushikesh Patange <rushikesh.patange@espressif.com>
  • Loading branch information
RushikeshPatange and Rushikesh Patange authored Aug 3, 2023
1 parent 5c52a06 commit 5fbecda
Show file tree
Hide file tree
Showing 4 changed files with 500 additions and 3 deletions.
6 changes: 3 additions & 3 deletions js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const Transport = esptooljs.Transport;

const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);

let term = new Terminal({cols: utilities.getTerminalColumns(mainContainer), rows:23, fontSize: 14});
let term = new Terminal({cols: utilities.getTerminalColumns(mainContainer), rows: 23, fontSize: 14, scrollback: 9999999});
let fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
term.open(terminal);
Expand Down Expand Up @@ -188,7 +188,7 @@ $(function () {
utilities.initializeTooltips();
})

document.getElementById('selectFile1').addEventListener('change', utilities.handleFileSelect, false);
document.getElementById('selectFile1').addEventListener('change', utilities.handleFileSelect);

let espLoaderTerminal = {
clean() {
Expand Down Expand Up @@ -338,7 +338,7 @@ addFile.onclick = async () => {
var btnName = "rem-" + rowCount;
element3.name = btnName;
element3.onclick = function() {
utilities.removeRow(table,btnName);
utilities.removeRow(table, btnName);
return false;
}
cell3.appendChild(element3);
Expand Down
127 changes: 127 additions & 0 deletions minimal-launchpad/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en" class="h-100">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>ESP Launchpad</title>
<!-- Core theme CSS (includes Bootstrap)-->
<link href="../css/styles.css" rel="stylesheet" />
<link href="../css/custom.css" rel="stylesheet" />
<link href="../css/xterm.css" rel="stylesheet" />
<link rel="stylesheet" href="./minimal_ui_styles.css" />
</head>

<body class="d-flex flex-column h-100" style="align-items: center">
<!-- Modal for reset device starts-->

<div id="main" class="maincontainer">
<!-- Responsive navbar-->
<nav class="navbar navbar-expand-lg">
<div class="container">
<h4 class="topic" data-target-tab-panel-id="about">
<img src="../assets/logo-v1.png" class="logo" />
</h4>
</div>
</nav>
<div class="container" id="alert-container" style="display: none;">
<div class="text-center mt-3 intro-text">
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<label style="display:none" id="lblConnTo"></label>
</div>
</div>
</div>
<div style="margin-top: auto">
<button class="connectButton" id="connectButton" tabindex="0" type="button">Connect Your Device<span
class="span-svg"><svg class="svg" focusable="false" aria-hidden="true" viewBox="0 0 24 24"
data-testid="EastIcon">
<path fill="#FFFFFF" d="m15 5-1.41 1.41L18.17 11H2v2h16.17l-4.59 4.59L15 19l7-7-7-7z"></path>
</svg></span></button>
</div>
<div id="spinner" style="display: none">
<div>
<div class="spinner-grow text-secondary bg-opacity-25" role="status">
<span class="sr-only"></span>
</div>
<div class="spinner-grow text-secondary bg-opacity-50" role="status">
<span class="sr-only"></span>
</div>
<div class="spinner-grow text-secondary bg-opacity-75" role="status">
<span class="sr-only"></span>
</div>
</div>
</div>
<div class="container">
<div class="row split">
<div class="productinfocontainer" id="productInfoContainer">
<div id="message" style="display: none"></div>
</div>
<div class="col-12 terminalcontainer" style="overflow: hidden; display: none;" id="terminalContainer">
<div class="button-container text-end mb-sm-3">
<div class="field-container" data-toggle="tooltip" data-placement="right"
title="Restart your device" style="display: inline-block">
<button type="button" class="app-button btn btn-outline-dark" id="consoleStartButton"
disabled>
Restart Device
</button>
</div>
</div>
<div id="terminal"><span class="devicelog">Device logs</span></div>
</div>
</div>
</div>
</div>

<!-- Safari & FireFox not supported error message-->
<div id="unsupportedBrowserErr" style="display: none">
<p align="center" style="color: red">
This tool is not supported on Safari & Firefox browsers yet! Please try
using another browser like Google Chrome.
<br />
<br />
ESP Launchpad makes use of WebUSB to communicate with the device.
Currently Safari & Firefox browsers don't support it yet. We will add
support for this browser as soon as Safari & Firefox starts supporting
WebUSB.
</p>
</div>


<!-- Third party libraries-->
<script src="../node_modules/xterm/lib/xterm.js"></script>
<script src="../node_modules/xterm-addon-fit/lib/xterm-addon-fit.js"></script>
<script src="../node_modules/crypto-js/crypto-js.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.3/pako.js"></script>
<script type="text/javascript" src="../node_modules/toml-js/src/toml.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js"></script>

<script src="./minimal_ui_index.js" type="module"></script>
<!-- Bootstrap core JS-->
<script src="../js/jquery.min.js"></script>
<script src="../js/bootstrap.bundle.min.js"></script>
<script src="../js/qrcode.min.js"></script>
<script>
// Safari 3.0+ "[object HTMLElementConstructor]"
var isSafari =
/constructor/i.test(window.HTMLElement) ||
(function (p) {
return p.toString() === "[object SafariRemoteNotification]";
})(
!window["safari"] ||
(typeof safari !== "undefined" && window["safari"].pushNotification)
);

var isFirefox = typeof InstallTrigger !== "undefined";

if (isSafari || isFirefox) {
document.getElementById("unsupportedBrowserErr").style.display =
"inline";
document.getElementById("main").style.display = "none";
}
</script>
</body>

</html>
233 changes: 233 additions & 0 deletions minimal-launchpad/minimal_ui_index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
const connectButton = document.getElementById("connectButton");
const consoleStartButton = document.getElementById("consoleStartButton");
const terminal = document.getElementById("terminal");
const spinner = document.getElementById("spinner");
const productInfoContainer = document.getElementById("productInfoContainer");
const terminalContainer = document.getElementById("terminalContainer");
const alertContainer = document.getElementById("alert-container");
const lblConnTo = document.getElementById("lblConnTo");
const message = document.getElementById("message");

import * as utilities from "../js/utils.js"
import * as esptooljs from "../node_modules/esptool-js/bundle.js";
const ESPLoader = esptooljs.ESPLoader;
const Transport = esptooljs.Transport;

let term = new Terminal({ cols: utilities.getTerminalColumns(), rows: 23, fontSize: 14, scrollback: 9999999 });
let fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
term.open(terminal);
fitAddon.fit();

let device = null;
let transport;
let chip = "default";
let chipDesc = "default";
let esploader;
let connected = false;
let resizeTimeout = false;
let imagePartsArray = undefined;
let imagePartsOffsetArray = undefined;
let reader = undefined;
var config = [];

// Code for minimalLaunchpad
function setImagePartsAndOffsetArray() {
let application = "supported_apps";
let chipInConfToml = undefined;
let imageString = undefined;
let addressString = undefined;
if (chip !== "default" && config["multipart"]) {
chipInConfToml = config["chip"];
}
if (chip !== "default" && chipInConfToml !== undefined) {
imageString = "image." + chipInConfToml.toLowerCase() + ".parts";
addressString = "image." + chipInConfToml.toLowerCase() + ".addresses";
}
imagePartsArray = config[config[application][0]][imageString];
imagePartsOffsetArray = config[config[application][0]][addressString];
}

async function downloadAndFlash() {
let fileArr = []
for (let index = 0; index < imagePartsArray.length; index++) {
let data = await utilities.getImageData(imagePartsArray[index]);
fileArr.push({ data: data, address: imagePartsOffsetArray[index] });
}
try {
const flashOptions = {
fileArray: fileArr,
flashSize: "keep",
flashMode: undefined,
flashFreq: undefined,
eraseAll: false,
compress: true,
};
await esploader.write_flash(flashOptions);
} catch (error) {
}
}

function MDtoHtml() {
let application = "supported_apps";
var converter = new showdown.Converter({ tables: true });
converter.setFlavor('github');
try {
fetch(config[config[application][0]]["readme.text"]).then(response => {
return response.text();
}).then(result => {
let htmlText = converter.makeHtml(result);
message.innerHTML = htmlText;
consoleStartButton.click()
message.style.display = "block";
productInfoContainer.classList.add("col-6", "slide-up");
terminalContainer.classList.remove("col-12", "fade-in");
terminalContainer.classList.add("col-6", "slide-right");
utilities.resizeTerminal(fitAddon);

})
} catch (error) {
message.style.display = "none";
}
}
// Build the Minimal Launchpad UI using the config toml file.
async function buildMinimalLaunchpadUI() {
let tomlFileURL = undefined;
const urlParams = new URLSearchParams(window.location.search);
const url = window.location.search;
const parameter = "flashConfigURL";
if (url.includes("&")) {
tomlFileURL = url.substring(url.search(parameter) + parameter.length + 1);
} else {
tomlFileURL = urlParams.get(parameter);
}
var xhr = new XMLHttpRequest();
xhr.open('GET', tomlFileURL, true);
xhr.send();
xhr.onload = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
config = toml.parse(xhr.responseText);
return config;
}
}
}

config = await buildMinimalLaunchpadUI();

$(function () {
utilities.initializeTooltips();
})

let espLoaderTerminal = {
clean() {
term.clear();
},
writeLine(data) {
term.writeln(data);
},
write(data) {
term.write(data)
}
}

async function connectToDevice() {
connectButton.style.display = "none";
if (device === null) {
device = await navigator.serial.requestPort({
filters: utilities.usbPortFilters
});
transport = new Transport(device);
}
spinner.style.display = "flex";
spinner.style.flexDirection = "column";
spinner.style.alignItems = "center";

try {
const loaderOptions = {
transport: transport,
baudrate: 921600,
terminal: espLoaderTerminal
};
esploader = new ESPLoader(loaderOptions);
connected = true;

chipDesc = await esploader.main_fn();
chip = esploader.chip.CHIP_NAME;

await esploader.flash_id();
} catch (e) {
}
spinner.style.display = "none";
}

connectButton.onclick = async () => {
try {
if (!connected)
await connectToDevice();
if (chipDesc !== "default") {
terminalContainer.classList.add("fade-in");
terminalContainer.style.display = 'initial'
setImagePartsAndOffsetArray();
await downloadAndFlash();
consoleStartButton.disabled = false;
MDtoHtml();
setTimeout(() => {
productInfoContainer.classList.add("bounce");
}, 2500)
} else {
alertContainer.style.display = "initial";
lblConnTo.innerHTML = "<b><span style='color:red'>Unable to detect device. Please ensure the device is not connected in another application</span></b>";
lblConnTo.style.display = "block";
setTimeout(() => {
alertContainer.style.display = "none";
connectButton.style.display = "initial";
connected = false;
}, 5000)
}
} catch (error) {
if (error.message === "Failed to execute 'requestPort' on 'Serial': No port selected by the user.") {
connectButton.style.display = "initial";
}
}

}

consoleStartButton.onclick = async () => {
if (transport) {
if (reader !== undefined) {
reader.releaseLock();
}
if (device) {
await device.close();
}
}
await transport.connect();
await transport.setDTR(false);
await new Promise(resolve => setTimeout(resolve, 100));
await transport.setDTR(true);
while (device.readable) {

if (!device.readable.locked) {
reader = device.readable.getReader();
}

try {
while (true) {
const { value, done } = await reader.read();
if (done) {
// Allow the serial port to be closed later.
reader.releaseLock();
break;
}
if (value) {
term.write(value);
}
}
} catch (error) { }
}
}

$(window).resize(function () {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => utilities.resizeTerminal(fitAddon), 300);
});
Loading

0 comments on commit 5fbecda

Please sign in to comment.