carrot/selfdrive/app/commawebview.py

858 lines
36 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
import json
import socket
import threading
import time
import traceback
from datetime import datetime
import argparse
from flask import Flask, render_template_string, jsonify, render_template
class CommaWebListener:
def __init__(self, port=8088):
"""初始化接收器"""
self.port = port
self.data = {}
self.last_update = 0
self.running = True
self.device_ip = None
def start_listening(self):
"""启动UDP监听"""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind(('0.0.0.0', self.port))
print(f"正在监听端口 {self.port} 的广播数据...")
while self.running:
try:
data, addr = sock.recvfrom(4096)
self.device_ip = addr[0]
try:
self.data = json.loads(data.decode('utf-8'))
self.last_update = time.time()
except json.JSONDecodeError:
print(f"接收到无效的JSON数据: {data[:100]}...")
except Exception as e:
print(f"接收数据时出错: {e}")
except Exception as e:
print(f"无法绑定到端口 {self.port}: {e}")
finally:
sock.close()
# HTML模板
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CommaAssist 数据监视器</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.status {
text-align: center;
padding: 10px;
margin-bottom: 20px;
border-radius: 5px;
}
.waiting {
background-color: #fff3cd;
color: #856404;
}
.connected {
background-color: #d4edda;
color: #155724;
}
.expired {
background-color: #f8d7da;
color: #721c24;
}
.card {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.card-header {
font-weight: bold;
font-size: 18px;
border-bottom: 1px solid #eee;
padding: 15px;
background-color: #f8f9fa;
}
.card-body {
padding: 15px;
}
.row {
margin-bottom: 10px;
}
.col-6 strong {
color: #666;
}
.badge {
font-size: 100%;
}
.bg-primary {
background-color: #007bff;
}
.bg-success {
background-color: #28a745;
}
.bg-danger {
background-color: #dc3545;
}
.bg-warning {
background-color: #ffc107;
color: #212529;
}
.bg-info {
background-color: #17a2b8;
}
.nav-tabs {
margin-bottom: 20px;
}
.tab-content {
padding-top: 20px;
}
.map-container {
height: 300px;
width: 100%;
background-color: #eee;
margin-top: 10px;
border-radius: 5px;
}
.controls {
margin-bottom: 20px;
}
.auto-refresh {
display: inline-block;
margin-right: 15px;
}
pre {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
white-space: pre-wrap;
word-break: break-all;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>CommaAssist 数据监视器</h1>
</div>
<div id="status-container" class="status waiting">
等待来自comma3的数据...
</div>
<div class="controls">
<label class="auto-refresh">
<input type="checkbox" id="auto-refresh" checked> 自动刷新 (1)
</label>
<button class="btn btn-primary float-end" onclick="fetchData()">刷新数据</button>
<div style="clear: both;"></div>
</div>
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="car-tab" data-bs-toggle="tab" data-bs-target="#car-tab-pane" type="button" role="tab">
车辆信息
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="device-tab" data-bs-toggle="tab" data-bs-target="#device-tab-pane" type="button" role="tab">
设备信息
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="location-tab" data-bs-toggle="tab" data-bs-target="#location-tab-pane" type="button" role="tab">
位置信息
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="json-tab" data-bs-toggle="tab" data-bs-target="#json-tab-pane" type="button" role="tab">
原始数据
</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<!-- 车辆信息标签页 -->
<div class="tab-pane fade show active" id="car-tab-pane" role="tabpanel" aria-labelledby="car-tab" tabindex="0">
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">基本车辆信息</div>
<div class="card-body">
<div id="vehicle-status-container">
<div class="alert alert-warning">等待车辆数据...</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">方向盘与控制系统</div>
<div class="card-body">
<div id="steering-system-container">
<div class="alert alert-warning">等待车辆数据...</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">详细车辆信息</div>
<div class="card-body">
<div id="detailed-vehicle-info-container">
<div class="alert alert-warning">等待车辆数据...</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">踏板与制动系统</div>
<div class="card-body">
<div id="pedal-status-container">
<div class="alert alert-warning">等待车辆数据...</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">车门与信号灯</div>
<div class="card-body">
<div id="door-lights-container">
<div class="alert alert-warning">等待车辆数据...</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">巡航控制</div>
<div class="card-body">
<div id="cruise-info-container">
<div class="alert alert-warning">等待车辆数据...</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 设备信息标签页 -->
<div class="tab-pane fade" id="device-tab-pane" role="tabpanel" aria-labelledby="device-tab" tabindex="0">
<div class="card">
<div class="card-header">设备状态</div>
<div class="card-body">
<div id="device-info-container">
<div class="alert alert-warning">等待设备数据...</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">系统资源</div>
<div class="card-body">
<div id="system-resources-container">
<div class="alert alert-warning">等待设备数据...</div>
</div>
</div>
</div>
</div>
<!-- 位置信息标签页 -->
<div class="tab-pane fade" id="location-tab-pane" role="tabpanel" aria-labelledby="location-tab" tabindex="0">
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">GPS位置</div>
<div class="card-body">
<div id="gps-info-container">
<div class="alert alert-warning">等待GPS数据...</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">导航信息</div>
<div class="card-body">
<div id="navigation-container">
<div class="alert alert-warning">等待导航数据...</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">地图</div>
<div class="card-body">
<div id="map" class="map-container"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 原始数据标签页 -->
<div class="tab-pane fade" id="json-tab-pane" role="tabpanel" aria-labelledby="json-tab" tabindex="0">
<div class="card">
<div class="card-header">原始JSON数据</div>
<div class="card-body">
<pre id="raw-data">等待数据...</pre>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
let map, marker;
let lastValidLatLng = null;
let activeTab = 'car'; // 默认显示车辆信息标签
// 根据车辆状态自动切换标签
function autoSwitchTabs(isCarActive) {
// 如果车辆启动切换到车辆标签否则切换到设备标签
if (isCarActive && activeTab !== 'car') {
document.getElementById('car-tab').click();
activeTab = 'car';
} else if (!isCarActive && activeTab === 'car') {
document.getElementById('device-tab').click();
activeTab = 'device';
}
}
function initMap() {
if (typeof google !== 'undefined') {
const defaultPos = {lat: 39.9042, lng: 116.4074}; // 默认位置北京
map = new google.maps.Map(document.getElementById('map'), {
zoom: 16,
center: defaultPos,
mapTypeId: 'roadmap'
});
marker = new google.maps.Marker({
position: defaultPos,
map: map,
title: 'Comma3位置'
});
}
}
function updateMap(lat, lng, bearing) {
if (typeof google !== 'undefined' && map && marker) {
const latLng = new google.maps.LatLng(lat, lng);
// 更新标记位置
marker.setPosition(latLng);
// 根据方向旋转标记
if (bearing !== undefined) {
// 如果我们有自定义的带方向的标记图标可以在这里设置
}
// 平滑移动地图中心
map.panTo(latLng);
lastValidLatLng = {lat, lng};
}
}
function formatDataRow(label, value, badgeClass = null) {
let valueHtml = value;
if (badgeClass) {
valueHtml = `<span class="badge ${badgeClass}">${value}</span>`;
}
return `
<div class="row">
<div class="col-6">
<strong>${label}</strong>
</div>
<div class="col-6">
${valueHtml}
</div>
</div>
`;
}
function updateVehicleStatus(car) {
const speed = car.speed || 0;
const isCarMoving = speed > 1.0;
let html = '';
html += formatDataRow('运行状态', isCarMoving ? '行驶中' : '静止', isCarMoving ? 'bg-success' : 'bg-secondary');
html += formatDataRow('当前速度', `${speed.toFixed(1)} km/h`, 'bg-primary');
html += formatDataRow('档位', car.gear_shifter || 'Unknown');
document.getElementById('vehicle-status-container').innerHTML = html;
return isCarMoving;
}
function updateSteeringSystem(car) {
let html = '';
html += formatDataRow('方向盘角度', `${(car.steering_angle || 0).toFixed(1)}°`);
html += formatDataRow('转向力矩', `${(car.steering_torque || 0).toFixed(1)} Nm`);
let blinkerStatus = '';
if (car.left_blinker && car.right_blinker) blinkerStatus = '双闪';
else if (car.left_blinker) blinkerStatus = '左转';
else if (car.right_blinker) blinkerStatus = '右转';
else blinkerStatus = '关闭';
html += formatDataRow('转向灯', blinkerStatus);
document.getElementById('steering-system-container').innerHTML = html;
}
function updatePedalStatus(car) {
let html = '';
const brakeStatus = car.brake_pressed ? '已踩下' : '释放';
const gasStatus = car.gas_pressed ? '已踩下' : '释放';
html += formatDataRow('制动踏板', brakeStatus, car.brake_pressed ? 'bg-danger' : 'bg-secondary');
html += formatDataRow('油门踏板', gasStatus, car.gas_pressed ? 'bg-success' : 'bg-secondary');
document.getElementById('pedal-status-container').innerHTML = html;
}
function updateDoorLights(car) {
let html = '';
const doorStatus = car.door_open ? '打开' : '关闭';
html += formatDataRow('车门状态', doorStatus, car.door_open ? 'bg-danger' : 'bg-success');
html += formatDataRow('左转向灯', car.left_blinker ? '开启' : '关闭', car.left_blinker ? 'bg-warning' : 'bg-secondary');
html += formatDataRow('右转向灯', car.right_blinker ? '开启' : '关闭', car.right_blinker ? 'bg-warning' : 'bg-secondary');
document.getElementById('door-lights-container').innerHTML = html;
}
function updateCruiseInfo(car) {
let html = '';
html += formatDataRow('巡航速度', `${(car.cruise_speed || 0).toFixed(1)} km/h`, 'bg-info');
document.getElementById('cruise-info-container').innerHTML = html;
}
function updateDeviceInfo(device) {
let html = '';
html += formatDataRow('设备IP', device.ip || 'Unknown');
const battery = device.battery || {};
const batPercent = battery.percent || 0;
let batClass = 'bg-danger';
if (batPercent > 50) batClass = 'bg-success';
else if (batPercent > 20) batClass = 'bg-warning';
html += formatDataRow('电池电量', `${batPercent}%`, batClass);
html += formatDataRow('电池电压', `${(battery.voltage || 0).toFixed(2)} V`);
html += formatDataRow('电池电流', `${(battery.status || 0).toFixed(2)} A`);
document.getElementById('device-info-container').innerHTML = html;
}
function updateSystemResources(device) {
let html = '';
let memClass = 'bg-success';
if (device.mem_usage > 80) memClass = 'bg-danger';
else if (device.mem_usage > 60) memClass = 'bg-warning';
html += formatDataRow('内存使用', `${(device.mem_usage || 0).toFixed(1)}%`, memClass);
html += formatDataRow('CPU温度', `${(device.cpu_temp || 0).toFixed(1)}°C`);
html += formatDataRow('存储空间', `剩余 ${(device.free_space || 0).toFixed(1)}%`);
document.getElementById('system-resources-container').innerHTML = html;
}
function updateGpsInfo(location) {
if (!location.gps_valid) {
document.getElementById('gps-info-container').innerHTML =
'<div class="alert alert-warning">GPS信号无效或未获取</div>';
return;
}
let html = '';
html += formatDataRow('纬度', location.latitude.toFixed(6));
html += formatDataRow('经度', location.longitude.toFixed(6));
html += formatDataRow('方向', `${location.bearing.toFixed(1)}°`);
html += formatDataRow('海拔', `${location.altitude.toFixed(1)} m`);
html += formatDataRow('GPS精度', `${location.accuracy.toFixed(1)} m`);
html += formatDataRow('GPS速度', `${location.speed.toFixed(1)} km/h`, 'bg-primary');
document.getElementById('gps-info-container').innerHTML = html;
// 更新地图
updateMap(location.latitude, location.longitude, location.bearing);
}
function updateNavigation(nav) {
if (!nav || Object.keys(nav).length === 0) {
document.getElementById('navigation-container').innerHTML =
'<div class="alert alert-info">没有活动的导航</div>';
return;
}
let html = '';
// 格式化距离显示
const distRemaining = nav.distance_remaining || 0;
let distText = '';
if (distRemaining > 1000) {
distText = `${(distRemaining / 1000).toFixed(1)} km`;
} else {
distText = `${Math.round(distRemaining)} m`;
}
// 格式化时间显示
const timeRemaining = nav.time_remaining || 0;
const minutes = Math.floor(timeRemaining / 60);
const seconds = timeRemaining % 60;
html += formatDataRow('剩余距离', distText, 'bg-info');
html += formatDataRow('剩余时间', `${minutes}${seconds}`, 'bg-info');
html += formatDataRow('道路限速', `${(nav.speed_limit || 0).toFixed(1)} km/h`, 'bg-danger');
if (nav.maneuver_distance > 0) {
html += formatDataRow('下一动作', nav.maneuver_text, 'bg-warning');
html += formatDataRow('动作距离', `${nav.maneuver_distance} m`);
}
document.getElementById('navigation-container').innerHTML = html;
}
function updateDetailedVehicleInfo(carInfo) {
if (!carInfo || !carInfo.details) {
document.getElementById('detailed-vehicle-info-container').innerHTML =
'<div class="alert alert-info">没有详细车辆信息</div>';
return;
}
let html = '';
// 基本信息
if (carInfo.basic) {
html += '<h5>基本信息</h5>';
html += '<div class="mb-3">';
html += formatDataRow('车型', carInfo.basic.car_model);
html += formatDataRow('车辆指纹', carInfo.basic.fingerprint);
html += formatDataRow('车重', carInfo.basic.weight);
html += formatDataRow('轴距', carInfo.basic.wheelbase);
html += formatDataRow('转向比', carInfo.basic.steering_ratio);
html += '</div>';
}
// 巡航信息
if (carInfo.details.cruise) {
const cruise = carInfo.details.cruise;
html += '<h5>巡航控制</h5>';
html += '<div class="mb-3">';
html += formatDataRow('巡航状态', cruise.enabled ? '开启' : '关闭', cruise.enabled ? 'bg-success' : 'bg-secondary');
html += formatDataRow('自适应巡航', cruise.available ? '可用' : '不可用');
html += formatDataRow('设定速度', `${(cruise.speed || 0).toFixed(1)} km/h`, 'bg-info');
if (cruise.gap !== undefined) {
html += formatDataRow('跟车距离', cruise.gap);
}
html += '</div>';
}
// 车轮速度
if (carInfo.details.wheel_speeds) {
const ws = carInfo.details.wheel_speeds;
html += '<h5>车轮速度</h5>';
html += '<div class="mb-3">';
html += formatDataRow('左前', `${(ws.fl || 0).toFixed(1)} km/h`);
html += formatDataRow('右前', `${(ws.fr || 0).toFixed(1)} km/h`);
html += formatDataRow('左后', `${(ws.rl || 0).toFixed(1)} km/h`);
html += formatDataRow('右后', `${(ws.rr || 0).toFixed(1)} km/h`);
html += '</div>';
}
// 方向盘信息
if (carInfo.details.steering) {
const steering = carInfo.details.steering;
html += '<h5>方向盘系统</h5>';
html += '<div class="mb-3">';
html += formatDataRow('方向盘角度', `${(steering.angle || 0).toFixed(1)}°`);
html += formatDataRow('方向盘力矩', `${(steering.torque || 0).toFixed(1)} Nm`);
if (steering.rate !== undefined) {
html += formatDataRow('转向速率', `${steering.rate.toFixed(1)}°/s`);
}
html += '</div>';
}
// 安全系统
if (carInfo.details.safety_systems && Object.keys(carInfo.details.safety_systems).length > 0) {
const safety = carInfo.details.safety_systems;
html += '<h5>安全系统</h5>';
html += '<div class="mb-3">';
if (safety.esp_disabled !== undefined) {
html += formatDataRow('ESP状态', safety.esp_disabled ? '禁用' : '正常', safety.esp_disabled ? 'bg-warning' : 'bg-success');
}
if (safety.abs_active !== undefined) {
html += formatDataRow('ABS状态', safety.abs_active ? '激活' : '正常', safety.abs_active ? 'bg-warning' : 'bg-success');
}
if (safety.tcs_active !== undefined) {
html += formatDataRow('牵引力控制', safety.tcs_active ? '激活' : '正常', safety.tcs_active ? 'bg-warning' : 'bg-success');
}
if (safety.collision_warning !== undefined) {
html += formatDataRow('碰撞警告', safety.collision_warning ? '警告' : '正常', safety.collision_warning ? 'bg-danger' : 'bg-success');
}
html += '</div>';
}
// 车门信息
if (carInfo.details.doors && Object.keys(carInfo.details.doors).length > 0) {
const doors = carInfo.details.doors;
html += '<h5>车门状态</h5>';
html += '<div class="mb-3">';
html += formatDataRow('驾驶员门', doors.driver ? '打开' : '关闭', doors.driver ? 'bg-danger' : 'bg-success');
if (doors.passenger !== undefined) {
html += formatDataRow('乘客门', doors.passenger ? '打开' : '关闭', doors.passenger ? 'bg-danger' : 'bg-success');
}
if (doors.trunk !== undefined) {
html += formatDataRow('行李箱', doors.trunk ? '打开' : '关闭', doors.trunk ? 'bg-danger' : 'bg-success');
}
if (doors.hood !== undefined) {
html += formatDataRow('引擎盖', doors.hood ? '打开' : '关闭', doors.hood ? 'bg-danger' : 'bg-success');
}
if (carInfo.status && carInfo.status.seatbelt_unlatched !== undefined) {
html += formatDataRow('安全带', carInfo.status.seatbelt_unlatched ? '未系' : '已系', carInfo.status.seatbelt_unlatched ? 'bg-danger' : 'bg-success');
}
html += '</div>';
}
// 灯光状态
if (carInfo.details.lights && Object.keys(carInfo.details.lights).length > 0) {
const lights = carInfo.details.lights;
html += '<h5>灯光状态</h5>';
html += '<div class="mb-3">';
html += formatDataRow('左转向灯', lights.left_blinker ? '开启' : '关闭', lights.left_blinker ? 'bg-warning' : 'bg-secondary');
html += formatDataRow('右转向灯', lights.right_blinker ? '开启' : '关闭', lights.right_blinker ? 'bg-warning' : 'bg-secondary');
if (lights.high_beam !== undefined) {
html += formatDataRow('远光灯', lights.high_beam ? '开启' : '关闭', lights.high_beam ? 'bg-info' : 'bg-secondary');
}
if (lights.low_beam !== undefined) {
html += formatDataRow('近光灯', lights.low_beam ? '开启' : '关闭', lights.low_beam ? 'bg-info' : 'bg-secondary');
}
html += '</div>';
}
// 盲点监测
if (carInfo.details.blind_spot && Object.keys(carInfo.details.blind_spot).length > 0) {
const bs = carInfo.details.blind_spot;
html += '<h5>盲点监测</h5>';
html += '<div class="mb-3">';
if (bs.left !== undefined) {
html += formatDataRow('左侧', bs.left ? '检测到车辆' : '无车辆', bs.left ? 'bg-warning' : 'bg-success');
}
if (bs.right !== undefined) {
html += formatDataRow('右侧', bs.right ? '检测到车辆' : '无车辆', bs.right ? 'bg-warning' : 'bg-success');
}
html += '</div>';
}
// 其他信息
if (carInfo.details.other && Object.keys(carInfo.details.other).length > 0) {
const other = carInfo.details.other;
html += '<h5>其他信息</h5>';
html += '<div class="mb-3">';
if (other.outside_temp !== undefined) {
html += formatDataRow('车外温度', `${other.outside_temp.toFixed(1)}°C`);
}
if (other.fuel_range !== undefined) {
html += formatDataRow('续航里程', `${other.fuel_range.toFixed(1)} km`);
}
if (other.odometer !== undefined) {
html += formatDataRow('里程表', `${other.odometer.toFixed(1)} km`);
}
if (other.fuel_consumption !== undefined) {
html += formatDataRow('油耗', `${other.fuel_consumption.toFixed(1)} L/100km`);
}
html += '</div>';
}
// 如果没有任何详细信息
if (html === '') {
html = '<div class="alert alert-info">没有获取到详细车辆信息</div>';
}
document.getElementById('detailed-vehicle-info-container').innerHTML = html;
}
function fetchData() {
fetch('/api/data')
.then(response => response.json())
.then(data => {
const statusContainer = document.getElementById('status-container');
if (!data || !data.data || Object.keys(data.data).length === 0) {
statusContainer.className = 'status waiting';
statusContainer.textContent = '等待来自comma3的数据...';
return;
}
const currentTime = new Date().getTime() / 1000;
if (currentTime - data.last_update > 5) {
statusContainer.className = 'status expired';
statusContainer.textContent = `数据已过期! 上次更新: ${new Date(data.last_update * 1000).toLocaleTimeString()}`;
return;
}
// 数据有效更新UI
statusContainer.className = 'status connected';
statusContainer.textContent = `已连接到 ${data.device_ip || 'Comma3设备'} - 最后更新: ${new Date(data.last_update * 1000).toLocaleTimeString()}`;
// 获取数据
const carData = data.data.car || {};
const deviceData = data.data.device || {};
const locationData = data.data.location || {};
const navData = data.data.navigation || {};
// 先更新车辆状态并获取车辆是否启动
const isCarActive = updateVehicleStatus(carData);
// 根据车辆状态自动切换标签页
autoSwitchTabs(isCarActive);
// 更新所有数据区域
updateSteeringSystem(carData);
updatePedalStatus(carData);
updateDoorLights(carData);
updateCruiseInfo(carData);
updateDeviceInfo(deviceData);
updateSystemResources(deviceData);
updateGpsInfo(locationData);
updateNavigation(navData);
updateDetailedVehicleInfo(data.data.car_info);
// 更新原始数据
document.getElementById('raw-data').textContent = JSON.stringify(data.data, null, 2);
})
.catch(error => {
console.error('获取数据出错:', error);
document.getElementById('status-container').className = 'status expired';
document.getElementById('status-container').textContent = '连接错误: ' + error.message;
});
}
// 标签页切换处理
document.addEventListener('DOMContentLoaded', function() {
const tabEls = document.querySelectorAll('button[data-bs-toggle="tab"]');
tabEls.forEach(tabEl => {
tabEl.addEventListener('shown.bs.tab', function (event) {
// 更新当前活动标签
const id = event.target.id;
if (id.includes('car')) activeTab = 'car';
else if (id.includes('device')) activeTab = 'device';
else if (id.includes('location')) activeTab = 'location';
else activeTab = 'json';
});
});
fetchData();
// 设置自动刷新
const autoRefreshCheckbox = document.getElementById('auto-refresh');
let refreshInterval;
function setAutoRefresh() {
if (autoRefreshCheckbox.checked) {
refreshInterval = setInterval(fetchData, 1000);
} else {
clearInterval(refreshInterval);
}
}
autoRefreshCheckbox.addEventListener('change', setAutoRefresh);
setAutoRefresh();
});
</script>
<!-- 加载Google地图API (需要替换为您自己的API密钥) -->
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>
</body>
</html>
"""
def create_app(listener):
app = Flask(__name__)
@app.route('/')
def index():
"""主页"""
return render_template_string(HTML_TEMPLATE)
@app.route('/api/data')
def get_data():
"""提供JSON数据API"""
return jsonify({
'data': listener.data,
'last_update': listener.last_update,
'device_ip': listener.device_ip
})
return app
def main():
"""主函数"""
parser = argparse.ArgumentParser(description='CommaAssist Web监视器')
parser.add_argument('-p', '--port', type=int, default=8088, help='监听comma设备的UDP端口(默认: 8088)')
parser.add_argument('-w', '--web-port', type=int, default=5000, help='Web服务器端口(默认: 5000)')
parser.add_argument('--host', default='0.0.0.0', help='Web服务器监听地址(默认: 0.0.0.0)')
args = parser.parse_args()
# 创建监听器
listener = CommaWebListener(port=args.port)
# 启动接收线程
receiver_thread = threading.Thread(target=listener.start_listening)
receiver_thread.daemon = True
receiver_thread.start()
# 创建Flask应用
app = create_app(listener)
# 启动Web服务器
try:
print(f"Web服务已启动请访问 http://{args.host}:{args.web_port}/")
app.run(host=args.host, port=args.web_port)
except KeyboardInterrupt:
print("\n退出...")
finally:
listener.running = False
receiver_thread.join(timeout=1.0)
if __name__ == "__main__":
main()