diff options
Diffstat (limited to 'web')
| -rw-r--r-- | web/Dockerfile | 3 | ||||
| -rw-r--r-- | web/index.html | 38 | ||||
| -rw-r--r-- | web/main.js | 25 | ||||
| -rw-r--r-- | web/plot.js | 81 | ||||
| -rw-r--r-- | web/style.css | 3 |
5 files changed, 96 insertions, 54 deletions
diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..d728eb4 --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,3 @@ +FROM nginx:alpine +COPY . /usr/share/nginx/html +RUN chmod 555 -R /usr/share/nginx/html diff --git a/web/index.html b/web/index.html index 5e8bd6a..34215ff 100644 --- a/web/index.html +++ b/web/index.html @@ -24,15 +24,32 @@ <label id="status">(disconnected)</label> <button id="connect">connect</button> </div> + <h2>temperatures</h2> <div> - <label>chamber temp</label> + <label>chamber</label> <label id="temps_chamber">-</label> </div> <div> - <label>controller temp</label> + <label>controller</label> <label id="temps_controller">-</label> </div> <div> + <label>export traces</label> + <button id="export" disabled>download csv</button> + </div> + <h2>manual controls</h2> + <div> + <label for="heat">heat</label> + <input id="heat" class="grow" type="range" min="0" max="255" value="0" /> + <input id="heat_mode" type="checkbox" /> + </div> + <div> + <label for="cool">cool</label> + <input id="cool" class="grow" type="range" min="0" max="255" value="0" /> + <input id="cool_mode" type="checkbox" /> + </div> + <h2>stored profiles</h2> + <div> <select id="profile" value="" class="grow"> <option value="">none / manual</option> <option value="0">profile 1</option> @@ -51,19 +68,6 @@ <button id="write" class="grow" disabled>write</button> </div> </section> - <section> - <h1>manual control</h1> - <div> - <label for="heat">heat</label> - <input id="heat" class="grow" type="range" min="0" max="255" value="0" /> - <input id="heat_mode" type="checkbox" /> - </div> - <div> - <label for="cool">cool</label> - <input id="cool" class="grow" type="range" min="0" max="255" value="0" /> - <input id="cool_mode" type="checkbox" /> - </div> - </section> <section class="grow"> <h1> <span class="grow">reflow profile</span> @@ -100,6 +104,10 @@ <option value="" class="new">(new paste)</option> </select> </section> + <section> + help, information and source code: + <a href="https://git.s-ol.nu/t937-serial">git.s-ol.nu/t937-serial</a> + </section> </aside> <svg id="plot" /> <script type="module" src="main.js" ></script> diff --git a/web/main.js b/web/main.js index 6d29a50..e568bbc 100644 --- a/web/main.js +++ b/web/main.js @@ -102,7 +102,7 @@ plot.addPlot('temp3', { color: 'sienna' }); plot.addPlot('tempavg', { color: 'rebeccapurple' }); const elements = [ - 'connect', 'status', 'temps_chamber', 'temps_controller', + 'connect', 'status', 'temps_chamber', 'temps_controller', 'export', 'profile', 'start', 'read', 'write', 'heat', 'heat_mode', 'cool', 'cool_mode', 'profile_load', 'profile_save', 'profile_delete', 'profile_show', 'profile_edit', 'profile_clear', 'profile_stored', @@ -118,6 +118,7 @@ const updateUI = () => { el.start.disabled = !ready; el.read.disabled = !ready; el.write.disabled = !ready; + el.export.disabled = !plot.PLOTS.tempavg.data.length; el.profile_show.firstElementChild.firstElementChild.href.baseVal = plot.PLOTS.profile.visible ? '#i-eye' : '#i-no-eye'; el.paste_show.firstElementChild.firstElementChild.href.baseVal = plot.PLOTS.paste.visible ? '#i-eye' : '#i-no-eye'; @@ -278,6 +279,28 @@ for (const [slider, checkbox] of pwms) { }; } +el.export.onclick = () => { + const tavg = plot.PLOTS.tempavg.data; + const columns = [ + plot.PLOTS.temp1.data, + plot.PLOTS.temp2.data, + plot.PLOTS.tempavg.data, + plot.PLOTS.temp3.data, + ]; + + let csv = 'Seconds,Chamber Temp 1,Chamber Temp 2,Chamber Average,Controller Temp\n'; + for (let i = 0; i < tavg.length; i++) { + const data = columns.map(col => col[i][1]).join(','); + csv += `${tavg[i][0]},${data}\n`; + } + + const a = document.createElement('a'); + a.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv); + a.target = '_blank'; + a.download = `temps_${new Date().toISOString}.csv`; + a.click(); +}; + el.start.onclick = () => { if (STATUS === 'idle') { STATUS = el.profile.value === '' ? 'manual' : 'running'; diff --git a/web/plot.js b/web/plot.js index 98d8d88..cc40989 100644 --- a/web/plot.js +++ b/web/plot.js @@ -23,6 +23,46 @@ const min = d3.format(' '); const minsec = v => `${min(Math.floor(v / 60))}:${sec(Math.floor(v % 60))}`; const degc = v => `${Math.floor(v)}°C`; +const crosshair = svg.append('g') + .attr('display', 'none') + .attr('color', '#00000080') + .attr('font-size', 10) + .attr('font-family', 'sans-serif') + .attr('stroke-width', 0.5); +crosshair.append('text') + .attr('id', 'ctextx') + .attr('y', marginy) + .attr('dx', '0.32em') + .attr('dy', '1em') + .attr('fill', 'currentColor'); +crosshair.append('text') + .attr('id', 'ctexty') + .attr('x', marginx) + .attr('dx', '0.32em') + .attr('dy', '-0.32em') + .attr('fill', 'currentColor');; +crosshair.append('line') + .attr('id', 'clinex') + .attr('stroke', 'currentColor'); +crosshair.append('line') + .attr('id', 'cliney') + .attr('stroke', 'currentColor'); + +const updateCrosshair = ([px, py]) => { + const [time, temp] = [x.invert(px), y.invert(py)]; + + const width = svg.node().clientWidth; + const height = svg.node().clientHeight; + const inRange = marginx < px && px < width - marginx && + marginy < py && py < height - marginy; + + crosshair.attr('display', inRange ? null : 'none').lower(); + crosshair.select('#ctextx').text(minsec(time)).attr('x', px); + crosshair.select('#ctexty').text(degc(temp)).attr('y', py); + crosshair.select('#clinex').attr('x1', px).attr('x2', px).attr('y1', marginy).attr('y2', height - marginy); + crosshair.select('#cliney').attr('y1', py).attr('y2', py).attr('x1', marginx).attr('x2', width - marginx); +}; + export const addPlot = (name, options={}) => { const plot = Object.assign({ color: 'black', @@ -126,7 +166,9 @@ export const update = (transition=true) => { d3.select(this) .attr('cx', (d) => x(d[0])) .attr('cy', (d) => y(d[1])); + update(false); + updateCrosshair([e.x, e.y]); }) .on('end', () => points.attr('cursor', 'grab')) ); @@ -156,46 +198,9 @@ const onresize = () => { }; window.addEventListener('resize', onresize); -const crosshair = svg.append('g') - .attr('display', 'none') - .attr('color', '#00000080') - .attr('font-size', 10) - .attr('font-family', 'sans-serif') - .attr('stroke-width', 0.5); -crosshair.append('text') - .attr('id', 'ctextx') - .attr('y', marginy) - .attr('dx', '0.32em') - .attr('dy', '1em') - .attr('fill', 'currentColor'); -crosshair.append('text') - .attr('id', 'ctexty') - .attr('x', marginx) - .attr('dx', '0.32em') - .attr('dy', '-0.32em') - .attr('fill', 'currentColor');; -crosshair.append('line') - .attr('id', 'clinex') - .attr('stroke', 'currentColor'); -crosshair.append('line') - .attr('id', 'cliney') - .attr('stroke', 'currentColor'); - svg .on('mousemove', (e) => { - const [px, py] = d3.pointer(e); - const [time, temp] = [x.invert(px), y.invert(py)]; - - const width = svg.node().clientWidth; - const height = svg.node().clientHeight; - const inRange = marginx < px && px < width - marginx && - marginy < py && py < height - marginy; - - crosshair.attr('display', inRange ? null : 'none').lower(); - crosshair.select('#ctextx').text(minsec(time)).attr('x', px); - crosshair.select('#ctexty').text(degc(temp)).attr('y', py); - crosshair.select('#clinex').attr('x1', px).attr('x2', px).attr('y1', marginy).attr('y2', height - marginy); - crosshair.select('#cliney').attr('y1', py).attr('y2', py).attr('x1', marginx).attr('x2', width - marginx); + updateCrosshair(d3.pointer(e)); }) .on('dblclick', (e) => { const plot = ACTIVE && PLOTS[ACTIVE]; diff --git a/web/style.css b/web/style.css index 2f0db1a..c0fcf8e 100644 --- a/web/style.css +++ b/web/style.css @@ -29,6 +29,9 @@ aside h1 { aside h1 span { display: flex; } +aside h2 { + font-size: 1em; +} aside section { display: flex; flex-direction: column; |
