Fixed seismic sensors

This commit is contained in:
2026-07-03 13:37:09 -04:00
parent bbcc62af7d
commit 5bd2e786c2
2 changed files with 101 additions and 72 deletions

View File

@@ -5,11 +5,18 @@ import { api } from '../services/api';
import MetricRow from './MetricRow.vue'; import MetricRow from './MetricRow.vue';
const d = ref<any>(null); const d = ref<any>(null);
const history = ref<number[]>([]);
const canvas = ref<HTMLCanvasElement | null>(null); const canvas = ref<HTMLCanvasElement | null>(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 SEG_W = W / (MAX_PTS - 1);
const SUB = 4; const SUB = 4;
const RIGHT_PAD = 2 * SEG_W;
let interval: ReturnType<typeof setInterval>; let interval: ReturnType<typeof setInterval>;
let rafId: number; let rafId: number;
@@ -17,35 +24,81 @@ let drawProgress = 0;
let lastTimestamp = 0; let lastTimestamp = 0;
const ANIM_DURATION = 1200; const ANIM_DURATION = 1200;
let pointBuffer: { x: number; y: number }[] = []; type Pt = { x: number; y: number };
let prevMax = 1.0; 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; 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); const newMax = Math.max(...pts, 1.0);
prevMax = prevMax + (newMax - prevMax) * 0.15; t.prevMax = t.prevMax + (newMax - t.prevMax) * 0.15; // eased amplitude scale
const buf: { x: number; y: number }[] = []; const tipX = W - RIGHT_PAD;
const buf: Pt[] = [];
pts.forEach((v, i) => { pts.forEach((v, i) => {
const prev = pts[i - 1] ?? v; const prev = pts[i - 1] ?? v;
const max = prevMax; const prevNorm = prev === 0 ? 0 : (prev / t.prevMax) * amp;
const prevNorm = prev === 0 ? 0 : (prev / max) * amp; const norm = v === 0 ? 0 : (v / t.prevMax) * amp;
const norm = v === 0 ? 0 : (v / max) * amp; const baseX = tipX - (pts.length - i) * SEG_W;
const baseX = i * SEG_W;
const s = SEG_W / SUB; const s = SEG_W / SUB;
// one oscillation per value, amplitude fading prevNorm → norm
const a0 = prevNorm * 0.75 + norm * 0.25; const a0 = prevNorm * 0.75 + norm * 0.25;
const a1 = prevNorm * 0.25 + norm * 0.75; 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 - a0 }); // up
buf.push({ x: baseX + s, y: mid - a1 }); // up peak buf.push({ x: baseX + s * 2, y: mid - a1 }); // up peak
buf.push({ x: baseX + s * 2, y: mid + a2 }); // down buf.push({ x: baseX + s * 3, y: mid + norm }); // down
buf.push({ x: baseX + s * 3, y: mid }); // back to exact center ✓ 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) { function draw(timestamp: number) {
@@ -58,64 +111,38 @@ function draw(timestamp: number) {
ctx.clearRect(0, 0, W, H); ctx.clearRect(0, 0, W, H);
// Center line — full width AXES.forEach((_, i) => {
ctx.beginPath(); const mid = i * BAND + BAND / 2;
ctx.strokeStyle = 'rgba(255,255,255,0.1)';
ctx.lineWidth = 1;
ctx.moveTo(0, H / 2);
ctx.lineTo(W, H / 2);
ctx.stroke();
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 // Center line per band — full width
const xOffset = 3 * SEG_W; 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; drawTrace(ctx, traces[i], AXES[i].color);
const anchor = pointBuffer[settledCount - 1]; });
const totalSteps = SUB + 1;
const animFrac = drawProgress * totalSteps;
const animFloor = Math.floor(animFrac);
const animRemainder = animFrac - animFloor;
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); rafId = requestAnimationFrame(draw);
} }
async function poll() { async function poll() {
d.value = await api.current('seismic_magnitude'); d.value = await api.current('seismic_magnitude,seismic_x,seismic_y,seismic_z');
history.value.push(d.value.seismic_magnitude ?? 0); AXES.forEach((axis, i) => {
if (history.value.length > MAX_PTS) history.value.shift(); const t = traces[i];
buildBuffer(history.value); t.history.push(d.value[axis.key] ?? 0);
if (t.history.length > MAX_PTS) t.history.shift();
buildBuffer(t, i);
});
drawProgress = 0; drawProgress = 0;
} }
@@ -133,14 +160,14 @@ onUnmounted(() => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
canvas { width: 100%; height: 60px; } canvas { width: 100%; height: 180px; }
</style> </style>
<template> <template>
<div class="card"> <div class="card">
<div class="card-title">🫨 Seismic</div> <div class="card-title">🫨 Seismic</div>
<div v-if="!d" class="p-2"> <div v-if="!d" class="p-2">
<div class="w-100 pos-rel br-2 overflow-hidden" style="height: 80px"> <div class="w-100 pos-rel br-2 overflow-hidden" style="height: 200px">
<Loading /> <Loading />
</div> </div>
</div> </div>

View File

@@ -185,8 +185,8 @@ def mpu_loop():
o = _mpu_offsets o = _mpu_offsets
ax = s16((d[0] << 8) | d[1]) / 16384.0 - o[0] ax = s16((d[0] << 8) | d[1]) / 16384.0 - o[0]
ay = s16((d[2] << 8) | d[3]) / 16384.0 - o[1] ay = s16((d[2] << 8) | d[3]) / 16384.0 - o[1]
az = s16((d[4] << 8) | d[5]) / 16384.0 - o[2] az = s16((d[4] << 8) | d[5]) / 16384.0 - o[2] - 1.0 # remove gravity
mag = math.sqrt(ax**2 + ay**2 + (az - 1.0)**2) mag = math.sqrt(ax**2 + ay**2 + az**2)
with _mpu_lock: with _mpu_lock:
if _mpu_peak is None or mag > _mpu_peak['seismic_magnitude']: if _mpu_peak is None or mag > _mpu_peak['seismic_magnitude']:
_mpu_peak = { _mpu_peak = {
@@ -203,6 +203,8 @@ def flush_mpu(c):
data, _mpu_peak = _mpu_peak, None data, _mpu_peak = _mpu_peak, None
if data and data['seismic_magnitude'] > c['SEISMIC_NOISE_FLOOR']: if data and data['seismic_magnitude'] > c['SEISMIC_NOISE_FLOOR']:
write(c, data) write(c, data)
else:
write(c, {'seismic_x': 0.0, 'seismic_y': 0.0, 'seismic_z': 0.0, 'seismic_magnitude': 0.0})
# ── QMC5883L ────────────────────────────────────────────────────────────────── # ── QMC5883L ──────────────────────────────────────────────────────────────────