diff options
author | Andy Green <andy.green@linaro.org> | 2012-10-17 12:42:33 +0800 |
---|---|---|
committer | Andy Green <andy.green@linaro.org> | 2012-10-17 12:42:33 +0800 |
commit | 78f353909176a92632af496626eb968fc0892954 (patch) | |
tree | 69ab44fe0f2e2b05e84fe1c104648c96c52d91a4 | |
parent | 3f41cccaef284e02e1a0e6fd0bc028410d1471d1 (diff) |
add measuring calipers
Signed-off-by: Andy Green <andy.green@linaro.org>
-rw-r--r-- | aepd/Makefile.am | 5 | ||||
-rw-r--r-- | aepd/share/aepscope.html | 625 | ||||
-rw-r--r-- | aepd/share/caliper.png | bin | 0 -> 1365 bytes | |||
-rw-r--r-- | aepd/websocket-protocol.c | 105 | ||||
-rw-r--r-- | config | 2 |
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]+'"> </td>'; tab += '<td colspan="'+ depth +'"> </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> </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 Binary files differnew file mode 100644 index 0000000..fac7b3d --- /dev/null +++ b/aepd/share/caliper.png 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"); @@ -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 |