Skip to content

Instantly share code, notes, and snippets.

@jadonk
Last active August 29, 2015 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jadonk/73c4135c946a47849a44 to your computer and use it in GitHub Desktop.
Save jadonk/73c4135c946a47849a44 to your computer and use it in GitHub Desktop.
<script src="/static/bonescript.js"></script>
<script src="/static/jquery.microdata.js"></script>
<script src="/static/jquery.microdata.json.js"></script>
<div>
<h1>BeagleBone Image Updater</h1>
<p>
This interactive page wizard will use the BoneScript server on your
BeagleBone Black to download the latest BeagleBone Black software image
onto a uSD card
inserted in your BeagleBone Black uSD card slot. To do this,
you
will need to have your BeagleBone Black connected to the Internet and
visible to the computer you are using to visit this web page.
</p>
<p>
This wizard currently requires you to connect your BeagleBone Black
up to the Internet. The simplest way to do so is to put it on your
home router via Ethernet. You'll then need to locate the board
address via your router's DHCP logs or mDNS.
</p>
<p id="bootSource-unknown">
This script will write the selected image to your uSD card
if you are running off of your on-board eMMC, so be
sure to insert a blank uSD card <i>AFTER</i> you finish booting
up. If you boot off of a uSD card, you will write
to the eMMC instead.
</p>
<p id="bootSource-emmc" style="display:none;">
This script will write the selected image to your uSD card,
so be sure the uSD card is blank or doesn't have
any content you want to preserve.
</p>
<p id="bootSource-usd" style="display:none;">
This script will <b>write the selected image to your on-board eMMC
flash.</b> If you'd prefer to program the uSD card, be
sure to insert a blank uSD card <i>AFTER</i> you finish booting
up.
</p>
<p>
When you type your board's address into the box below and click
<b>GO</b>, a script will
be copied onto your board to write images to your uSD cards.
The progress meter below will show you the status of the download.
When the uSD card has been written, your board will reboot automatically.
</p>
<div id="latestImages" style="display:inline;"></div>
<input id="address" value="beaglebone.local" size="40" style="height:22px;padding:1px;border:2px inset;margin:2px;font-size:1em;"></input>
<input id="address-go" name="address-go" type="submit" value="GO »" style="border:1px solid #B4A279; border-radius:5px 5px 5px 5px; margin-bottom:20px; margin-left:25px; padding:10px; width:80px; background-color:#de7224; text-align:center; color:#fff; font-weight:bold; height:3em; cursor:pointer;" />
<div id="progressBarContainer" style="display:inline-block;border:1px solid #B4A279;border-radius:5px 5px 5px 5px;width:300px;height:20px;">
<div id="progressBar" style="height:20px;width:1px;background-color:#de7224"></div>
</div>
<div id="progressPercent" style="display:inline;font-size:2em;">0 %</div>
<pre id="step1-log"></pre>
<pre id="step2-log"></pre>
</div>
<div style="clear:both;"></div>
<script>
var address = getParameterByName("address");
var image = getParameterByName("image");
var md5sum;
var progress = 0;
var handlers = {initialized: run, timeout: noBoneScript};
var socket = null;
var verifyAttempts = 0;
var socketLoadTimeout;
var downloadTimeout;
$.get('/latest-images/', 'html').done(onLatestImages).fail(onLatestImagesFail);
var menuYloc = parseInt($('#floatMenu').css("top").substring(0,$('#floatMenu').css("top").indexOf("px")));
$(window).scroll(function () {
var offset = menuYloc+$(document).scrollTop()+"px";
$('#floatMenu').animate({top:offset},{duration:500,queue:false});
});
var addressEnabled = true;
var connected = false;
$('#address').keypress(onAddress);
$('#address-go').click(onAddressGo);
if(address) {
doConnect();
}
function doConnect() {
if(!connected) {
addressEnabled = false;
$('#address-go').off('click');
$('#address-go').css('background-color', '#B4A279');
$('#step1-log').append("Attempting connection to " + address + '\n');
setTargetAddress(address, handlers);
} else {
$('#step1-log').append("New connection not started to " + address + '\n');
}
}
function onAddress(e) {
if(addressEnabled && (e.which == 10 || e.which == 13)) {
onAddressGo();
}
}
function onAddressGo() {
address = $('#address').val();
doConnect();
}
function noBoneScript() {
$('#address-go').on('click');
$('#address-go').css('background-color', '#de7224');
$('#step1-log').append("Connection failed\n");
verifyAttempts = 0;
}
function onLatestImages(data) {
data = data.replace(/<script/g, "<!--");
data = data.replace(/<\/script>/g, "-->");
var page = $(data).items("http://schema.org/SoftwareApplication");
var parsed = $.microdata.json(page, dataFormatter);
//$('#step2-log').append(JSON.stringify(parsed, null, 4) + "\n");
var images = [];
for(var i in parsed.items) {
var x = parsed.items[i].properties;
if(x.device[0].match(/BeagleBone Black/)) {
var name = x.name[0] + ' ' + x.datePublished[0] + ' ' + x.memoryRequirements[0];
if(typeof x.md5sum == typeof []) {
images.push('<option value="'+ x.downloadURL[0] + '" md5sum="' +
x.md5sum[0] + '">'+ name +'</option>');
} else {
images.push('<option value="'+ x.downloadURL[0] +'">'+ name +'</option>');
}
}
}
var select = '<select>' + images.join('') + '</select>';
$('#latestImages').append(select);
}
function dataFormatter(o) { return o; }
function jsonFormatter(o) { return JSON.stringify(o, null, 4); }
function onLatestImagesFail() {
$('#step2-log').append("Fetch of latest-images failed\n");
}
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search);
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
function run() {
$('#step1-log').append('Connected to ' + address + '\n');
var b = require('bonescript');
var sourceJS = '/static/updater.js';
var targetJS = '/var/lib/cloud9/autorun/updater.js';
b.getPlatform(onGetPlatform);
function onGetPlatform(platform) {
$('#step1-log').append("Running BoneScript version " + platform.bonescript + '\n');
b.setDate(Date().toString(), onSetDate);
}
function onSetDate() {
$('#step1-log').append("Reading " + sourceJS + '\n');
jQuery.get(sourceJS, onJSReadSuccess, 'text').fail(onJSReadFail);
}
function onJSReadSuccess(contentsJS) {
$('#step1-log').append("Writing " + targetJS + '\n');
b.writeTextFile(targetJS, contentsJS, onJSWritten);
}
function onJSReadFail() {
$('#step1-log').append("Failed to read " + targetJS + '\n');
}
function onJSWritten() {
$('#step1-log').append("Waiting 10 seconds...\n");
setTimeout(connectSocket, 10000);
}
}
function connectSocket() {
if(verifyAttempts < 5) {
verifyAttempts++;
var path = 'http://' + address + ':5000/socket.io/socket.io.js';
$('#step1-log').append("Attempting to fetch " + path + '\n');
socketLoadTimeout = setTimeout(onSocketFail, 10000);
$.getScript(path).done(onSocketIOLoad).fail(onSocketFail);
} else {
$('#step1-log').append("Connection failed\n");
}
}
function onSocketFail(jqxhr, settings, exception) {
$('#step1-log').append("Load failed...\n");
if(socketLoadTimeout) clearTimeout(onSocketFail);
setTimeout(connectSocket(), 3000);
//noBoneScript();
}
function onSocketIOLoad(script, textStatus) {
connected = false;
clearTimeout(socketLoadTimeout);
$('#step1-log').append("Load status: " + textStatus + '\n');
socket = new io.connect('http://' + address + ':5000');
socket.on('start', onConnect);
socket.on('progress', onProgress);
socket.on('done', onDone);
}
function onConnect(msg) {
if(connected) return;
connected = true;
if(msg && msg.rootfs) {
$('#step1-log').append('Boot source is ' + msg.rootfs + '\n');
if(msg.rootfs == 'eMMC') {
$('#bootSource-usd').hide();
$('#bootSource-unknown').hide();
$('#bootSource-emmc').show();
} else if(msg.rootfs == 'uSD') {
$('#bootSource-emmc').hide();
$('#bootSource-unknown').hide();
$('#bootSource-usd').show();
}
}
stepOneDone();
}
function stepOneDone() {
$('#step1-log').append("Connection successful... on to Step #2\n");
setTimeout(startStepTwo, 50);
}
function startStepTwo() {
onImageGo();
}
var startTime;
var updateBarTimer;
function onImageGo() {
image = $('#latestImages').find(":selected").attr('value');
md5sum = $('#latestImages').find(":selected").attr('md5sum');
progress = 0;
startTime = new Date();
updateBarTimer = setInterval(updateBar, 1000);
$('#step2-log').append("Attempting to download " + image + '\n');
socket.emit('download', { file: image, md5sum: md5sum });
downloadTimeout = setTimeout(onDownloadTimeout, 60000);
}
var waitingForCurl = false;
function onProgress(msg) {
if(downloadTimeout) clearTimeout(downloadTimeout);
if(progress < 99) {
downloadTimeout = setTimeout(onDownloadTimeout, 60000);
} else {
if(!waitingForCurl) {
$('#step2-log').append("Waiting for write buffer to flush\n");
waitingForCurl = true;
}
}
if(msg.progress) {
progress = msg.progress;
updateBar();
}
}
function onDownloadTimeout() {
$('#step2-log').append("Connection timeout\n");
socket.disconnect();
socket = null;
if(updateBarTimer) clearInterval(updateBarTimer);
}
function onDone(msg) {
if(downloadTimeout) clearTimeout(downloadTimeout);
downloadTimeout = null;
if(updateBarTimer) clearInterval(updateBarTimer);
if(msg.md5sum && msg.md5sum == md5sum) {
$('#step2-log').append("Download successful\n");
stepTwoDone();
} else {
$('#step2-log').append("Download failed: " + JSON.stringify(msg, null, 4) + "\n");
}
}
function stepTwoDone() {
if(socket) {
socket.disconnect()
socket = null;
setTimeout(startStepThree, 50);
}
else {
$('#step2-log').append("Lost socket connection early\n");
}
}
function startStepThree() {
updateBar();
$('#address-go').on('click');
$('#address-go').css('background-color', '#de7224');
}
function updateBar() {
var elapsedTime = (new Date()) - startTime;
var hours = parseInt(elapsedTime / (1000*60*60));
var minutes = parseInt((elapsedTime % (1000*60*60)) / (1000*60));
var seconds = parseInt((elapsedTime % (1000*60)) / 1000);
var timeString = hours + ':' + minutes + ':' + seconds;
$('#progressBar').width(progress + '%');
$('#progressPercent').html(progress + ' % ' + timeString);
}
</script>
var fs = require('fs');
var http = require('http');
var b = require('bonescript');
var crypto = require('crypto');
var child_process = require('child_process');
var socketio = require('bonescript/node_modules/socket.io');
// Setup LED indicators
var state = 'init';
var phase = 0;
var leds = ['USR0', 'USR1', 'USR2', 'USR3'];
for(var i in leds) {
b.pinMode(leds[i], b.OUTPUT);
b.digitalWrite(leds[i], b.HIGH);
}
var ledTimer = setInterval(updateLEDs, 100);
if(false) {
doDownload('http://debian.beagleboard.org/images/bone-debian-7.4-2014-04-14-2gb.img.xz');
//doDownload('http://beagleboard.org/static/test.txt.xz');
} else if(process.argv.length > 2) {
doDownload(process.argv[2]);
} else {
// Try to figure out rootfs media
var rootfs;
fs.readdir("/sys/bus/mmc/devices/mmc1:0001/block", onReadDir);
// Timeout on no connect for 3 minutes
var timeout = setTimeout(onDisconnect, 3*60*1000);
}
function onReadDir(error, files) {
if(error) {
onDisconnect();
} else {
if((typeof files == typeof []) && (files[0] == 'mmcblk0')) {
rootfs = 'eMMC';
} else {
rootfs = 'uSD';
}
startServer();
}
}
var port = 4999;
function startServer() {
// Setup connection listener
getPort();
}
function getPort() {
port++;
console.log('Trying port ' + port);
var io = socketio.listen(port);
io.set('log level', 1);
io.on('error', getPort);
io.sockets.on('connection', onConnection);
}
var client;
var waitForCurl = false;
function onConnection(socket) {
client = socket;
clearTimeout(timeout);
state = 'connected';
var connectDate = new Date();
socket.emit('start', { date: connectDate, port: port, rootfs: rootfs });
socket.on('mounts', onMounts);
socket.on('download', onDownload);
socket.on('disconnect', onDisconnect);
function onMounts(data) {
fs.readFile('/proc/mounts', 'ascii', onMountsFile);
}
function onMountsFile(error, data) {
socket.emit('mounts', {error: error, data: data});
}
function onDownload(msg) {
doDownload(msg.file);
}
}
function doDownload(file) {
state = 'download';
console.log('downloading: ' + file);
var umount = child_process.spawn('bash', ['-c', "for n in /dev/mmcblk1*;do umount $n;done"]);
umount.stdout.pipe(process.stdout);
umount.stderr.pipe(process.stderr);
umount.on('close', startCurl);
function startCurl() {
//var curl = child_process.spawn('bash', ['-c', "curl -# " + file + " | xzcat | tee >(dd of=/dev/null) | md5sum"]);
var curl = child_process.spawn('bash', ['-c', "curl -# " + file + " | tee >(xzcat | dd of=/dev/mmcblk1) | md5sum"]);
curl.on('error', onError);
curl.on('close', onCurlExit);
curl.stdout.on('data', onCurlData);
curl.stdout.setEncoding('ascii');
curl.stderr.on('data', onCurlUpdate);
curl.stderr.setEncoding('ascii');
}
function onCurlExit() {
console.log('curl exited');
doExit();
}
function onCurlData(data) {
console.log('md5sum: ' + data.substring(0,32));
if(client) client.emit('done', { md5sum: data.substring(0,32) });
}
var lastProgress = 0;
function onCurlUpdate(data) {
var progress;
var x = data.match(/\d+\.+\d*\%/);
if(x) progress = parseFloat(x[0]);
if(progress > 99) waitForCurl = true;
if(progress && lastProgress < progress) {
lastProgress = progress;
if(client) client.emit('progress', { progress: progress.toFixed(1) });
else console.log('progress: ' + progress + '%');
}
}
function onError(error) {
console.log('Error: ' + error);
if(client) client.emit('error', { error: error });
doExit();
}
}
function doExit() {
restoreLEDs();
var shutdown = child_process.spawn('shutdown', ['-r', '-t', '5', 'now']);
shutdown.stdout.pipe(process.stdout);
shutdown.stderr.pipe(process.stderr);
shutdown.on('close', onShutdown);
}
function onShutdown() {
fs.unlinkSync(__filename);
process.exit(0);
}
function onDisconnect() {
state = 'disconnected';
if(!waitForCurl) {
doExit();
}
}
function updateLEDs() {
phase = (phase + 1) % 8;
if(state == 'init') {
if(phase == 0) {
setLEDs(b.HIGH);
} else if(phase == 6) {
setLEDs(b.LOW);
}
} else if(state == 'connected') {
if(phase == 0) {
setLEDs(b.HIGH);
} else if(phase == 1) {
setLEDs(b.LOW);
}
} else if(state == 'download') {
b.digitalWrite('USR0', (phase == 0 || phase == 7) ? b.HIGH : b.LOW);
b.digitalWrite('USR1', (phase == 1 || phase == 6) ? b.HIGH : b.LOW);
b.digitalWrite('USR2', (phase == 2 || phase == 5) ? b.HIGH : b.LOW);
b.digitalWrite('USR3', (phase == 3 || phase == 4) ? b.HIGH : b.LOW);
} else if(state == 'done') {
if(phase % 1) {
setLEDs(b.HIGH);
} else {
setLEDs(b.LOW);
}
}
}
function setLEDs(level) {
for(var i in leds) {
b.digitalWrite(leds[i], level);
}
}
function restoreLEDs() {
clearInterval(ledTimer);
var p = '/sys/class/leds/beaglebone:green:usr';
b.digitalWrite('USR0', b.LOW);
b.digitalWrite('USR1', b.LOW);
b.digitalWrite('USR2', b.LOW);
b.digitalWrite('USR3', b.LOW);
b.writeTextFile(p+'0/trigger', 'heartbeat');
b.writeTextFile(p+'1/trigger', 'mmc0');
b.writeTextFile(p+'2/trigger', 'cpu0');
b.writeTextFile(p+'3/trigger', 'mmc1');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment