2 Commits

Author SHA1 Message Date
Rune Harlyk 04fecf33f8 🪴 Adds webserial lidar support 2024-08-04 13:53:53 +02:00
Rune Harlyk acf4efde4c 🗺️ Adds lidar visualization 2024-08-04 00:02:17 +02:00
6 changed files with 223 additions and 2 deletions
+77
View File
@@ -0,0 +1,77 @@
<script lang="ts">
import { onMount } from "svelte";
import { lidar, type LidarPoint } from '$lib/stores/lidar'
function getIntersection(angle:number, size:number):number {
const sinAngle = Math.sin(angle);
const cosAngle = Math.cos(angle);
let x, y;
if (Math.abs(cosAngle) > Math.abs(sinAngle)) {
x = size * Math.sign(cosAngle);
y = x * sinAngle / cosAngle;
} else {
y = size * Math.sign(sinAngle);
x = y * cosAngle / sinAngle;
}
return Math.sqrt(x**2 + y**2);
}
let canvas:HTMLCanvasElement
let ctx
const DEG2RAD = 0.017453292519943;
onMount(() => {
ctx = canvas.getContext("2d")
resize()
lidar.subscribe(lidar => {
draw(lidar.points)
})
})
const draw = (points:LidarPoint[]) => {
if(!points) return
const centerX = canvas.width / 2
const centerY = canvas.height / 2
const scale = 0.01//Math.max(centerX, centerY) / Math.max(...points.map((point) => point.distance))
if (!ctx) return
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < points.length; i++){
const angle = points[i].angle
const distance = points[i].distance
const quality = points[i].quality
const endX = centerX + (distance * scale) * Math.cos(angle * DEG2RAD);
const endY = centerY - (distance * scale) * Math.sin(angle * DEG2RAD);
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(endX, endY);
ctx.strokeStyle = "grey"
ctx.stroke();
ctx.beginPath();
ctx.arc(endX, endY, 3, 0, Math.PI * 2);
ctx.fillStyle = "#1bfc06"
ctx.fill();
}
}
const resize = () => {
const parentElement = canvas.parentElement;
if (parentElement) {
canvas.width = parentElement.clientWidth
canvas.height = parentElement.clientHeight
}
}
</script>
<svelte:window on:resize={resize}></svelte:window>
<canvas bind:this={canvas} class="w-full h-full"></canvas>
+29
View File
@@ -0,0 +1,29 @@
import { writable } from 'svelte/store';
export type LidarPoint = {
distance: number;
angle: number;
quality: number;
};
let lidar_data = {
points: <LidarPoint[]>[]
};
const maxLidarData = 600;
function createLidar() {
const { subscribe, update } = writable(lidar_data);
return {
subscribe,
addData: (lidarPoint: LidarPoint) => {
update((lidar_data) => ({
...lidar_data,
points: [...lidar_data.points, lidarPoint].slice(-maxLidarData)
}));
}
};
}
export const lidar = createLidar();
+8 -2
View File
@@ -1,9 +1,15 @@
<script lang="ts"> <script lang="ts">
import Visualization from "$lib/components/Visualization.svelte"; import Visualization from "$lib/components/Visualization.svelte";
import Lidar from "$lib/components/Lidar.svelte";
</script> </script>
<div class="grow flex"> <div class="grow flex">
<div class="absolute h-screen w-full top-0"> <div class="absolute h-screen w-full top-0 flex">
<Visualization debug /> <div class="flex-1 overflow-hidden">
<Visualization debug />
</div>
<div class="flex-1">
<Lidar />
</div>
</div> </div>
</div> </div>
+7
View File
@@ -8,6 +8,7 @@
import Devices from '~icons/mdi/devices' import Devices from '~icons/mdi/devices'
import Camera from '~icons/mdi/camera-outline'; import Camera from '~icons/mdi/camera-outline';
import Rotate3d from '~icons/mdi/rotate-3d'; import Rotate3d from '~icons/mdi/rotate-3d';
import MdiLandslideOutline from '~icons/mdi/landslide-outline';
import MotorOutline from '~icons/mdi/motor-outline'; import MotorOutline from '~icons/mdi/motor-outline';
import Health from '~icons/mdi/stethoscope'; import Health from '~icons/mdi/stethoscope';
import Folder from '~icons/mdi/folder-outline'; import Folder from '~icons/mdi/folder-outline';
@@ -83,6 +84,12 @@
icon: Rotate3d, icon: Rotate3d,
href: '/peripherals/imu', href: '/peripherals/imu',
feature: $page.data.features.imu || $page.data.features.mag || $page.data.features.bmp, feature: $page.data.features.imu || $page.data.features.mag || $page.data.features.bmp,
},
{
title: 'Lidar',
icon: MdiLandslideOutline,
href: '/peripherals/lidar',
feature: true//$page.data.features.lidar,
} }
] ]
}, },
@@ -0,0 +1,7 @@
<script lang="ts">
import Lidar from './lidar.svelte';
</script>
<div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8">
<Lidar />
</div>
@@ -0,0 +1,95 @@
<script lang="ts">
import Lidar from "$lib/components/Lidar.svelte";
import SettingsCard from "$lib/components/SettingsCard.svelte";
import { lidar } from "$lib/stores/lidar";
import { onMount } from "svelte";
import { writable } from "svelte/store";
import { distance } from "three/examples/jsm/nodes/Nodes.js";
let port;
let reader;
let inputDone;
let inputStream;
let isConnected = false;
let buffer = '';
let lastLine = ""
onMount(() => {
navigator.serial.addEventListener("connect", (e) => {
console.log("Connected");
});
navigator.serial.addEventListener("disconnect", (e) => {
console.log("Disconnected");
});
navigator.serial.getPorts().then((ports) => {
// Initialize the list of available ports with `ports` on page load.
});
})
const connect = async () => {
try {
port = await navigator.serial.requestPort();
await port.open({ baudRate: 115200 });
const decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable.pipeThrough(new TransformStream(new LineBreakTransformer()));
reader = inputStream.getReader();
readLoop();
} catch (err) {
console.error('Failed to open serial port:', err);
}
}
async function readLoop() {
while (true) {
const { value, done } = await reader.read();
if (done) {
console.log('[readLoop] DONE', done);
reader.releaseLock();
break;
}
if (value.split(",").length !== 3) continue
const [distance, angle, quality] = value.split(",").map((val:string) => parseFloat(val))
const lidarData = { distance, angle, quality }
if (distance <1000 || distance > 40000 || quality < 40) continue
lidar.addData(lidarData)
}
}
class LineBreakTransformer {
container: string;
constructor() {
this.container = '';
}
transform(chunk: any, controller: { enqueue: (arg0: any) => any; }) {
let re = /\r\n|\n|\r/gm;
this.container += chunk;
const lines = this.container.split(re);
this.container = lines.pop() || "";
lines.forEach(line => controller.enqueue(line));
}
flush(controller: { enqueue: (arg0: string) => void; }) {
controller.enqueue(this.container);
}
}
</script>
<SettingsCard collapsible={false}>
<!-- <MdiConnection slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" /> -->
<span slot="title">Lidar</span>
<div>
<button on:click={connect} class="btn">Connect</button>
</div>
</SettingsCard>
<div class="h-96 w-96">
<div class="w-full h-full">
<Lidar />
</div>
</div>