From 5bd2e786c2a0797d5117efcb02e0540563a8a3c8 Mon Sep 17 00:00:00 2001 From: ztimson Date: Fri, 3 Jul 2026 13:37:09 -0400 Subject: [PATCH] Fixed seismic sensors --- client/src/components/Seismic.vue | 167 +++++++++++++++++------------- sensors/main.py | 6 +- 2 files changed, 101 insertions(+), 72 deletions(-) diff --git a/client/src/components/Seismic.vue b/client/src/components/Seismic.vue index 3493565..2c654e3 100644 --- a/client/src/components/Seismic.vue +++ b/client/src/components/Seismic.vue @@ -5,11 +5,18 @@ import { api } from '../services/api'; import MetricRow from './MetricRow.vue'; const d = ref(null); -const history = ref([]); const canvas = ref(null); -const W = 640, H = 120, MAX_PTS = 120; + +const AXES = [ + { key: 'seismic_x', label: 'X', color: '#ccc' }, + { key: 'seismic_y', label: 'Y', color: '#ccc' }, + { key: 'seismic_z', label: 'Z', color: '#ccc' }, +]; + +const W = 640, BAND = 80, H = BAND * AXES.length, MAX_PTS = 120; const SEG_W = W / (MAX_PTS - 1); const SUB = 4; +const RIGHT_PAD = 2 * SEG_W; let interval: ReturnType; let rafId: number; @@ -17,35 +24,81 @@ let drawProgress = 0; let lastTimestamp = 0; const ANIM_DURATION = 1200; -let pointBuffer: { x: number; y: number }[] = []; -let prevMax = 1.0; +type Pt = { x: number; y: number }; +const traces = AXES.map(() => ({ + history: [] as number[], + prevMax: 1.0, + buffer: [] as Pt[], +})); -function buildBuffer(pts: number[]) { +function buildBuffer(t: (typeof traces)[number], band: number) { + const pts = t.history; if (pts.length < 2) return; - const mid = H / 2, amp = H / 2 - 6; + const mid = band * BAND + BAND / 2, amp = BAND / 2 - 6; const newMax = Math.max(...pts, 1.0); - prevMax = prevMax + (newMax - prevMax) * 0.15; - const buf: { x: number; y: number }[] = []; + t.prevMax = t.prevMax + (newMax - t.prevMax) * 0.15; // eased amplitude scale + const tipX = W - RIGHT_PAD; + const buf: Pt[] = []; pts.forEach((v, i) => { const prev = pts[i - 1] ?? v; - const max = prevMax; - const prevNorm = prev === 0 ? 0 : (prev / max) * amp; - const norm = v === 0 ? 0 : (v / max) * amp; - const baseX = i * SEG_W; + const prevNorm = prev === 0 ? 0 : (prev / t.prevMax) * amp; + const norm = v === 0 ? 0 : (v / t.prevMax) * amp; + const baseX = tipX - (pts.length - i) * SEG_W; const s = SEG_W / SUB; + // one oscillation per value, amplitude fading prevNorm → norm const a0 = prevNorm * 0.75 + norm * 0.25; const a1 = prevNorm * 0.25 + norm * 0.75; - const a2 = norm; - buf.push({ x: baseX, y: mid - a0 }); // up - buf.push({ x: baseX + s, y: mid - a1 }); // up peak - buf.push({ x: baseX + s * 2, y: mid + a2 }); // down - buf.push({ x: baseX + s * 3, y: mid }); // back to exact center ✓ + buf.push({ x: baseX + s, y: mid - a0 }); // up + buf.push({ x: baseX + s * 2, y: mid - a1 }); // up peak + buf.push({ x: baseX + s * 3, y: mid + norm }); // down + buf.push({ x: baseX + s * 4, y: mid }); // back to exact center ✓ }); - pointBuffer = buf; + t.buffer = buf; +} + +function drawTrace(ctx: CanvasRenderingContext2D, t: (typeof traces)[number], color: string) { + const buf = t.buffer; + if (buf.length <= SUB) return; + + // slide everything left as the new segment draws in + const slide = (1 - drawProgress) * SEG_W; + + const settledCount = buf.length - SUB; + const anchor = buf[settledCount - 1]; + const animFrac = drawProgress * (SUB + 1); + const animFloor = Math.floor(animFrac); + const animRemainder = animFrac - animFloor; + + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = 1; + ctx.lineJoin = 'round'; + + for (let i = 0; i < settledCount; i++) { + const p = buf[i]; + i === 0 ? ctx.moveTo(p.x + slide, p.y) : ctx.lineTo(p.x + slide, p.y); + } + + const animPoints = [anchor, ...buf.slice(settledCount)]; + + for (let i = 1; i <= animFloor && i < animPoints.length; i++) { + ctx.lineTo(animPoints[i].x + slide, animPoints[i].y); + } + + if (animFloor < animPoints.length - 1 && animRemainder > 0) { + const curr = animPoints[animFloor]; + const next = animPoints[animFloor + 1]; + ctx.lineTo( + curr.x + (next.x - curr.x) * animRemainder + slide, + curr.y + (next.y - curr.y) * animRemainder, + ); + } + + ctx.stroke(); } function draw(timestamp: number) { @@ -58,64 +111,38 @@ function draw(timestamp: number) { ctx.clearRect(0, 0, W, H); - // Center line — full width - ctx.beginPath(); - ctx.strokeStyle = 'rgba(255,255,255,0.1)'; - ctx.lineWidth = 1; - ctx.moveTo(0, H / 2); - ctx.lineTo(W, H / 2); - ctx.stroke(); + AXES.forEach((_, i) => { + const mid = i * BAND + BAND / 2; - if (pointBuffer.length < 2) { rafId = requestAnimationFrame(draw); return; } + // Axis label — top-left of each band + ctx.font = '16px monospace'; + ctx.fillStyle = AXES[i].color; + ctx.globalAlpha = 0.6; + ctx.fillText(AXES[i].label, W - 20, i * BAND + 14); + ctx.globalAlpha = 1; - // Shift the whole path left by PAD_SEGS segments so tip never hits the edge - const xOffset = 3 * SEG_W; + // Center line per band — full width + ctx.beginPath(); + ctx.strokeStyle = 'rgba(255,255,255,0.1)'; + ctx.lineWidth = 1; + ctx.moveTo(0, mid); + ctx.lineTo(W, mid); + ctx.stroke(); - const settledCount = pointBuffer.length - SUB; - const anchor = pointBuffer[settledCount - 1]; - const totalSteps = SUB + 1; - const animFrac = drawProgress * totalSteps; - const animFloor = Math.floor(animFrac); - const animRemainder = animFrac - animFloor; + drawTrace(ctx, traces[i], AXES[i].color); + }); - ctx.beginPath(); - ctx.strokeStyle = '#4d9fff'; - ctx.lineWidth = 1.5; - ctx.lineJoin = 'round'; - - for (let i = 0; i < settledCount; i++) { - const p = pointBuffer[i]; - const x = p.x - xOffset; - i === 0 ? ctx.moveTo(x, p.y) : ctx.lineTo(x, p.y); - } - - const animPoints = [ - anchor, - ...Array.from({ length: SUB }, (_, i) => pointBuffer[settledCount + i]), - ]; - - for (let i = 1; i <= animFloor && i < animPoints.length; i++) { - ctx.lineTo(animPoints[i].x - xOffset, animPoints[i].y); - } - - if (animFloor < animPoints.length - 1 && animRemainder > 0) { - const curr = animPoints[animFloor]; - const next = animPoints[animFloor + 1]; - ctx.lineTo( - curr.x + (next.x - curr.x) * animRemainder - xOffset, - curr.y + (next.y - curr.y) * animRemainder, - ); - } - - ctx.stroke(); rafId = requestAnimationFrame(draw); } async function poll() { - d.value = await api.current('seismic_magnitude'); - history.value.push(d.value.seismic_magnitude ?? 0); - if (history.value.length > MAX_PTS) history.value.shift(); - buildBuffer(history.value); + d.value = await api.current('seismic_magnitude,seismic_x,seismic_y,seismic_z'); + AXES.forEach((axis, i) => { + const t = traces[i]; + t.history.push(d.value[axis.key] ?? 0); + if (t.history.length > MAX_PTS) t.history.shift(); + buildBuffer(t, i); + }); drawProgress = 0; } @@ -133,14 +160,14 @@ onUnmounted(() => {