api-amr/web_worker.ts

289 lines
8.6 KiB
TypeScript
Raw Permalink Normal View History

2025-06-04 19:15:02 +08:00
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();