- Complete Next.js 14 app with TypeScript and Tailwind CSS - ESP32 serial communication via API routes - Real-time UWB positioning visualization - Interactive 2D warehouse mapping with Canvas - Device connection interface with auto-detection - AT command parsing for UWBHelper library integration - Clean project structure with comprehensive documentation
112 lines
No EOL
3.3 KiB
TypeScript
112 lines
No EOL
3.3 KiB
TypeScript
import { NextApiRequest, NextApiResponse } from 'next'
|
|
import { serialConnection, parser } from '../serial/connect'
|
|
import { RangeResult, DeviceData } from '@/lib/uwb-types'
|
|
|
|
// Data storage for real-time updates
|
|
let latestRangeData: RangeResult | null = null
|
|
let latestDeviceData: DeviceData[] = []
|
|
|
|
// Parse UWB range data (from ESP32 UWBHelper parseRangeData format)
|
|
function parseUWBRangeData(data: string): RangeResult | DeviceData[] | null {
|
|
if (!data.startsWith("AT+RANGE=")) {
|
|
return null
|
|
}
|
|
|
|
// Parse tid (Tag ID)
|
|
const tidMatch = data.match(/tid:(\d+)/)
|
|
if (!tidMatch) return null
|
|
|
|
const tagId = parseInt(tidMatch[1])
|
|
|
|
// Parse detailed range data format
|
|
const maskMatch = data.match(/mask:(\d+)/)
|
|
const seqMatch = data.match(/seq:(\d+)/)
|
|
const timerMatch = data.match(/timer:(\d+)/)
|
|
const timerSysMatch = data.match(/timerSys:(\d+)/)
|
|
|
|
// Parse range data
|
|
const rangeMatch = data.match(/range:\(([^)]+)\)/)
|
|
const rssiMatch = data.match(/rssi:\(([^)]+)\)/)
|
|
const ancidMatch = data.match(/ancid:\(([^)]+)\)/)
|
|
|
|
if (rangeMatch && rssiMatch) {
|
|
const ranges = rangeMatch[1].split(',').map(r => parseFloat(r) / 100) // Convert cm to meters
|
|
const rssi = rssiMatch[1].split(',').map(r => parseFloat(r))
|
|
const anchorIds = ancidMatch ? ancidMatch[1].split(',').map(a => parseInt(a)) : Array(8).fill(0)
|
|
|
|
const result: RangeResult = {
|
|
tagId,
|
|
mask: maskMatch ? parseInt(maskMatch[1]) : 0,
|
|
sequence: seqMatch ? parseInt(seqMatch[1]) : 0,
|
|
ranges: ranges.slice(0, 8).concat(Array(8 - ranges.length).fill(0)),
|
|
rssi: rssi.slice(0, 8).concat(Array(8 - rssi.length).fill(0)),
|
|
anchorIds: anchorIds.slice(0, 8).concat(Array(8 - anchorIds.length).fill(0)),
|
|
timer: timerMatch ? parseInt(timerMatch[1]) : 0,
|
|
timerSys: timerSysMatch ? parseInt(timerSysMatch[1]) : 0
|
|
}
|
|
|
|
latestRangeData = result
|
|
return result
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// Set up data listener when connection is established
|
|
if (parser) {
|
|
parser.on('data', (data: string) => {
|
|
const parsed = parseUWBRangeData(data.trim())
|
|
if (parsed) {
|
|
console.log('UWB Data received:', parsed)
|
|
}
|
|
})
|
|
}
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
if (req.method === 'GET') {
|
|
if (!serialConnection?.isOpen) {
|
|
return res.status(400).json({
|
|
message: 'Serial connection not established'
|
|
})
|
|
}
|
|
|
|
// Return latest data
|
|
res.status(200).json({
|
|
rangeData: latestRangeData,
|
|
deviceData: latestDeviceData,
|
|
timestamp: Date.now()
|
|
})
|
|
}
|
|
|
|
else if (req.method === 'POST') {
|
|
// Send command to UWB device
|
|
const { command } = req.body
|
|
|
|
if (!serialConnection?.isOpen) {
|
|
return res.status(400).json({
|
|
message: 'Serial connection not established'
|
|
})
|
|
}
|
|
|
|
if (!command) {
|
|
return res.status(400).json({ message: 'Command is required' })
|
|
}
|
|
|
|
try {
|
|
serialConnection.write(command + '\r\n')
|
|
res.status(200).json({ message: 'Command sent', command })
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
message: 'Failed to send command',
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
})
|
|
}
|
|
}
|
|
|
|
else {
|
|
res.status(405).json({ message: 'Method not allowed' })
|
|
}
|
|
} |