Skip to content

webrepl: Allow the page to run from the device (over HTTP). #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ The MIT License (MIT)

Copyright (c) 2016 Damien P. George
Copyright (c) 2016 Paul Sokolovsky
Copyright (c) 2022 Jim Mussared

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
16 changes: 16 additions & 0 deletions make_html_js.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python3

# This script transforms webrepl.html into a javascript string wrapped in
# document.write, which allows the device to serve a very simple page
# containing
# <base href="https://url-to-static-host/"></base>
# <script src="webrepl_content.js"></script>

import datetime

with open("webrepl.html", "r") as f_in:
with open("webrepl_content.js", "w") as f_out:
f_out.write("// {}\n".format(datetime.datetime.now().isoformat()))
f_out.write("document.write(\"")
f_out.write(f_in.read().replace("\"", "\\\"").replace("\n", "\\n"))
f_out.write("\");")
13 changes: 13 additions & 0 deletions webrepl.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
html {
background: #555;
}

h1 {
margin-bottom: 20px;
font: 20px/1.5 sans-serif;
}
.file-box {
margin: 4px;
padding: 4px;
background: #888;
}
328 changes: 3 additions & 325 deletions webrepl.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,15 @@
<html>
<head>
<title>MicroPython WebREPL</title>
<!--
term.js
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
Copyright (c) 2016, Paul Sokolovsky
-->
<style>
html {
background: #555;
}

h1 {
margin-bottom: 20px;
font: 20px/1.5 sans-serif;
}

/*
.terminal {
float: left;
border: #000 solid 5px;
font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;
font-size: 11px;
color: #f0f0f0;
background: #000;
}

.terminal-cursor {
color: #000;
background: #f0f0f0;
}
*/

.file-box {
margin: 4px;
padding: 4px;
background: #888;
}
</style>
<link rel="stylesheet" href="webrepl.css">
<script src="term.js"></script>
<script src="FileSaver.js"></script>
</head>
<body>

<div style="display:inline-block; vertical-align:top;">
<form>
<input type="text" name="webrepl_url" id="url" value="ws://192.168.4.1:8266/" />
<input type="text" name="webrepl_url" id="url" value="" />
<input type="submit" id="button" value="Connect" onclick="button_click(); return false;" />
</form>
<div id="term">
Expand Down Expand Up @@ -77,292 +41,6 @@
<i>To paste, press Ctrl+A, then Ctrl+V</i>
</body>

<script>
;

var term;
var ws;
var connected = false;
var binary_state = 0;
var put_file_name = null;
var put_file_data = null;
var get_file_name = null;
var get_file_data = null;

function calculate_size(win) {
var cols = Math.max(80, Math.min(150, (win.innerWidth - 280) / 7)) | 0;
var rows = Math.max(24, Math.min(80, (win.innerHeight - 180) / 12)) | 0;
return [cols, rows];
}

(function() {
window.onload = function() {
var url = window.location.hash.substring(1);
if (url) {
document.getElementById('url').value = 'ws://' + url;
}
var size = calculate_size(self);
term = new Terminal({
cols: size[0],
rows: size[1],
useStyle: true,
screenKeys: true,
cursorBlink: false
});
term.open(document.getElementById("term"));
show_https_warning();
};
window.addEventListener('resize', function() {
var size = calculate_size(self);
term.resize(size[0], size[1]);
});
}).call(this);

function show_https_warning() {
if (window.location.protocol == 'https:') {
var warningDiv = document.createElement('div');
warningDiv.style.cssText = 'background:#f99;padding:5px;margin-bottom:10px;line-height:1.5em;text-align:center';
warningDiv.innerHTML = [
'At this time, the WebREPL client cannot be accessed over HTTPS connections.',
'Use a HTTP connection, eg. <a href="http://micropython.org/webrepl/">http://micropython.org/webrepl/</a>.',
'Alternatively, download the files from <a href="https://github.com/micropython/webrepl">GitHub</a> and run them locally.'
].join('<br>');
document.body.insertBefore(warningDiv, document.body.childNodes[0]);
term.resize(term.cols, term.rows - 7);
}
}

function button_click() {
if (connected) {
ws.close();
} else {
document.getElementById('url').disabled = true;
document.getElementById('button').value = "Disconnect";
connected = true;
connect(document.getElementById('url').value);
}
}

function prepare_for_connect() {
document.getElementById('url').disabled = false;
document.getElementById('button').value = "Connect";
}

function update_file_status(s) {
document.getElementById('file-status').innerHTML = s;
}

function connect(url) {
window.location.hash = url.substring(5);
ws = new WebSocket(url);
ws.binaryType = 'arraybuffer';
ws.onopen = function() {
term.removeAllListeners('data');
term.on('data', function(data) {
// Pasted data from clipboard will likely contain
// LF as EOL chars.
data = data.replace(/\n/g, "\r");
ws.send(data);
});

term.on('title', function(title) {
document.title = title;
});

term.focus();
term.element.focus();
term.write('\x1b[31mWelcome to MicroPython!\x1b[m\r\n');

ws.onmessage = function(event) {
if (event.data instanceof ArrayBuffer) {
var data = new Uint8Array(event.data);
switch (binary_state) {
case 11:
// first response for put
if (decode_resp(data) == 0) {
// send file data in chunks
for (var offset = 0; offset < put_file_data.length; offset += 1024) {
ws.send(put_file_data.slice(offset, offset + 1024));
}
binary_state = 12;
}
break;
case 12:
// final response for put
if (decode_resp(data) == 0) {
update_file_status('Sent ' + put_file_name + ', ' + put_file_data.length + ' bytes');
} else {
update_file_status('Failed sending ' + put_file_name);
}
binary_state = 0;
break;

case 21:
// first response for get
if (decode_resp(data) == 0) {
binary_state = 22;
var rec = new Uint8Array(1);
rec[0] = 0;
ws.send(rec);
}
break;
case 22: {
// file data
var sz = data[0] | (data[1] << 8);
if (data.length == 2 + sz) {
// we assume that the data comes in single chunks
if (sz == 0) {
// end of file
binary_state = 23;
} else {
// accumulate incoming data to get_file_data
var new_buf = new Uint8Array(get_file_data.length + sz);
new_buf.set(get_file_data);
new_buf.set(data.slice(2), get_file_data.length);
get_file_data = new_buf;
update_file_status('Getting ' + get_file_name + ', ' + get_file_data.length + ' bytes');

var rec = new Uint8Array(1);
rec[0] = 0;
ws.send(rec);
}
} else {
binary_state = 0;
}
break;
}
case 23:
// final response
if (decode_resp(data) == 0) {
update_file_status('Got ' + get_file_name + ', ' + get_file_data.length + ' bytes');
saveAs(new Blob([get_file_data], {type: "application/octet-stream"}), get_file_name);
} else {
update_file_status('Failed getting ' + get_file_name);
}
binary_state = 0;
break;
case 31:
// first (and last) response for GET_VER
console.log('GET_VER', data);
binary_state = 0;
break;
}
}
term.write(event.data);
};
};

ws.onclose = function() {
connected = false;
if (term) {
term.write('\x1b[31mDisconnected\x1b[m\r\n');
}
term.off('data');
prepare_for_connect();
}
}

function decode_resp(data) {
if (data[0] == 'W'.charCodeAt(0) && data[1] == 'B'.charCodeAt(0)) {
var code = data[2] | (data[3] << 8);
return code;
} else {
return -1;
}
}

function put_file() {
var dest_fname = put_file_name;
var dest_fsize = put_file_data.length;

// WEBREPL_FILE = "<2sBBQLH64s"
var rec = new Uint8Array(2 + 1 + 1 + 8 + 4 + 2 + 64);
rec[0] = 'W'.charCodeAt(0);
rec[1] = 'A'.charCodeAt(0);
rec[2] = 1; // put
rec[3] = 0;
rec[4] = 0; rec[5] = 0; rec[6] = 0; rec[7] = 0; rec[8] = 0; rec[9] = 0; rec[10] = 0; rec[11] = 0;
rec[12] = dest_fsize & 0xff; rec[13] = (dest_fsize >> 8) & 0xff; rec[14] = (dest_fsize >> 16) & 0xff; rec[15] = (dest_fsize >> 24) & 0xff;
rec[16] = dest_fname.length & 0xff; rec[17] = (dest_fname.length >> 8) & 0xff;
for (var i = 0; i < 64; ++i) {
if (i < dest_fname.length) {
rec[18 + i] = dest_fname.charCodeAt(i);
} else {
rec[18 + i] = 0;
}
}

// initiate put
binary_state = 11;
update_file_status('Sending ' + put_file_name + '...');
ws.send(rec);
}

function get_file() {
var src_fname = document.getElementById('get_filename').value;

// WEBREPL_FILE = "<2sBBQLH64s"
var rec = new Uint8Array(2 + 1 + 1 + 8 + 4 + 2 + 64);
rec[0] = 'W'.charCodeAt(0);
rec[1] = 'A'.charCodeAt(0);
rec[2] = 2; // get
rec[3] = 0;
rec[4] = 0; rec[5] = 0; rec[6] = 0; rec[7] = 0; rec[8] = 0; rec[9] = 0; rec[10] = 0; rec[11] = 0;
rec[12] = 0; rec[13] = 0; rec[14] = 0; rec[15] = 0;
rec[16] = src_fname.length & 0xff; rec[17] = (src_fname.length >> 8) & 0xff;
for (var i = 0; i < 64; ++i) {
if (i < src_fname.length) {
rec[18 + i] = src_fname.charCodeAt(i);
} else {
rec[18 + i] = 0;
}
}

// initiate get
binary_state = 21;
get_file_name = src_fname;
get_file_data = new Uint8Array(0);
update_file_status('Getting ' + get_file_name + '...');
ws.send(rec);
}

function get_ver() {
// WEBREPL_REQ_S = "<2sBBQLH64s"
var rec = new Uint8Array(2 + 1 + 1 + 8 + 4 + 2 + 64);
rec[0] = 'W'.charCodeAt(0);
rec[1] = 'A'.charCodeAt(0);
rec[2] = 3; // GET_VER
// rest of "rec" is zero

// initiate GET_VER
binary_state = 31;
ws.send(rec);
}

function handle_put_file_select(evt) {
// The event holds a FileList object which is a list of File objects,
// but we only support single file selection at the moment.
var files = evt.target.files;

// Get the file info and load its data.
var f = files[0];
put_file_name = f.name;
var reader = new FileReader();
reader.onload = function(e) {
put_file_data = new Uint8Array(e.target.result);
document.getElementById('put-file-list').innerHTML = '' + escape(put_file_name) + ' - ' + put_file_data.length + ' bytes';
document.getElementById('put-file-button').disabled = false;
};
reader.readAsArrayBuffer(f);
}

document.getElementById('put-file-select').addEventListener('click', function(){
this.value = null;
}, false);

document.getElementById('put-file-select').addEventListener('change', handle_put_file_select, false);
document.getElementById('put-file-button').disabled = true;

</script>
<script src="webrepl.js"></script>

</html>
Loading