Last active
August 29, 2015 14:01
-
-
Save jadonk/73c4135c946a47849a44 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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