api-amr/web_worker.ts
2025-06-04 19:15:02 +08:00

289 lines
8.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Hono } from '@hono/hono';
import { serveStatic } from '@hono/hono/serve-static';
import { html } from '@hono/hono/html';
import { Context } from '@hono/hono';
// Create Hono app
const app = new Hono();
// Store AGV positions
const agvPositions: Map<string, { x: number; y: number; theta: number }> = new Map();
let server: { shutdown: () => Promise<void> } | null = null;
let isRunning = false;
// Serve static files
app.use('/*', serveStatic({
root: './',
getContent: async (path, c) => {
try {
const file = await Deno.readFile(path);
return file;
} catch {
return null;
}
}
}));
// Main page with canvas
app.get('/', (c: Context) => {
return c.html(html`
<!DOCTYPE html>
<html>
<head>
<title>AGV Position Monitor</title>
<style>
body {
margin: 0;
padding: 20px;
background-color: #f0f0f0;
font-family: Arial, sans-serif;
}
canvas {
background-color: white;
border: 1px solid #ccc;
margin: 20px 0;
}
.controls {
margin: 20px 0;
}
button {
padding: 8px 16px;
margin-right: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>AGV Position Monitor</h1>
<div class="controls">
<button onclick="startUpdates()">Start Updates</button>
<button onclick="stopUpdates()">Stop Updates</button>
</div>
<canvas id="agvCanvas" width="800" height="800"></canvas>
<script>
const canvas = document.getElementById('agvCanvas');
const ctx = canvas.getContext('2d');
let updateInterval;
// Scale factor for visualization
const SCALE = 2; // Scale adjusted for 400x400 meter area
const AGV_SIZE = 8; // AGV representation size
const GRID_SIZE = 400; // 400x400 meter grid
const COLORS = ['#4CAF50', '#2196F3', '#FFC107', '#9C27B0', '#FF5722', '#607D8B'];
function getColor(id) {
// Simple hash function to get consistent color for each AGV
let hash = 0;
for (let i = 0; i < id.length; i++) {
hash = id.charCodeAt(i) + ((hash << 5) - hash);
}
return COLORS[Math.abs(hash) % COLORS.length];
}
function drawAGV(x, y, theta, id, color) {
ctx.save();
// Center the canvas and flip Y axis to match coordinate system
ctx.translate(canvas.width/2 + x * SCALE, canvas.height/2 - y * SCALE);
ctx.rotate(-theta); // Negative theta to match coordinate system
// Draw AGV body
ctx.fillStyle = color;
ctx.fillRect(-AGV_SIZE/2, -AGV_SIZE/2, AGV_SIZE, AGV_SIZE);
// Draw direction indicator
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(AGV_SIZE/2, 0);
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
// Draw AGV name
ctx.save();
ctx.translate(canvas.width/2 + x * SCALE, canvas.height/2 - y * SCALE - AGV_SIZE);
ctx.fillStyle = color;
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.fillText(id, 0, 0);
ctx.restore();
}
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw grid
ctx.strokeStyle = '#ddd';
ctx.lineWidth = 1;
// Grid interval for labels (show every 20 units)
const LABEL_INTERVAL = 20;
// Vertical lines
for (let x = -GRID_SIZE/2; x <= GRID_SIZE/2; x++) {
const drawX = canvas.width/2 + x * SCALE;
ctx.beginPath();
ctx.moveTo(drawX, 0);
ctx.lineTo(drawX, canvas.height);
ctx.stroke();
// Add X axis labels (every LABEL_INTERVAL units)
if (x % LABEL_INTERVAL === 0) {
ctx.fillStyle = '#666';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.fillText(x.toString(), drawX, canvas.height/2 + 20);
}
}
// Horizontal lines
for (let y = -GRID_SIZE/2; y <= GRID_SIZE/2; y++) {
const drawY = canvas.height/2 + y * SCALE;
ctx.beginPath();
ctx.moveTo(0, drawY);
ctx.lineTo(canvas.width, drawY);
ctx.stroke();
// Add Y axis labels (every LABEL_INTERVAL units)
if (y % LABEL_INTERVAL === 0) {
ctx.fillStyle = '#666';
ctx.font = '12px Arial';
ctx.textAlign = 'right';
ctx.fillText((-y).toString(), canvas.width/2 - 10, drawY + 4);
}
}
// Draw axis lines
ctx.strokeStyle = '#999';
ctx.lineWidth = 2;
// X axis
ctx.beginPath();
ctx.moveTo(0, canvas.height/2);
ctx.lineTo(canvas.width, canvas.height/2);
ctx.stroke();
// Y axis
ctx.beginPath();
ctx.moveTo(canvas.width/2, 0);
ctx.lineTo(canvas.width/2, canvas.height);
ctx.stroke();
// Add axes labels
ctx.fillStyle = '#444';
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'center';
ctx.fillText('X', canvas.width - 15, canvas.height/2 - 10);
ctx.fillText('Y', canvas.width/2 + 15, 15);
}
function updatePositions() {
fetch('/positions')
.then(response => response.json())
.then(positions => {
clearCanvas();
positions.forEach(pos => {
const color = getColor(pos.id);
drawAGV(
pos.position.x,
pos.position.y,
pos.position.theta,
pos.id,
color
);
});
})
.catch(error => console.error('Error fetching positions:', error));
}
function startUpdates() {
if (!updateInterval) {
updateInterval = setInterval(updatePositions, 1000);
}
}
function stopUpdates() {
if (updateInterval) {
clearInterval(updateInterval);
updateInterval = null;
}
}
// Start updates automatically
startUpdates();
</script>
</body>
</html>
`);
});
// API endpoint to get current positions
app.get('/positions', (c: Context) => {
// Convert Map to array of objects with id and position
const positions = Array.from(agvPositions.entries()).map(([id, pos]) => ({
id,
position: pos
}));
return c.json(positions);
});
// Handle messages from main thread
self.onmessage = (event) => {
const message = event.data;
if (message.type === 'positionUpdate') {
const { agvId, position } = message.data;
// console.log("agvId", agvId, "position", position);
agvPositions.set(`${agvId.manufacturer}/${agvId.serialNumber}`, position);
} else if (message.type === 'shutdown') {
stopServer();
}
};
// Start the server
export async function startServer(port: number = 3001) {
if (isRunning) {
console.log("Web服务器已在运行中");
return false;
}
try {
server = Deno.serve({ port }, app.fetch);
isRunning = true;
console.log(`Web服务器已启动监听端口 ${port}`);
return true;
} catch (error) {
console.error(`服务器启动失败: ${error}`);
return false;
}
}
// Stop the server
export async function stopServer() {
if (!isRunning || !server) {
console.log("Web服务器未在运行");
return false;
}
try {
await server.shutdown();
isRunning = false;
server = null;
console.log('Web服务器已关闭');
return true;
} catch (error) {
console.error(`服务器关闭失败: ${error}`);
return false;
}
}
// Start the server when the worker is initialized
startServer();