add web install tools
This commit is contained in:
parent
10b04509ba
commit
14a8b44221
|
@ -0,0 +1,24 @@
|
||||||
|
.item {
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
.itemTitle {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
button.installButton, button.showConsole, button.hideConsole {
|
||||||
|
font-size: 1em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
select.consoleBaud {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 10em;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
.console {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: system-ui;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/xterm@4.19.0/lib/xterm.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@4.19.0/css/xterm.css">
|
||||||
|
<script type="module" src="install.js"></script>
|
||||||
|
<link rel="stylesheet" href="install.css"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
<div class="heading"></div>
|
||||||
|
<div class="console"></div>
|
||||||
|
<div class="content"></div>
|
||||||
|
<div id="terminal"></div>
|
||||||
|
</html>
|
|
@ -0,0 +1,151 @@
|
||||||
|
import {XtermOutputHandler} from "./installUtil.js";
|
||||||
|
import ESPInstaller from "./installUtil.js";
|
||||||
|
(function(){
|
||||||
|
let espLoaderTerminal;
|
||||||
|
let espInstaller;
|
||||||
|
let releaseData={};
|
||||||
|
const addEl=ESPInstaller.addEl; //shorter typing
|
||||||
|
let showConsole;
|
||||||
|
let hideConsole;
|
||||||
|
const enableConsole=(enable,disableBoth)=>{
|
||||||
|
if (showConsole) showConsole.disabled=!enable || disableBoth;
|
||||||
|
if (hideConsole) hideConsole.disabled=enable || disableBoth;
|
||||||
|
}
|
||||||
|
const showError=(txt)=>{
|
||||||
|
let hFrame=document.querySelector('.heading');
|
||||||
|
if (hFrame){
|
||||||
|
hFrame.textContent=txt;
|
||||||
|
hFrame.classList.add("error");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alert(txt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const buildHeading=(user,repo,element)=>{
|
||||||
|
let hFrame=document.querySelector(element||'.heading');
|
||||||
|
if (! hFrame) return;
|
||||||
|
hFrame.textContent='';
|
||||||
|
let h=addEl('h2',undefined,hFrame,`ESP32 Install ${user}:${repo}`)
|
||||||
|
}
|
||||||
|
const checkChip=(chipFamily,assetName)=>{
|
||||||
|
//for now only ESP32
|
||||||
|
if (chipFamily != "ESP32"){
|
||||||
|
throw new Error(`unexpected chip family ${chipFamily}, expected ESP32`);
|
||||||
|
}
|
||||||
|
return assetName;
|
||||||
|
}
|
||||||
|
const baudRates=[1200,
|
||||||
|
2400,
|
||||||
|
4800,
|
||||||
|
9600,
|
||||||
|
14400,
|
||||||
|
19200,
|
||||||
|
28800,
|
||||||
|
38400,
|
||||||
|
57600,
|
||||||
|
115200,
|
||||||
|
230400,
|
||||||
|
460800];
|
||||||
|
const buildConsoleButtons=(element)=>{
|
||||||
|
let bFrame=document.querySelector(element||'.console');
|
||||||
|
if (! bFrame) return;
|
||||||
|
bFrame.textContent='';
|
||||||
|
let cLine=addEl('div','buttons',bFrame);
|
||||||
|
let bSelect=addEl('select','consoleBaud',cLine);
|
||||||
|
baudRates.forEach((baud)=>{
|
||||||
|
let v=addEl('option',undefined,bSelect,baud+'');
|
||||||
|
v.setAttribute('value',baud);
|
||||||
|
});
|
||||||
|
bSelect.value=115200;
|
||||||
|
showConsole=addEl('button','showConsole',cLine,'ShowConsole');
|
||||||
|
showConsole.addEventListener('click',async()=>{
|
||||||
|
enableConsole(false);
|
||||||
|
await espInstaller.startConsole(bSelect.value);
|
||||||
|
})
|
||||||
|
hideConsole=addEl('button','hideConsole',cLine,'HideConsole');
|
||||||
|
hideConsole.addEventListener('click',async()=>{
|
||||||
|
await espInstaller.stopConsole();
|
||||||
|
enableConsole(true);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const buildButtons=(user,repo,element)=>{
|
||||||
|
let bFrame=document.querySelector(element||'.content');
|
||||||
|
if (! bFrame) return;
|
||||||
|
bFrame.textContent='';
|
||||||
|
if (!releaseData.assets) return;
|
||||||
|
let version=releaseData.name;
|
||||||
|
if (! version){
|
||||||
|
alert("no version found in release data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addEl('div','version',bFrame,`Version: ${version}`);
|
||||||
|
let items={};
|
||||||
|
releaseData.assets.forEach((asset)=>{
|
||||||
|
let name=asset.name;
|
||||||
|
let base=name.replace(/-all\.bin/,'').replace(/-update\.bin/,'');
|
||||||
|
if (items[base] === undefined){
|
||||||
|
items[base]={};
|
||||||
|
}
|
||||||
|
let item=items[base];
|
||||||
|
item.label=base.replace(/-[0-9][0-9]*/,'');
|
||||||
|
if (name.match(/-update\./)){
|
||||||
|
item.update=name;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
item.basic=name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (let k in items){
|
||||||
|
let item=items[k];
|
||||||
|
let line=addEl('div','item',bFrame);
|
||||||
|
addEl('div','itemTitle',line,item.label);
|
||||||
|
let btLine=addEl('div','buttons',line);
|
||||||
|
let tb=addEl('button','installButton',line,'Initial');
|
||||||
|
tb.addEventListener('click',async ()=>{
|
||||||
|
enableConsole(false,true);
|
||||||
|
await espInstaller.installClicked(
|
||||||
|
true,
|
||||||
|
user,
|
||||||
|
repo,
|
||||||
|
version,
|
||||||
|
4096,
|
||||||
|
(chip)=>checkChip(chip,item.basic)
|
||||||
|
)
|
||||||
|
enableConsole(true);
|
||||||
|
});
|
||||||
|
tb=addEl('button','installButton',line,'Update');
|
||||||
|
tb.addEventListener('click',async ()=>{
|
||||||
|
enableConsole(false,true);
|
||||||
|
await espInstaller.installClicked(
|
||||||
|
false,
|
||||||
|
user,
|
||||||
|
repo,
|
||||||
|
version,
|
||||||
|
65536,
|
||||||
|
(chip)=>checkChip(chip,item.update)
|
||||||
|
)
|
||||||
|
enableConsole(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
window.onload = async () => {
|
||||||
|
if (! ESPInstaller.checkAvailable()){
|
||||||
|
showError("your browser does not support the ESP flashing (no serial)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let user = window.gitHubUser||ESPInstaller.getParam('user');
|
||||||
|
let repo = window.gitHubRepo || ESPInstaller.getParam('repo');
|
||||||
|
if (!user || !repo) {
|
||||||
|
alert("missing parameter user or repo");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
espLoaderTerminal = new XtermOutputHandler('terminal');
|
||||||
|
espInstaller = new ESPInstaller(espLoaderTerminal);
|
||||||
|
buildHeading(user, repo);
|
||||||
|
buildConsoleButtons();
|
||||||
|
releaseData = await espInstaller.getReleaseInfo(user, repo);
|
||||||
|
buildButtons(user, repo);
|
||||||
|
} catch(error){alert("unable to query release info for user "+user+", repo "+repo+": "+error)};
|
||||||
|
}
|
||||||
|
})();
|
|
@ -0,0 +1,155 @@
|
||||||
|
<?php
|
||||||
|
$api="https://api.github.com/repos/#user#/#repo#/releases/latest";
|
||||||
|
$download="https://github.com/#user#/#repo#/releases/download/#dlVersion#/#dlName#";
|
||||||
|
$manifest="?dlName=#mName#&dlVersion=#mVersion#&user=#user#&repo=#repo#";
|
||||||
|
$allowed=array(
|
||||||
|
'user'=> array('wellenvogel'),
|
||||||
|
'repo'=> array('esp32-nmea2000')
|
||||||
|
);
|
||||||
|
if (!function_exists('getallheaders')) {
|
||||||
|
function getallheaders()
|
||||||
|
{
|
||||||
|
$headers = [];
|
||||||
|
foreach ($_SERVER as $name => $value) {
|
||||||
|
if (substr($name, 0, 5) == 'HTTP_') {
|
||||||
|
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function safeName($name){
|
||||||
|
return preg_replace('[^0-9_a-zA-Z.-]','',$name);
|
||||||
|
}
|
||||||
|
function replaceVars($str,$vars){
|
||||||
|
foreach ($vars as $n => &$v){
|
||||||
|
$str=str_replace("#".$n."#",$v,$str);
|
||||||
|
}
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillUserAndRepo($vars=null){
|
||||||
|
global $allowed;
|
||||||
|
if ($vars == null) {
|
||||||
|
$vars=array();
|
||||||
|
}
|
||||||
|
foreach (array('user','repo') as $n){
|
||||||
|
if (! isset($_REQUEST[$n])){
|
||||||
|
die("missing parameter $n");
|
||||||
|
}
|
||||||
|
$v=$_REQUEST[$n];
|
||||||
|
$av=$allowed[$n];
|
||||||
|
if (! in_array($v,$av)){
|
||||||
|
die("value $v for $n not allowed");
|
||||||
|
}
|
||||||
|
$vars[$n]=$v;
|
||||||
|
}
|
||||||
|
return $vars;
|
||||||
|
}
|
||||||
|
function addVars($vars,$names){
|
||||||
|
foreach ($names as $n){
|
||||||
|
if (! isset($_REQUEST[$n])){
|
||||||
|
die("missing parameter $n");
|
||||||
|
}
|
||||||
|
$safe=safeName($_REQUEST[$n]);
|
||||||
|
$vars[$n]=$safe;
|
||||||
|
}
|
||||||
|
return $vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
function curl_exec_follow(/*resource*/ $ch, /*int*/ &$maxredirect = null) {
|
||||||
|
$mr = $maxredirect === null ? 5 : intval($maxredirect);
|
||||||
|
if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off') && false) {
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $mr > 0);
|
||||||
|
curl_setopt($ch, CURLOPT_MAXREDIRS, $mr);
|
||||||
|
} else {
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
||||||
|
if ($mr > 0) {
|
||||||
|
$newurl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||||||
|
$rch = curl_copy_handle($ch);
|
||||||
|
curl_setopt($rch, CURLOPT_HEADER, true);
|
||||||
|
curl_setopt($rch, CURLOPT_NOBODY, true);
|
||||||
|
curl_setopt($rch, CURLOPT_FORBID_REUSE, false);
|
||||||
|
curl_setopt($rch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
do {
|
||||||
|
curl_setopt($rch, CURLOPT_URL, $newurl);
|
||||||
|
$header = curl_exec($rch);
|
||||||
|
if (curl_errno($rch)) {
|
||||||
|
$code = 0;
|
||||||
|
} else {
|
||||||
|
$code = curl_getinfo($rch, CURLINFO_HTTP_CODE);
|
||||||
|
if ($code == 301 || $code == 302) {
|
||||||
|
preg_match('/Location:(.*?)\n/', $header, $matches);
|
||||||
|
$newurl = trim(array_pop($matches));
|
||||||
|
} else {
|
||||||
|
$code = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while ($code && --$mr);
|
||||||
|
curl_close($rch);
|
||||||
|
if (!$mr) {
|
||||||
|
if ($maxredirect === null) {
|
||||||
|
trigger_error('Too many redirects. When following redirects, libcurl hit the maximum amount.', E_USER_WARNING);
|
||||||
|
} else {
|
||||||
|
$maxredirect = 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $newurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curl_setopt(
|
||||||
|
$ch,
|
||||||
|
CURLOPT_HEADERFUNCTION,
|
||||||
|
function ($curl, $header) {
|
||||||
|
header($header);
|
||||||
|
return strlen($header);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
curl_setopt(
|
||||||
|
$ch,
|
||||||
|
CURLOPT_WRITEFUNCTION,
|
||||||
|
function ($curl, $body) {
|
||||||
|
echo $body;
|
||||||
|
return strlen($body);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
header('Access-Control-Allow-Origin:*');
|
||||||
|
return curl_exec($ch);
|
||||||
|
}
|
||||||
|
function proxy($url)
|
||||||
|
{
|
||||||
|
$headers=getallheaders();
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array(
|
||||||
|
$ch,
|
||||||
|
[
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 30,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$FWHDR = ['User-Agent'];
|
||||||
|
$outHeaders = array();
|
||||||
|
foreach ($FWHDR as $k) {
|
||||||
|
if (isset($headers[$k])) {
|
||||||
|
array_push($outHeaders, "$k: $headers[$k]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $outHeaders);
|
||||||
|
$response = curl_exec_follow($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_REQUEST['api'])) {
|
||||||
|
$vars=fillUserAndRepo();
|
||||||
|
proxy(replaceVars($api,$vars));
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
if (isset($_REQUEST['dlName'])){
|
||||||
|
$vars=fillUserAndRepo();
|
||||||
|
$vars=addVars($vars,array('dlName','dlVersion'));
|
||||||
|
proxy(replaceVars($download,$vars));
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
die("invalid request");
|
||||||
|
?>
|
|
@ -0,0 +1,338 @@
|
||||||
|
import {ESPLoader,Transport} from "https://cdn.jsdelivr.net/npm/esptool-js@0.2.1/bundle.js";
|
||||||
|
/**
|
||||||
|
* write all messages to the console
|
||||||
|
*/
|
||||||
|
class ConsoleOutputHandler{
|
||||||
|
clean() {
|
||||||
|
}
|
||||||
|
writeLine(data) {
|
||||||
|
console.log("ESPInstaller:",data);
|
||||||
|
}
|
||||||
|
write(data) {
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* write messages to an instance of xterm
|
||||||
|
* to use this, include in your html
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/xterm@4.19.0/lib/xterm.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@4.19.0/css/xterm.css">
|
||||||
|
* and create a div element
|
||||||
|
<div id="terminal"/>
|
||||||
|
* provide the id of this div to the constructor
|
||||||
|
*/
|
||||||
|
class XtermOutputHandler {
|
||||||
|
constructor(termId) {
|
||||||
|
let termElement = document.getElementById(termId);
|
||||||
|
if (termElement) {
|
||||||
|
this.term = new Terminal({ cols: 120, rows: 40 , convertEol: true });
|
||||||
|
this.term.open(termElement);
|
||||||
|
}
|
||||||
|
this.clean=this.clean.bind(this);
|
||||||
|
this.writeLine=this.writeLine.bind(this);
|
||||||
|
this.write=this.write.bind(this);
|
||||||
|
}
|
||||||
|
clean() {
|
||||||
|
if (!this.term) return;
|
||||||
|
this.term.clear();
|
||||||
|
}
|
||||||
|
writeLine(data) {
|
||||||
|
if (!this.term) {
|
||||||
|
console.log("TERM:", data);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
this.term.writeln(data);
|
||||||
|
}
|
||||||
|
write(data) {
|
||||||
|
if (!this.term) {
|
||||||
|
console.log("TERM:", data);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
this.term.write(data)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
class ESPInstaller{
|
||||||
|
constructor(outputHandler){
|
||||||
|
this.espLoaderTerminal=outputHandler|| new ConsoleOutputHandler();
|
||||||
|
this.transport=undefined;
|
||||||
|
this.esploader=undefined;
|
||||||
|
this.chipFamily=undefined;
|
||||||
|
this.base=import.meta.url.replace(/[^/]*$/,"install.php");
|
||||||
|
this.consoleDevice=undefined;
|
||||||
|
this.consoleReader=undefined;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* get an URL query parameter
|
||||||
|
* @param key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static getParam(key){
|
||||||
|
let value=RegExp(""+key+"[^&]+").exec(window.location.search);
|
||||||
|
// Return the unescaped value minus everything starting from the equals sign or an empty string
|
||||||
|
return decodeURIComponent(!!value ? value.toString().replace(/^[^=]+./,"") : "");
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* add an HTML element
|
||||||
|
* @param {*} type
|
||||||
|
* @param {*} clazz
|
||||||
|
* @param {*} parent
|
||||||
|
* @param {*} text
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static addEl(type, clazz, parent, text) {
|
||||||
|
let el = document.createElement(type);
|
||||||
|
if (clazz) {
|
||||||
|
if (!(clazz instanceof Array)) {
|
||||||
|
clazz = clazz.split(/ */);
|
||||||
|
}
|
||||||
|
clazz.forEach(function (ce) {
|
||||||
|
el.classList.add(ce);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (text) el.textContent = text;
|
||||||
|
if (parent) parent.appendChild(el);
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* call a function for each matching element
|
||||||
|
* @param {*} selector
|
||||||
|
* @param {*} cb
|
||||||
|
*/
|
||||||
|
static forEachEl(selector,cb){
|
||||||
|
let arr=document.querySelectorAll(selector);
|
||||||
|
for (let i=0;i<arr.length;i++){
|
||||||
|
cb(arr[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static checkAvailable(){
|
||||||
|
if (! navigator.serial || ! navigator.serial.requestPort) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* execute a reset on the connected device
|
||||||
|
*/
|
||||||
|
async resetTransport() {
|
||||||
|
if (!this.transport) {
|
||||||
|
throw new Error("not connected");
|
||||||
|
}
|
||||||
|
this.espLoaderTerminal.writeLine("Resetting...");
|
||||||
|
await this.transport.device.setSignals({
|
||||||
|
dataTerminalReady: false,
|
||||||
|
requestToSend: true,
|
||||||
|
});
|
||||||
|
await this.transport.device.setSignals({
|
||||||
|
dataTerminalReady: false,
|
||||||
|
requestToSend: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async disconnect(){
|
||||||
|
if (this.consoleDevice){
|
||||||
|
try{
|
||||||
|
if (this.consoleReader){
|
||||||
|
await this.consoleReader.cancel();
|
||||||
|
this.consoleReader=undefined;
|
||||||
|
}
|
||||||
|
await this.consoleDevice.close();
|
||||||
|
}catch(e){
|
||||||
|
console.log(`error cancel serial read ${e}`);
|
||||||
|
}
|
||||||
|
this.consoleDevice=undefined;
|
||||||
|
}
|
||||||
|
if (this.transport){
|
||||||
|
try{
|
||||||
|
await this.transport.disconnect();
|
||||||
|
await this.transport.waitForUnlock(1500);
|
||||||
|
}catch (e){}
|
||||||
|
this.transport=undefined;
|
||||||
|
}
|
||||||
|
this.esploader=undefined;
|
||||||
|
}
|
||||||
|
async connect() {
|
||||||
|
this.espLoaderTerminal.clean();
|
||||||
|
await this.disconnect();
|
||||||
|
let device = await navigator.serial.requestPort({});
|
||||||
|
if (!device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.transport = new Transport(device);
|
||||||
|
this.esploader = new ESPLoader(this.transport, 115200, this.espLoaderTerminal);
|
||||||
|
let foundChip = await this.esploader.main_fn();
|
||||||
|
if (!foundChip) {
|
||||||
|
throw new Error("unable to read chip id");
|
||||||
|
}
|
||||||
|
this.espLoaderTerminal.writeLine(`chip: ${foundChip}`);
|
||||||
|
await this.esploader.flash_id();
|
||||||
|
this.chipFamily = this.esploader.chip.CHIP_NAME;
|
||||||
|
this.espLoaderTerminal.writeLine(`chipFamily: ${this.chipFamily}`);
|
||||||
|
} catch (e) {
|
||||||
|
this.disconnect();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async startConsole(baud) {
|
||||||
|
await this.disconnect();
|
||||||
|
try {
|
||||||
|
let device = await navigator.serial.requestPort({});
|
||||||
|
if (!device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.consoleDevice=device;
|
||||||
|
let br=baud || 115200;
|
||||||
|
await device.open({
|
||||||
|
baudRate: br
|
||||||
|
});
|
||||||
|
this.consoleReader=device.readable.getReader();
|
||||||
|
this.espLoaderTerminal.clean();
|
||||||
|
this.espLoaderTerminal.writeLine(`Console at ${br}:`);
|
||||||
|
while (this.consoleReader) {
|
||||||
|
let {value:val,done:done} = await this.consoleReader.read();
|
||||||
|
if (typeof val !== 'undefined') {
|
||||||
|
this.espLoaderTerminal.write(val);
|
||||||
|
}
|
||||||
|
if (done){
|
||||||
|
console.log("Console reader stopped");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { this.espLoaderTerminal.writeLine(`Error: ${e}`) }
|
||||||
|
this.espLoaderTerminal.writeLine("Console reader stopped");
|
||||||
|
}
|
||||||
|
async stopConsole(){
|
||||||
|
await this.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnected(){
|
||||||
|
return this.transport !== undefined;
|
||||||
|
}
|
||||||
|
checkConnected(){
|
||||||
|
if (! this.isConnected){
|
||||||
|
throw new Error("not connected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getChipFamily(){
|
||||||
|
this.checkConnected();
|
||||||
|
return this.chipFamily;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* flass the device
|
||||||
|
* @param {*} fileList : an array of entries {data:blob,address:number}
|
||||||
|
*/
|
||||||
|
async writeFlash(fileList){
|
||||||
|
this.checkConnected();
|
||||||
|
this.espLoaderTerminal.writeLine(`Flashing....`);
|
||||||
|
await this.esploader.write_flash(
|
||||||
|
fileList,
|
||||||
|
"keep",
|
||||||
|
"keep",
|
||||||
|
"keep",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
await this.resetTransport();
|
||||||
|
this.espLoaderTerminal.writeLine(`Done.`);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* fetch a release asset from github
|
||||||
|
* @param {*} user
|
||||||
|
* @param {*} repo
|
||||||
|
* @param {*} version
|
||||||
|
* @param {*} name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getReleaseAsset(user,repo,version,name){
|
||||||
|
const url=this.base+"?dlName="+encodeURIComponent(name)+
|
||||||
|
"&dlVersion="+encodeURIComponent(version)+
|
||||||
|
"&user="+encodeURIComponent(user)+
|
||||||
|
"&repo="+encodeURIComponent(repo);
|
||||||
|
this.espLoaderTerminal.writeLine(`downloading image from ${url}`);
|
||||||
|
const resp=await fetch(url);
|
||||||
|
if (! resp.ok){
|
||||||
|
throw new Error(`unable to download image from ${url}: ${resp.status}`);
|
||||||
|
}
|
||||||
|
const reader=new FileReader();
|
||||||
|
const blob= await resp.blob();
|
||||||
|
let data=await new Promise((resolve)=>{
|
||||||
|
reader.addEventListener("load",() => resolve(reader.result));
|
||||||
|
reader.readAsBinaryString(blob);
|
||||||
|
});
|
||||||
|
this.espLoaderTerminal.writeLine(`successfully loaded ${data.length} bytes`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* handle the click of an install button
|
||||||
|
* @param {*} isFull
|
||||||
|
* @param {*} user
|
||||||
|
* @param {*} repo
|
||||||
|
* @param {*} version
|
||||||
|
* @param {*} address
|
||||||
|
* @param {*} assetName the name of the asset file.
|
||||||
|
* can be a function - will be called with the chip family
|
||||||
|
* and must return the asset file name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async installClicked(isFull, user, repo, version, address, assetName) {
|
||||||
|
try {
|
||||||
|
await this.connect();
|
||||||
|
let assetFileName = assetName;
|
||||||
|
if (typeof (assetName) === 'function') {
|
||||||
|
assetFileName = assetName(this.getChipFamily());
|
||||||
|
}
|
||||||
|
let imageData = await this.getReleaseAsset(user, repo, version, assetFileName);
|
||||||
|
if (!imageData || imageData.length == 0) {
|
||||||
|
throw new Error(`no image data fetched`);
|
||||||
|
}
|
||||||
|
let fileList = [
|
||||||
|
{ data: imageData, address: address }
|
||||||
|
];
|
||||||
|
let txt = isFull ? "baseImage (all data will be erased)" : "update";
|
||||||
|
if (!confirm(`ready to install ${version}\n${txt}`)) {
|
||||||
|
this.espLoaderTerminal.writeLine("aborted by user...");
|
||||||
|
await this.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.writeFlash(fileList);
|
||||||
|
await this.disconnect();
|
||||||
|
} catch (e) {
|
||||||
|
this.espLoaderTerminal.writeLine(`Error: ${e}`);
|
||||||
|
alert(`Error: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* fetch the release info from the github API
|
||||||
|
* @param {*} user
|
||||||
|
* @param {*} repo
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getReleaseInfo(user,repo){
|
||||||
|
let url=this.base+"?api=1&user="+encodeURIComponent(user)+"&repo="+encodeURIComponent(repo)
|
||||||
|
let resp=await fetch(url);
|
||||||
|
if (! resp.ok){
|
||||||
|
throw new Error(`unable to query release info from ${url}: ${resp.status}`);
|
||||||
|
}
|
||||||
|
return await resp.json();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* get the release info in a parsed form
|
||||||
|
* @param {*} user
|
||||||
|
* @param {*} repo
|
||||||
|
* @returns an object: {version:nnn, assets:[name1,name2,...]}
|
||||||
|
*/
|
||||||
|
async getParsedReleaseInfo(user,repo){
|
||||||
|
let raw=await this.getReleaseInfo(user,repo);
|
||||||
|
let rt={
|
||||||
|
version:raw.name,
|
||||||
|
assets:[]
|
||||||
|
};
|
||||||
|
if (! raw.assets) return rt;
|
||||||
|
raw.assets.forEach((asset)=>{
|
||||||
|
rt.assets.push(asset.name);
|
||||||
|
})
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export {ConsoleOutputHandler, XtermOutputHandler};
|
||||||
|
export default ESPInstaller;
|
Loading…
Reference in New Issue