feat: 收敛玩法运行时配置并加入故障恢复

This commit is contained in:
2026-04-01 13:04:26 +08:00
parent 1635a11780
commit 3ef841ecc7
73 changed files with 8820 additions and 2122 deletions

View File

@@ -18,6 +18,7 @@ export interface HeartRateInputControllerDebugState {
mockBridgeConnected: boolean
mockBridgeStatusText: string
mockBridgeUrlText: string
mockChannelIdText: string
mockHeartRateText: string
}
@@ -55,6 +56,7 @@ export class HeartRateInputController {
sourceMode: HeartRateSourceMode
mockBridgeStatusText: string
mockBridgeUrl: string
mockChannelId: string
mockBpm: number | null
constructor(callbacks: HeartRateInputControllerCallbacks) {
@@ -62,6 +64,7 @@ export class HeartRateInputController {
this.sourceMode = 'real'
this.mockBridgeUrl = DEFAULT_MOCK_HEART_RATE_BRIDGE_URL
this.mockBridgeStatusText = `未连接 (${this.mockBridgeUrl})`
this.mockChannelId = 'default'
this.mockBpm = null
const realCallbacks: HeartRateControllerCallbacks = {
@@ -194,6 +197,7 @@ export class HeartRateInputController {
mockBridgeConnected: this.mockBridge.connected,
mockBridgeStatusText: this.mockBridgeStatusText,
mockBridgeUrlText: this.mockBridgeUrl,
mockChannelIdText: this.mockChannelId,
mockHeartRateText: formatMockHeartRateText(this.mockBpm),
}
}
@@ -269,6 +273,16 @@ export class HeartRateInputController {
this.emitDebugState()
}
setMockChannelId(channelId: string): void {
const normalized = String(channelId || '').trim() || 'default'
this.mockChannelId = normalized
this.mockBridge.setChannelId(normalized)
if (this.sourceMode === 'mock') {
this.callbacks.onStatus(`模拟心率通道已切换到 ${normalized}`)
}
this.emitDebugState()
}
connectMockBridge(url = DEFAULT_MOCK_HEART_RATE_BRIDGE_URL): void {
if (this.mockBridge.connected || this.mockBridge.connecting) {
if (this.sourceMode === 'mock') {

View File

@@ -12,6 +12,7 @@ export interface LocationControllerDebugState {
mockBridgeConnected: boolean
mockBridgeStatusText: string
mockBridgeUrlText: string
mockChannelIdText: string
mockCoordText: string
mockSpeedText: string
}
@@ -70,12 +71,14 @@ export class LocationController {
sourceMode: LocationSourceMode
mockBridgeStatusText: string
mockBridgeUrl: string
mockChannelId: string
constructor(callbacks: LocationControllerCallbacks) {
this.callbacks = callbacks
this.sourceMode = 'real'
this.mockBridgeUrl = DEFAULT_MOCK_LOCATION_BRIDGE_URL
this.mockBridgeStatusText = `未连接 (${this.mockBridgeUrl})`
this.mockChannelId = 'default'
const sourceCallbacks: LocationSourceCallbacks = {
onLocation: (sample) => {
@@ -129,6 +132,7 @@ export class LocationController {
mockBridgeConnected: this.mockBridge.connected,
mockBridgeStatusText: this.mockBridgeStatusText,
mockBridgeUrlText: this.mockBridgeUrl,
mockChannelIdText: this.mockChannelId,
mockCoordText: formatMockCoordText(this.mockSource.lastSample),
mockSpeedText: formatMockSpeedText(this.mockSource.lastSample),
}
@@ -187,6 +191,14 @@ export class LocationController {
this.emitDebugState()
}
setMockChannelId(channelId: string): void {
const normalized = String(channelId || '').trim() || 'default'
this.mockChannelId = normalized
this.mockBridge.setChannelId(normalized)
this.callbacks.onStatus(`模拟定位通道已切换到 ${normalized}`)
this.emitDebugState()
}
connectMockBridge(url = DEFAULT_MOCK_LOCATION_BRIDGE_URL): void {
if (this.mockBridge.connected || this.mockBridge.connecting) {
this.callbacks.onStatus('模拟定位源已连接')

View File

@@ -11,6 +11,12 @@ type RawMockHeartRateMessage = {
type?: string
timestamp?: number
bpm?: number
channelId?: string
}
function normalizeMockChannelId(rawChannelId: string | null | undefined): string {
const trimmed = String(rawChannelId || '').trim()
return trimmed || 'default'
}
function safeParseMessage(data: string): RawMockHeartRateMessage | null {
@@ -21,11 +27,15 @@ function safeParseMessage(data: string): RawMockHeartRateMessage | null {
}
}
function toHeartRateValue(message: RawMockHeartRateMessage): number | null {
function toHeartRateValue(message: RawMockHeartRateMessage, expectedChannelId: string): number | null {
if (message.type !== 'mock_heart_rate' || !Number.isFinite(message.bpm)) {
return null
}
if (normalizeMockChannelId(message.channelId) !== expectedChannelId) {
return null
}
const bpm = Math.round(Number(message.bpm))
if (bpm <= 0) {
return null
@@ -40,6 +50,7 @@ export class MockHeartRateBridge {
connected: boolean
connecting: boolean
url: string
channelId: string
constructor(callbacks: MockHeartRateBridgeCallbacks) {
this.callbacks = callbacks
@@ -47,6 +58,11 @@ export class MockHeartRateBridge {
this.connected = false
this.connecting = false
this.url = DEFAULT_MOCK_HEART_RATE_BRIDGE_URL
this.channelId = 'default'
}
setChannelId(channelId: string): void {
this.channelId = normalizeMockChannelId(channelId)
}
connect(url = DEFAULT_MOCK_HEART_RATE_BRIDGE_URL): void {
@@ -96,7 +112,7 @@ export class MockHeartRateBridge {
return
}
const bpm = toHeartRateValue(parsed)
const bpm = toHeartRateValue(parsed, this.channelId)
if (bpm === null) {
return
}

View File

@@ -17,6 +17,12 @@ type RawMockGpsMessage = {
accuracyMeters?: number
speedMps?: number
headingDeg?: number
channelId?: string
}
function normalizeMockChannelId(rawChannelId: string | null | undefined): string {
const trimmed = String(rawChannelId || '').trim()
return trimmed || 'default'
}
function safeParseMessage(data: string): RawMockGpsMessage | null {
@@ -27,7 +33,7 @@ function safeParseMessage(data: string): RawMockGpsMessage | null {
}
}
function toLocationSample(message: RawMockGpsMessage): LocationSample | null {
function toLocationSample(message: RawMockGpsMessage, expectedChannelId: string): LocationSample | null {
if (message.type !== 'mock_gps') {
return null
}
@@ -36,6 +42,10 @@ function toLocationSample(message: RawMockGpsMessage): LocationSample | null {
return null
}
if (normalizeMockChannelId(message.channelId) !== expectedChannelId) {
return null
}
return {
latitude: Number(message.lat),
longitude: Number(message.lon),
@@ -53,6 +63,7 @@ export class MockLocationBridge {
connected: boolean
connecting: boolean
url: string
channelId: string
constructor(callbacks: MockLocationBridgeCallbacks) {
this.callbacks = callbacks
@@ -60,6 +71,11 @@ export class MockLocationBridge {
this.connected = false
this.connecting = false
this.url = DEFAULT_MOCK_LOCATION_BRIDGE_URL
this.channelId = 'default'
}
setChannelId(channelId: string): void {
this.channelId = normalizeMockChannelId(channelId)
}
connect(url = DEFAULT_MOCK_LOCATION_BRIDGE_URL): void {
@@ -109,7 +125,7 @@ export class MockLocationBridge {
return
}
const sample = toLocationSample(parsed)
const sample = toLocationSample(parsed, this.channelId)
if (!sample) {
return
}