aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Green <andy.green@linaro.org>2012-10-17 12:42:33 +0800
committerAndy Green <andy.green@linaro.org>2012-10-17 12:42:33 +0800
commit78f353909176a92632af496626eb968fc0892954 (patch)
tree69ab44fe0f2e2b05e84fe1c104648c96c52d91a4
parent3f41cccaef284e02e1a0e6fd0bc028410d1471d1 (diff)
add measuring calipers
Signed-off-by: Andy Green <andy.green@linaro.org>
-rw-r--r--aepd/Makefile.am5
-rw-r--r--aepd/share/aepscope.html625
-rw-r--r--aepd/share/caliper.pngbin0 -> 1365 bytes
-rw-r--r--aepd/websocket-protocol.c105
-rw-r--r--config2
5 files changed, 684 insertions, 53 deletions
diff --git a/aepd/Makefile.am b/aepd/Makefile.am
index 9096ae3..ffd2f72 100644
--- a/aepd/Makefile.am
+++ b/aepd/Makefile.am
@@ -1,6 +1,6 @@
bin_PROGRAMS=aepd
aepd_SOURCES=main.c websocket-protocol.c
-aepd_CFLAGS=-fPIC -Wall -Werror -std=gnu99 -pedantic -DINSTALL_DATADIR=\"${datarootdir}\"
+aepd_CFLAGS=-fPIC -Wall -Werror -O0 -std=gnu99 -pedantic -DINSTALL_DATADIR=\"${datarootdir}\"
aepd_LDFLAGS=-fPIC
aepd_LDADD=-L../libarmep -larmep -lwebsockets
@@ -20,6 +20,7 @@ clean-local:
install-data-local: aepd.pem aepd.key.pem
mkdir -p $(DESTDIR)$(datadir)/aepd
- cp share/aepscope.html share/favicon.ico aepd.pem aepd.key.pem share/linaro-logo-32.png \
+ cp share/aepscope.html share/favicon.ico aepd.pem aepd.key.pem \
+ share/linaro-logo-32.png share/caliper.png \
$(DESTDIR)$(datadir)/aepd
diff --git a/aepd/share/aepscope.html b/aepd/share/aepscope.html
index b2f55c3..63e39f6 100644
--- a/aepd/share/aepscope.html
+++ b/aepd/share/aepscope.html
@@ -25,6 +25,13 @@
-->
<html lang="en">
<head>
+<style type="text/css">
+ div.meas { text-align:right; }
+ td.measname { text-align:left; }
+ div.topmeas { text-align:right; font-weight:bold; }
+ td.topmeasname { text-align:left; ; font-weight:bold; }
+ td.heading { text-align:center; vertical-align:middle; color:#ffffff; }
+</style>
<meta charset=utf-8 />
<title>ARM Energy Probe aepd UI</title>
</head>
@@ -41,7 +48,7 @@
<img src="linaro-logo-32.png">
</td>
<td>
- <table><tr><td>Time zoom</td><td><div id=tb></div></td></tr><tr><td colspan="2">
+ <table><tr><td>View duration</td><td><div id=tb></div></td></tr><tr><td colspan="2">
<input type="range" id="rate" min="0" max="14" onchange="update_options();"></td></tr></table>
</td>
<td>
@@ -53,6 +60,7 @@
<div id=val>-</div>
</td>
<td>
+ <input type="checkbox" id="measure" checked="checked" onchange="update_measure();">Measure</input>
<div id=val2>-</div>
</td>
</tr>
@@ -72,14 +80,15 @@
</table>
</td>
<td style="vertical-align:top">
- <div style="vertical-align:top" id="cha"></div>
+ <div style="vertical-align:top; font-size:8pt; font: Arial;" id="cha"></div>
</td>
</tr>
</table>
<script>
-var x_axis_height = 16 + 16;
+var caliper_headroom = 16;
+var x_axis_height = 16 + /* scroll */ 16;
var y_axis_width = 24;
var xlen = 600;
var ylen = 400;
@@ -131,8 +140,15 @@ var viewport_offset_time = 0;
var dirty = 1;
var loading = 0;
+var caliper;
+var cal_pos = new Array(2);
+var measure = 0;
+var drag_index = -1;
+var caliperV = new Array();
+var caliperA = new Array();
+var caliperW = new Array();
+
var rates = [ 10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 4, 3, 2, 1 ];
-var srates = [ "1s", "500ms", "200ms", "100ms", "50ms", "20ms", "10ms", "5ms", "2ms", "1ms", "500us", "400us", "300us", "200us", "100us" ];
var intervals = [ 50, 20, 10, 5, 2, 1, 1, 0.5, 0.25, 0.1, 0.05, 0.05, 0.025, 0.025, 0.01 ];
var sigs = [ 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 2, 2, 3, 3, 2 ];
@@ -159,13 +175,94 @@ function event_wheel(e)
return cancelEvent(e);
}
+function event_drag_start(e)
+{
+ var x, y, n;
+ if (e.offsetX) {
+ x = e.offsetX;
+ y = e.offsetY;
+ } else {
+ x = e.layerX - offsetX;
+ y = e.layerY - offsetY;
+ }
+
+ drag_index = -1;
+ for (n = 0; n < 2; n++) {
+ if (x > cal_pos[n] - 8 && x < cal_pos[n] + 16)
+ drag_index = n;
+ }
+
+ e.stopPropagation ();
+}
+
+function event_drag(e)
+{
+ var x, y;
+ if (e.offsetX) {
+ x = e.offsetX;
+ y = e.offsetY;
+ } else {
+ x = e.layerX - offsetX;
+ y = e.layerY - offsetY;
+ }
+
+ if (drag_index >= 0) {
+ cal_pos[drag_index] = x;
+ dirty = 1;
+ caliper_changed();
+ }
+
+ e.stopPropagation ();
+ return false;
+}
+
+function event_drag_end(e)
+{
+ drag_index = -1;
+}
+
+function update_measure()
+{
+ measure = document.getElementById("measure").checked;
+ localStorage["aepd.measure"] = measure;
+ dirty = 1;
+ caliper_changed();
+}
+
+function caliper_changed()
+{
+ var f;
+
+ if (!measure) {
+ document.getElementById("val2").textContent = "";
+ return;
+ }
+
+ f = rates[rate] * 0.0001 * (cal_pos[0] - cal_pos[1]);
+ if (f < 0)
+ f = -f;
+
+ document.getElementById("val2").textContent = units(f)+"s / "+ units(1/f)+"Hz";
+
+ /*
+ * caliper position in s behind rhs
+ */
+
+ if (socket)
+ if (socket.readyState == 1)
+ socket.send("c" + ((xlen - cal_pos[0]) * rates[rate] * 0.0001)+" "+
+ ((xlen - cal_pos[1]) * rates[rate] * 0.0001)+"\n");
+}
+
+
function read_local_stg(stg_name, element_name, default_value)
{
var n;
if (localStorage[stg_name]) {
n = parseFloat(localStorage[stg_name]);
- document.getElementById(element_name).value = n;
+ if (element_name.length)
+ document.getElementById(element_name).value = n;
return n;
}
@@ -174,7 +271,12 @@ function read_local_stg(stg_name, element_name, default_value)
rate = read_local_stg("aepd.rate", "rate", rate);
watts_fullscale = read_local_stg("aepd.scale", "scale", watts_fullscale);
-
+if (localStorage["aepd.measure"])
+ document.getElementById("measure").checked = localStorage["aepd.measure"];
+update_measure();
+cal_pos[0] = read_local_stg("aepd.caliper0", "", 100);
+cal_pos[1] = read_local_stg("aepd.caliper1", "", 200);
+caliper_changed();
const CHFLAG_TOPLEVEL = 1;
const CHFLAG_VIRTUAL = 2;
@@ -212,7 +314,7 @@ function get_toplevel_supply(name)
function do_all_children(name, depth, total_depth)
{
- var n;
+ var n, cl;
if (depth > worst_depth)
worst_depth = depth;
@@ -227,11 +329,17 @@ function do_all_children(name, depth, total_depth)
if (total_depth) {
tab += '<tr><td style="background-color: '+ch_colour[n]+'">&nbsp;&nbsp;&nbsp;&nbsp;</td>';
tab += '<td colspan="'+ depth +'">&nbsp;</td>';
- tab += '<td colspan="'+(total_depth - depth)+'"><font style="color:' + fcol + '">'+ name;
- if (ch_flags[n] & CHFLAG_TOPLEVEL)
- tab += ' top ';
- tab += ' '+ch_flags[n]+' ';
- tab += "</font></td><td>-</td><td>-</td><td>-</td></tr>";
+ tab += '<td colspan="'+(total_depth - depth)+'" ';
+ if (ch_flags[n] & CHFLAG_TOPLEVEL) {
+ tab += ' class="topmeasname"';
+ cl = "topmeas";
+ } else {
+ tab += ' class="measname"';
+ cl = "meas";
+ }
+ tab += '><font style="color:' + fcol + '">';
+ tab += name;
+ tab += '</font></td><td><div id="v'+n+'" class="'+cl+'"></div></td><td><div id="a'+n+'" class="'+cl+'"></div></td><td><div id="w'+n+'" class="'+cl+'"></div></td></tr>';
ch_order[order++] = n;
}
for (n = 0; n < ov; n++) {
@@ -264,6 +372,16 @@ function get_appropriate_ws_url()
return pcol + u[0];
}
+function caliper_data_update()
+{
+ var n;
+ for (n = 0; n < chans; n++) {
+ document.getElementById("v"+n).textContent = units3(caliperV[n])+"V";
+ document.getElementById("a"+n).textContent = units3(caliperA[n])+"A";
+ document.getElementById("w"+n).textContent = units3(caliperW[n])+"W";
+ }
+}
+
function conn_retry()
{
var r1, r2, r3, x, y, chan, z, block, channel_data = 0;
@@ -281,6 +399,10 @@ function conn_retry()
for (n = 0; n < xlen; n++)
for (i = 0; i < max_chans; i++)
ringW[i][n] = 0;
+
+ caliper = new Image();
+ caliper.src = "caliper.png";
+
last_head = -1;
ring_head = -1;
grayOut(false);
@@ -290,6 +412,10 @@ function conn_retry()
socket.onmessage = function got_packet(msg) {
+ /*
+ * 'time' in capture context update
+ */
+
if (msg.data[0] == 't') {
f = msg.data.substr(1);
@@ -306,6 +432,54 @@ function conn_retry()
return;
}
+ /*
+ * caliper measurement info
+ */
+
+ if (msg.data[0] == 'c') {
+ f = msg.data.substr(1);
+ y = f.split(',');
+
+ chan = 0;
+ while (chan < y.length) {
+
+ x = y[chan].split(' ');
+ if (x.length != 3) {
+ chan++;
+ continue;
+ }
+
+ if (x[0].charAt(0) == '-')
+ r1 = (-x[0].substring(1));
+ else
+ r1 = (+x[0]);
+
+ if (x[1].charAt(0) == '-')
+ r2 = (-x[1].substring(1));
+ else
+ r2 = (+x[1]);
+
+ if (x[2].charAt(0) == '-')
+ r3 = (-x[2].substring(1));
+ else
+ r3 = (+x[2]);
+
+
+ caliperV[chan] = r1;
+ caliperA[chan] = r2;
+ caliperW[chan] = r3;
+
+ chan++;
+ }
+ caliper_data_update();
+
+ }
+
+ /*
+ * list of channel (incl virtual ones) names
+ * and other attributes
+ */
+
if (msg.data[0] == '=') {
channel_data = 1;
do_title = 1;
@@ -313,6 +487,10 @@ function conn_retry()
} else
channel_data = 0;
+ /*
+ * default message, sample set
+ */
+
z = msg.data.split(';');
block = 0;
@@ -418,10 +596,10 @@ function conn_retry()
for (n = 0; n < worst_depth; n++)
tab += "<td>&nbsp;</td>";
- tab+= '<td align=center style="vertical-align:middle"><font style="color: #ffffff">Rail</font></td>';
- tab+= '<td align=center style="vertical-align:middle"><font style="color: #ffffff">V</font></td>';
- tab+= '<td align=center style="vertical-align:middle"><font style="color: #ffffff">A</font></td>';
- tab+= '<td align=center style="vertical-align:middle"><font style="color: #ffffff">W</font></td>';
+ tab+= '<td class="heading">Rail</td>';
+ tab+= '<td class="heading" width="45">Voltage</td>';
+ tab+= '<td class="heading" width="80">Current</td>';
+ tab+= '<td class="heading" width="80">Power</td>';
tab += '</tr>';
done_list = new Array;
@@ -464,6 +642,18 @@ function conn_retry()
}
}
+function _trim(f)
+{
+ var f, c;
+
+ while (f.charAt(f.length - 1) == '0')
+ f = f.substring(0, f.length - 1);
+ if (f.charAt(f.length - 1) == '.')
+ f = f.substring(0, f.length - 1);
+
+ return f;
+}
+
function units(n) {
var s = '';
@@ -472,23 +662,63 @@ function units(n) {
n = -n;
}
- if (n >= 1000000000) {
- b = n % 1000000;
- return (s + (n - b) / 1000000000);
- } else
- if (n >= 1000000) {
- b = n % 1000;
- return (s + (n - b) / 1000000 + 'm');
- } else
- if (n >= 1000)
- return (s + (n / 1000).toFixed(3) + 'u');
- else
- if (n != 0)
- return (s + n.toFixed(1) + 'n');
+ if (n > 1000000000)
+ return s + _trim((n / 1000000000).toFixed(3)) + 'G';
+
+ if (n > 1000000)
+ return s + _trim((n / 1000000).toFixed(3)) + 'M';
+
+ if (n > 1000)
+ return s + _trim((n / 1000).toFixed(3)) + 'K';
+
+ if (n >= 1)
+ return s + _trim(n.toFixed(3));
+
+ if (n >= 0.001)
+ return s+ _trim((n * 1000).toFixed(3)) + 'm';
+
+ if (n >= 0.000001)
+ return s+ _trim((n * 1000000).toFixed(3)) + 'u';
+
+ if (n >= 0.000000001)
+ return s+ _trim((n * 1000000000).toFixed(3)) + 'n';
+
+ return ('0');
+}
+
+function units3(n) {
+ var s = '';
+
+ if (n < 0) {
+ s = '-';
+ n = -n;
+ }
+
+ if (n > 1000000000)
+ return s + ((n / 1000000000).toFixed(3)) + 'G';
+
+ if (n > 1000000)
+ return s + ((n / 1000000).toFixed(3)) + 'M';
+
+ if (n > 1000)
+ return s + ((n / 1000).toFixed(3)) + 'K';
+
+ if (n >= 1)
+ return s + (n.toFixed(3));
+
+ if (n >= 0.001)
+ return s+ ((n * 1000).toFixed(3)) + 'm';
+
+ if (n >= 0.000001)
+ return s+ ((n * 1000000).toFixed(3)) + 'u';
+
+ if (n >= 0.000000001)
+ return s+ ((n * 1000000000).toFixed(3)) + 'n';
return ('0');
}
+
function show(cv)
{
var n, p, y, chan, q, z, peak, ro, rx, extent, x2, xx;
@@ -511,7 +741,7 @@ function show(cv)
ctx.fillStyle = '#ffffff';
if (running == 0)
ctx.fillStyle = '#f0f0f0';
- ctx.fillRect(0, 0, xlen + y_axis_width, ylen + x_axis_height);
+ ctx.fillRect(0, 0, xlen + y_axis_width, ylen + x_axis_height + caliper_headroom);
/*
@@ -536,13 +766,13 @@ function show(cv)
// document.getElementById("val").textContent = peak_time+" "+ro+" "+extent+" "+rx+" "+x;
- ctx.fillRect(x, ylen + x_axis_height - 12, xx, 8);
+ ctx.fillRect(x, ylen + x_axis_height + caliper_headroom - 12, xx, 8);
q = ring_head;
last_head = q;
for (n = 0; n < xlen; n++)
- height[n] = ylen -2;
+ height[n] = caliper_headroom + ylen -2;
chan = 0;
while (chan < ch_order.length) {
@@ -559,7 +789,7 @@ function show(cv)
for (n = xlen - 1; n >= 0; n--) {
q--;
- if (q == 0)
+ if (q < 0)
q = ring_size - 1;
ctx.beginPath();
@@ -568,8 +798,8 @@ function show(cv)
p = (ringW[z][q] * (ylen - 4)) / watts_fullscale;
if ((ch_flags[z] & (CHFLAG_TOPLEVEL | CHFLAG_VIRTUAL)) == CHFLAG_TOPLEVEL) {
- ctx.moveTo(n + 0.5, ylen - 2);
- ctx.lineTo(n + 0.5, ylen - 2 - p);
+ ctx.moveTo(n + 0.5, caliper_headroom + ylen - 2);
+ ctx.lineTo(n + 0.5, caliper_headroom + ylen - 2 - p);
} else {
ctx.moveTo(n + 0.5, height[n]);
@@ -619,11 +849,11 @@ function show(cv)
if (x < xlen) {
ctx.beginPath();
- ctx.moveTo(x, ylen);
- ctx.lineTo(x, 1);
+ ctx.moveTo(x, caliper_headroom + ylen);
+ ctx.lineTo(x, caliper_headroom + 1);
ctx.stroke();
}
- ctx.fillText(f, x, ylen + 16 - 2);
+ ctx.fillText(f, x, caliper_headroom + ylen + 16 - 2);
}
r -= interval;
}
@@ -670,16 +900,34 @@ function show(cv)
if ( r.toFixed(0) == r)
ctx.lineWidth = 2;
- ctx.beginPath();
- ctx.moveTo(0, y - 2);
- ctx.lineTo(xlen, y - 2);
- ctx.stroke();
+ if (y >= 0) {
+ ctx.beginPath();
+ ctx.moveTo(0, caliper_headroom + y - 2);
+ ctx.lineTo(xlen, caliper_headroom + y - 2);
+ ctx.stroke();
+ }
- ctx.fillText(f, xlen + (16 / 2), y + 2);
+ ctx.fillText(f, xlen + 8, caliper_headroom +y + 2);
r -= interval;
}
+ /*
+ * calipers
+ */
+
+ if (measure) {
+ ctx.strokeStyle = 'rgba(160, 160, 160, 0.5)';
+ ctx.lineWidth = 1;
+
+ for (n = 0; n < 2; n++) {
+ ctx.drawImage(caliper, cal_pos[n], 0);
+ ctx.beginPath();
+ ctx.moveTo(cal_pos[n] + 8, caliper_headroom);
+ ctx.lineTo(cal_pos[n] + 8, caliper_headroom + ylen - 2);
+ ctx.stroke();
+ }
+ }
ctx.restore();
}
@@ -693,8 +941,10 @@ function update_options()
run = 1;
rate = document.getElementById("rate").value;
+ if (!rate)
+ rate = 0;
localStorage["aepd.rate"] = document.getElementById("rate").value;
- document.getElementById("tb").textContent = srates[rate];
+ document.getElementById("tb").textContent = units(xlen * rates[rate] * 0.0001)+"s";
if (viewport_offset_time < (-peak_time + (rates[rate] * 0.0001 * xlen) - 0.0001))
viewport_offset_time = (-peak_time + (rates[rate] * 0.0001 * xlen) - 0.0001);
@@ -706,6 +956,8 @@ function update_options()
watts_fullscale = document.getElementById("scale").value;
localStorage["aepd.scale"] = document.getElementById("scale").value;
document.getElementById("pz").textContent = watts_fullscale + 'W';
+
+ caliper_changed();
}
/*
@@ -836,13 +1088,27 @@ for (n = 0; n < max_chans; n++) {
for (n = 0; n < 1; n++) {
canvas[n] = document.getElementById('canvas0');
- canvas[n].height = ylen + x_axis_height;
+ canvas[n].height = ylen + x_axis_height + caliper_headroom;
canvas[n].width = xlen + y_axis_width;
// document.getElementById('pane0').appendChild(canvas[n]);
}
hookEvent(document.getElementById("canvas0"), "mousewheel", event_wheel);
+hookEvent(document.getElementById("canvas0"), "mousedown", event_drag_start);
+hookEvent(document.getElementById("canvas0"), "mousemove", event_drag);
+hookEvent(document.getElementById("canvas0"), "mouseup", event_drag_end);
+
+offsetX = offsetY = 0;
+element = canvas;
+if (element.offsetParent) {
+do {
+ offsetX += element.offsetLeft;
+ offsetY += element.offsetTop;
+} while ((element = element.offsetParent));
+}
+
+
grayOut(true,{'zindex':'499'});
conn_retry();
@@ -899,6 +1165,275 @@ function unhookEvent(element, eventName, callback)
}
+/*
+html5slider - a JS implementation of <input type=range> for Firefox 4 and up
+https://github.com/fryn/html5slider
+
+Copyright (c) 2010-2011 Frank Yan, <http://frankyan.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+(function() {
+
+// test for native support
+var test = document.createElement('input');
+try {
+ test.type = 'range';
+ if (test.type == 'range')
+ return;
+} catch (e) {
+ return;
+}
+
+// test for required property support
+if (!document.mozSetImageElement || !('MozAppearance' in test.style))
+ return;
+
+var scale;
+var isMac = navigator.platform == 'MacIntel';
+var thumb = {
+ radius: isMac ? 9 : 6,
+ width: isMac ? 22 : 12,
+ height: isMac ? 16 : 20
+};
+var track = '-moz-linear-gradient(top, transparent ' + (isMac ?
+ '6px, #999 6px, #999 7px, #ccc 9px, #bbb 11px, #bbb 12px, transparent 12px' :
+ '9px, #999 9px, #bbb 10px, #fff 11px, transparent 11px') +
+ ', transparent)';
+var styles = {
+ 'min-width': thumb.width + 'px',
+ 'min-height': thumb.height + 'px',
+ 'max-height': thumb.height + 'px',
+ padding: 0,
+ border: 0,
+ 'border-radius': 0,
+ cursor: 'default',
+ 'text-indent': '-999999px' // -moz-user-select: none; breaks mouse capture
+};
+var onChange = document.createEvent('HTMLEvents');
+onChange.initEvent('change', true, false);
+
+if (document.readyState == 'loading')
+ document.addEventListener('DOMContentLoaded', initialize, true);
+else
+ initialize();
+
+function initialize() {
+ // create initial sliders
+ Array.forEach(document.querySelectorAll('input[type=range]'), transform);
+ // create sliders on-the-fly
+ document.addEventListener('DOMNodeInserted', onNodeInserted, true);
+}
+
+function onNodeInserted(e) {
+ check(e.target);
+ if (e.target.querySelectorAll)
+ Array.forEach(e.target.querySelectorAll('input'), check);
+}
+
+function check(input, async) {
+ if (input.localName != 'input' || input.type == 'range');
+ else if (input.getAttribute('type') == 'range')
+ transform(input);
+ else if (!async)
+ setTimeout(check, 0, input, true);
+}
+
+function transform(slider) {
+
+ var isValueSet, areAttrsSet, isChanged, isClick, prevValue, rawValue, prevX;
+ var min, max, step, range, value = slider.value;
+
+ // lazily create shared slider affordance
+ if (!scale) {
+ scale = document.body.appendChild(document.createElement('hr'));
+ style(scale, {
+ '-moz-appearance': isMac ? 'scale-horizontal' : 'scalethumb-horizontal',
+ display: 'block',
+ visibility: 'visible',
+ opacity: 1,
+ position: 'fixed',
+ top: '-999999px'
+ });
+ document.mozSetImageElement('__sliderthumb__', scale);
+ }
+
+ // reimplement value and type properties
+ var getValue = function() { return '' + value; };
+ var setValue = function setValue(val) {
+ value = '' + val;
+ isValueSet = true;
+ draw();
+ delete slider.value;
+ slider.value = value;
+ slider.__defineGetter__('value', getValue);
+ slider.__defineSetter__('value', setValue);
+ };
+ slider.__defineGetter__('value', getValue);
+ slider.__defineSetter__('value', setValue);
+ slider.__defineGetter__('type', function() { return 'range'; });
+
+ // sync properties with attributes
+ ['min', 'max', 'step'].forEach(function(prop) {
+ if (slider.hasAttribute(prop))
+ areAttrsSet = true;
+ slider.__defineGetter__(prop, function() {
+ return this.hasAttribute(prop) ? this.getAttribute(prop) : '';
+ });
+ slider.__defineSetter__(prop, function(val) {
+ val === null ? this.removeAttribute(prop) : this.setAttribute(prop, val);
+ });
+ });
+
+ // initialize slider
+ slider.readOnly = true;
+ style(slider, styles);
+ update();
+
+ slider.addEventListener('DOMAttrModified', function(e) {
+ // note that value attribute only sets initial value
+ if (e.attrName == 'value' && !isValueSet) {
+ value = e.newValue;
+ draw();
+ }
+ else if (~['min', 'max', 'step'].indexOf(e.attrName)) {
+ update();
+ areAttrsSet = true;
+ }
+ }, true);
+
+ slider.addEventListener('mousedown', onDragStart, true);
+ slider.addEventListener('keydown', onKeyDown, true);
+ slider.addEventListener('focus', onFocus, true);
+ slider.addEventListener('blur', onBlur, true);
+
+ function onDragStart(e) {
+ isClick = true;
+ setTimeout(function() { isClick = false; }, 0);
+ if (e.button || !range)
+ return;
+ var width = parseFloat(getComputedStyle(this, 0).width);
+ var multiplier = (width - thumb.width) / range;
+ if (!multiplier)
+ return;
+ // distance between click and center of thumb
+ var dev = e.clientX - this.getBoundingClientRect().left - thumb.width / 2 -
+ (value - min) * multiplier;
+ // if click was not on thumb, move thumb to click location
+ if (Math.abs(dev) > thumb.radius) {
+ isChanged = true;
+ this.value -= -dev / multiplier;
+ }
+ rawValue = value;
+ prevX = e.clientX;
+ this.addEventListener('mousemove', onDrag, true);
+ this.addEventListener('mouseup', onDragEnd, true);
+ }
+
+ function onDrag(e) {
+ var width = parseFloat(getComputedStyle(this, 0).width);
+ var multiplier = (width - thumb.width) / range;
+ if (!multiplier)
+ return;
+ rawValue += (e.clientX - prevX) / multiplier;
+ prevX = e.clientX;
+ isChanged = true;
+ this.value = rawValue;
+ }
+
+ function onDragEnd() {
+ this.removeEventListener('mousemove', onDrag, true);
+ this.removeEventListener('mouseup', onDragEnd, true);
+ }
+
+ function onKeyDown(e) {
+ if (e.keyCode > 36 && e.keyCode < 41) { // 37-40: left, up, right, down
+ onFocus.call(this);
+ isChanged = true;
+ this.value = value + (e.keyCode == 38 || e.keyCode == 39 ? step : -step);
+ }
+ }
+
+ function onFocus() {
+ if (!isClick)
+ this.style.boxShadow = !isMac ? '0 0 0 2px #fb0' :
+ '0 0 2px 1px -moz-mac-focusring, inset 0 0 1px -moz-mac-focusring';
+ }
+
+ function onBlur() {
+ this.style.boxShadow = '';
+ }
+
+ // determines whether value is valid number in attribute form
+ function isAttrNum(value) {
+ return !isNaN(value) && +value == parseFloat(value);
+ }
+
+ // validates min, max, and step attributes and redraws
+ function update() {
+ min = isAttrNum(slider.min) ? +slider.min : 0;
+ max = isAttrNum(slider.max) ? +slider.max : 100;
+ if (max < min)
+ max = min > 100 ? min : 100;
+ step = isAttrNum(slider.step) && slider.step > 0 ? +slider.step : 1;
+ range = max - min;
+ draw(true);
+ }
+
+ // recalculates value property
+ function calc() {
+ if (!isValueSet && !areAttrsSet)
+ value = slider.getAttribute('value');
+ if (!isAttrNum(value))
+ value = (min + max) / 2;;
+ // snap to step intervals (WebKit sometimes does not - bug?)
+ value = Math.round((value - min) / step) * step + min;
+ if (value < min)
+ value = min;
+ else if (value > max)
+ value = min + ~~(range / step) * step;
+ }
+
+ // renders slider using CSS background ;)
+ function draw(attrsModified) {
+ calc();
+ if (isChanged && value != prevValue)
+ slider.dispatchEvent(onChange);
+ isChanged = false;
+ if (!attrsModified && value == prevValue)
+ return;
+ prevValue = value;
+ var position = range ? (value - min) / range * 100 : 0;
+ var bg = '-moz-element(#__sliderthumb__) ' + position + '% no-repeat, ';
+ style(slider, { background: bg + track });
+ }
+
+}
+
+function style(element, styles) {
+ for (var prop in styles)
+ element.style.setProperty(prop, styles[prop], 'important');
+}
+
+})();
+
</script>
</article>
</body>
diff --git a/aepd/share/caliper.png b/aepd/share/caliper.png
new file mode 100644
index 0000000..fac7b3d
--- /dev/null
+++ b/aepd/share/caliper.png
Binary files differ
diff --git a/aepd/websocket-protocol.c b/aepd/websocket-protocol.c
index 43bb8eb..68eeb73 100644
--- a/aepd/websocket-protocol.c
+++ b/aepd/websocket-protocol.c
@@ -34,6 +34,7 @@ struct serveable {
static struct serveable whitelist[] = {
{ "/favicon.ico", "image/x-icon" },
{ "/linaro-logo-32.png", "image/png" },
+ { "/caliper.png", "image/png" },
/* last one is the default served if no match */
{ "/aepscope.html", "text/html" },
@@ -83,6 +84,9 @@ struct per_session_data__linaro_aepd {
int issue_timestamp;
int viewport_budget;
double viewport_offset_time;
+ long caliper_offset[2];
+ unsigned long ms10_last_caliper;
+ int seen_rate;
};
extern struct aep_context aep_context;
@@ -96,13 +100,16 @@ callback_linaro_aepd(struct libwebsocket_context *context,
int n, m;
struct per_session_data__linaro_aepd *pss = user;
double sam[MAX_PROBES * CHANNELS_PER_PROBE * 3];
+ double sam2[MAX_PROBES * CHANNELS_PER_PROBE * 3];
double d[10];
- char buf[LWS_SEND_BUFFER_PRE_PADDING + 16384 + LWS_SEND_BUFFER_POST_PADDING];
+ char buf[LWS_SEND_BUFFER_PRE_PADDING + 32768 + LWS_SEND_BUFFER_POST_PADDING];
char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
int budget = 10;
int no_valid_sam_flag = 0;
long extent;
long l;
+ struct timeval tv;
+ unsigned long ms10;
switch (reason) {
@@ -115,10 +122,59 @@ callback_linaro_aepd(struct libwebsocket_context *context,
pss->issue_timestamp = 1;
pss->viewport_offset_time = 0;
pss->viewport_budget = 0;
+ pss->caliper_offset[0] = -1;
+ pss->caliper_offset[1] = -1;
+ pss->seen_rate = 0;
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
+ gettimeofday(&tv, NULL);
+ ms10 = (tv.tv_sec * 10) + (tv.tv_usec / 100000);
+
+ if (ms10 != pss->ms10_last_caliper) {
+ pss->ms10_last_caliper = ms10;
+
+ l = pss->ringbuffer_tail - pss->caliper_offset[0];
+ if (l < 0)
+ l += aepd_shared->modulo_integer_chan_size;
+ lseek(aepd_shared->fd_fifo_read, l, SEEK_SET);
+ if (read(aepd_shared->fd_fifo_read, &sam[0], aepd_shared->chans * sizeof(double) * 3) < 0)
+ fprintf(stderr, "fifo read fail\n");
+
+ l = pss->ringbuffer_tail - pss->caliper_offset[1];
+ if (l < 0)
+ l += aepd_shared->modulo_integer_chan_size;
+ lseek(aepd_shared->fd_fifo_read, l, SEEK_SET);
+ if (read(aepd_shared->fd_fifo_read, &sam2[0], aepd_shared->chans * sizeof(double) * 3) < 0)
+ fprintf(stderr, "fifo read fail\n");
+
+ extent = (pss->caliper_offset[1] - pss->caliper_offset[0]) / (aepd_shared->chans * sizeof(double) * 3);
+
+// fprintf(stderr, "calp %ld %ld %ld\n", extent, pss->caliper_offset[0], pss->caliper_offset[1]);
+
+ *p++ = 'c';
+ m = 0;
+ for (n = 0; n < aepd_shared->chans; n++) {
+
+ p += sprintf(p, "%f %f %f",
+ (sam[m] - sam2[m]) / (double)extent,
+ (sam[m + 1] - sam2[m + 1]) / (double)extent,
+ (sam[m + 2] - sam2[m + 2]) / (double)extent
+ );
+
+ if (n + 1 != aepd_shared->chans) {
+ *p++ = ',';
+ *p = '\0';
+ }
+ m += 3;
+ }
+
+ *p = '\0';
+// puts(&buf[LWS_SEND_BUFFER_PRE_PADDING]);
+ goto send;
+ }
+
if (pss->issue_timestamp) {
pss->issue_timestamp = 0;
@@ -137,7 +193,8 @@ callback_linaro_aepd(struct libwebsocket_context *context,
extent /= aepd_shared->chans * sizeof(double) * 3;
p += sprintf(p, "t%f %d", aepd_shared->fifo_head_time - ((double)extent * 0.0001), aepd_shared->stop_flag ^ 1);
- fprintf(stderr, "timestamp headtime=%f aepd_shared->fifo_pos=%ld extent=%ld, l=%ld\n", aepd_shared->fifo_head_time, aepd_shared->fifo_pos, extent, l);
+ fprintf(stderr, "timestamp headtime=%f aepd_shared->fifo_pos=%ld extent=%ld, l=%ld\n",
+ aepd_shared->fifo_head_time, aepd_shared->fifo_pos, extent, l);
puts(&buf[LWS_SEND_BUFFER_PRE_PADDING]);
goto send;
}
@@ -161,8 +218,9 @@ callback_linaro_aepd(struct libwebsocket_context *context,
/*
* if we're not following the samples head, we don't need to
- * spam the viewport any more than one viewport's worth of samples
- * we're still collecting samples, he can get them by moving his
+ * spam the viewport any more than one viewport's worth of samples.
+ *
+ * We're still collecting samples, he can get them by moving his
* viewport offset
*/
@@ -176,6 +234,9 @@ callback_linaro_aepd(struct libwebsocket_context *context,
* aggregate up to 'budget' results in one websocket message
*/
+ if (!pss->seen_rate)
+ return 0;
+
while (budget--) {
if (pss->ringbuffer_tail <= (long)aepd_shared->fifo_pos)
@@ -218,8 +279,15 @@ callback_linaro_aepd(struct libwebsocket_context *context,
* Javascript can't cope with binary, so we must ascii-fy it
*/
+ if (aepd_shared->chans > 9 || aepd_shared->chans < 0)
+ fprintf(stderr, "insane chans %d\n", aepd_shared->chans);
+
m = 0;
for (n = 0; n < aepd_shared->chans; n++) {
+
+ if ((p - &buf[LWS_SEND_BUFFER_PRE_PADDING]) > (sizeof(buf) - 16384))
+ fprintf(stderr, "insane buffer usage! budget=%d\n", budget);
+
p += sprintf(p, "%f %f %f",
(sam[m] - pss->sam[m]) / (double)pss->stride,
(sam[m + 1] - pss->sam[m + 1]) / (double)pss->stride,
@@ -240,6 +308,11 @@ callback_linaro_aepd(struct libwebsocket_context *context,
memcpy(&pss->sam[0], &sam[0], aepd_shared->chans * sizeof(double) * 3);
pss->sam_valid = 1;
}
+
+ if ((p - &buf[LWS_SEND_BUFFER_PRE_PADDING]) > (sizeof(buf) - 16384))
+ budget = 0;
+
+
}
send:
/*
@@ -263,7 +336,28 @@ send:
case LWS_CALLBACK_RECEIVE:
switch (*(char *)in) {
- case 'r':
+ case 'c': /* calipers changed */
+// puts((char *)in + 1);
+ if (sscanf(((char *)in) + 1, "%lf %lf\n", &d[0], &d[1]) == 2) {
+ if (d[0] > d[1]) {
+ d[2] = d[1];
+ d[1] = d[0];
+ d[0] = d[2];
+ }
+
+ /*
+ * caliper positions in seconds behind rhs -->
+ * byte offset behind rhs in ringbuffer
+ */
+ for (n = 0; n < 2; n++)
+ pss->caliper_offset[n] = (d[n] * 10000 * aepd_shared->chans * sizeof(double) * 3);
+
+// fprintf(stderr, " %ld %ld\n", pss->caliper_offset[0], pss->caliper_offset[1]);
+ } else
+ fprintf(stderr, "caliper sscanf failed\n");
+ break;
+
+ case 'r': /* rate or other change */
puts((char *)in + 1);
if (sscanf(((char *)in) + 1, "%lf %lf %lf %lf\n", &d[0], &d[1], &d[2], &d[3]) == 4) {
pss->stride = (int)d[0];
@@ -277,6 +371,7 @@ send:
pss->ringbuffer_tail = l;
aepd_shared->stop_flag = ((int)d[2]) ^ 1;
pss->issue_timestamp = 1;
+ pss->seen_rate = 1;
libwebsocket_callback_on_writable(context, wsi);
} else
fprintf(stderr, "sscanf failed\n");
diff --git a/config b/config
index 793eb34..01e02b7 100644
--- a/config
+++ b/config
@@ -5,7 +5,7 @@ PandaBoardES-B1-ANDY
# <device path>
/dev/ttyACM0
- VDD_VCORE1 0.220000 -14.500000 -0.019856 0.011774 -0.000311 0.000943 0 SoCVCORE1/MPU VBAT #ff0000 SoC
+ VDD_VCORE1 0.220000 -14.500000 -0.015950 0.011774 -0.000311 0.000943 0 SoCVCORE1/MPU VBAT #ff0000 SoC
VDD_VCORE2 0.470000 -45.000000 -0.015710 0.007876 -0.002165 0.000874 0 SoCVCORE2/IVA\\_AUDIO VBAT #c00000 SoC
VDD_VCORE3 0.470000 -27.500000 -0.016074 0.002175 -0.000272 0.000625 0 SoCVCORE3/CORE VBAT #a00000 SoC