carrot/selfdrive/app/commawebview.py
机械小鸽 37da15755b
Dev (#17)
* app

* add

* add

* update and teest

* add service

* 1
2025-03-29 20:07:23 +08:00

858 lines
36 KiB
Python
Raw 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.

#!/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()