| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- import { HeartRateController, type HeartRateControllerCallbacks, type HeartRateDiscoveredDevice } from './heartRateController'
- import { DEFAULT_MOCK_HEART_RATE_BRIDGE_URL, MockHeartRateBridge } from './mockHeartRateBridge'
- export type HeartRateSourceMode = 'real' | 'mock'
- export interface HeartRateInputControllerCallbacks {
- onHeartRate: (bpm: number) => void
- onStatus: (message: string) => void
- onError: (message: string) => void
- onConnectionChange: (connected: boolean, deviceName: string | null) => void
- onDeviceListChange: (devices: HeartRateDiscoveredDevice[]) => void
- onDebugStateChange?: () => void
- }
- export interface HeartRateInputControllerDebugState {
- sourceMode: HeartRateSourceMode
- sourceModeText: string
- mockBridgeConnected: boolean
- mockBridgeStatusText: string
- mockBridgeUrlText: string
- mockChannelIdText: string
- mockHeartRateText: string
- }
- function formatSourceModeText(mode: HeartRateSourceMode): string {
- return mode === 'mock' ? '模拟心率' : '真实心率'
- }
- function formatMockHeartRateText(bpm: number | null): string {
- return bpm === null ? '--' : `${bpm} bpm`
- }
- function normalizeMockBridgeUrl(rawUrl: string): string {
- const trimmed = rawUrl.trim()
- if (!trimmed) {
- return DEFAULT_MOCK_HEART_RATE_BRIDGE_URL
- }
- let normalized = trimmed
- if (!/^wss?:\/\//i.test(normalized)) {
- normalized = `ws://${normalized.replace(/^\/+/, '')}`
- }
- if (!/\/mock-hr(?:\?.*)?$/i.test(normalized)) {
- normalized = normalized.replace(/\/+$/, '')
- normalized = `${normalized}/mock-hr`
- }
- return normalized
- }
- export class HeartRateInputController {
- callbacks: HeartRateInputControllerCallbacks
- realController: HeartRateController
- mockBridge: MockHeartRateBridge
- sourceMode: HeartRateSourceMode
- mockBridgeStatusText: string
- mockBridgeUrl: string
- mockChannelId: string
- mockBpm: number | null
- constructor(callbacks: HeartRateInputControllerCallbacks) {
- this.callbacks = callbacks
- this.sourceMode = 'real'
- this.mockBridgeUrl = DEFAULT_MOCK_HEART_RATE_BRIDGE_URL
- this.mockBridgeStatusText = `未连接 (${this.mockBridgeUrl})`
- this.mockChannelId = 'default'
- this.mockBpm = null
- const realCallbacks: HeartRateControllerCallbacks = {
- onHeartRate: (bpm) => {
- if (this.sourceMode !== 'real') {
- return
- }
- this.callbacks.onHeartRate(bpm)
- this.emitDebugState()
- },
- onStatus: (message) => {
- if (this.sourceMode !== 'real') {
- return
- }
- this.callbacks.onStatus(message)
- this.emitDebugState()
- },
- onError: (message) => {
- if (this.sourceMode !== 'real') {
- return
- }
- this.callbacks.onError(message)
- this.emitDebugState()
- },
- onConnectionChange: (connected, deviceName) => {
- if (this.sourceMode !== 'real') {
- return
- }
- this.callbacks.onConnectionChange(connected, deviceName)
- this.emitDebugState()
- },
- onDeviceListChange: (devices) => {
- this.callbacks.onDeviceListChange(devices)
- this.emitDebugState()
- },
- }
- this.realController = new HeartRateController(realCallbacks)
- this.mockBridge = new MockHeartRateBridge({
- onOpen: () => {
- this.mockBridgeStatusText = `已连接 (${this.mockBridge.url})`
- if (this.sourceMode === 'mock') {
- this.callbacks.onConnectionChange(true, '模拟心率源')
- this.callbacks.onStatus('模拟心率源已连接,等待外部输入')
- }
- this.emitDebugState()
- },
- onClose: () => {
- this.mockBridgeStatusText = `未连接 (${this.mockBridge.url})`
- if (this.sourceMode === 'mock') {
- this.callbacks.onConnectionChange(false, null)
- this.callbacks.onStatus('模拟心率源已断开')
- }
- this.emitDebugState()
- },
- onError: (message) => {
- this.mockBridgeStatusText = `连接失败 (${this.mockBridge.url})`
- if (this.sourceMode === 'mock') {
- this.callbacks.onConnectionChange(false, null)
- this.callbacks.onError(`模拟心率源错误: ${message}`)
- }
- this.emitDebugState()
- },
- onBpm: (bpm) => {
- this.mockBpm = bpm
- if (this.sourceMode === 'mock') {
- this.callbacks.onHeartRate(bpm)
- }
- this.emitDebugState()
- },
- })
- }
- get currentDeviceId(): string | null {
- if (this.sourceMode === 'mock') {
- return this.mockBridge.connected ? 'mock-heart-rate' : null
- }
- return this.realController.currentDeviceId
- }
- get currentDeviceName(): string | null {
- if (this.sourceMode === 'mock') {
- return this.mockBridge.connected ? '模拟心率源' : null
- }
- return this.realController.currentDeviceName
- }
- get connected(): boolean {
- return this.sourceMode === 'mock' ? this.mockBridge.connected : this.realController.connected
- }
- get connecting(): boolean {
- return this.sourceMode === 'mock' ? this.mockBridge.connecting : this.realController.connecting
- }
- get scanning(): boolean {
- return this.sourceMode === 'mock' ? false : this.realController.scanning
- }
- get reconnecting(): boolean {
- return this.sourceMode === 'mock' ? false : this.realController.reconnecting
- }
- get disconnecting(): boolean {
- return this.sourceMode === 'mock' ? false : this.realController.disconnecting
- }
- get discoveredDevices(): HeartRateDiscoveredDevice[] {
- return this.realController.discoveredDevices
- }
- get lastDeviceId(): string | null {
- return this.realController.lastDeviceId
- }
- get lastDeviceName(): string | null {
- return this.realController.lastDeviceName
- }
- getDebugState(): HeartRateInputControllerDebugState {
- return {
- sourceMode: this.sourceMode,
- sourceModeText: formatSourceModeText(this.sourceMode),
- mockBridgeConnected: this.mockBridge.connected,
- mockBridgeStatusText: this.mockBridgeStatusText,
- mockBridgeUrlText: this.mockBridgeUrl,
- mockChannelIdText: this.mockChannelId,
- mockHeartRateText: formatMockHeartRateText(this.mockBpm),
- }
- }
- startScanAndConnect(): void {
- if (this.sourceMode === 'mock') {
- this.callbacks.onStatus(this.mockBridge.connected ? '模拟心率源已连接' : '当前为模拟心率模式,请连接模拟源')
- this.emitDebugState()
- return
- }
- this.realController.startScanAndConnect()
- }
- disconnect(): void {
- if (this.sourceMode === 'mock') {
- if (!this.mockBridge.connected && !this.mockBridge.connecting) {
- this.callbacks.onStatus('模拟心率源未连接')
- this.emitDebugState()
- return
- }
- this.mockBridge.disconnect()
- this.emitDebugState()
- return
- }
- this.realController.disconnect()
- }
- destroy(): void {
- this.realController.destroy()
- this.mockBridge.destroy()
- }
- setSourceMode(mode: HeartRateSourceMode): void {
- if (this.sourceMode === mode) {
- this.callbacks.onStatus(`${formatSourceModeText(mode)}已启用`)
- this.emitDebugState()
- return
- }
- const previousMode = this.sourceMode
- this.sourceMode = mode
- if (previousMode === 'real') {
- this.realController.disconnect()
- } else {
- this.callbacks.onConnectionChange(false, null)
- }
- const activeDeviceName = this.currentDeviceName
- this.callbacks.onConnectionChange(this.connected, activeDeviceName)
- this.callbacks.onStatus(mode === 'mock' ? '已切换到模拟心率模式' : '已切换到真实心率模式')
- this.emitDebugState()
- }
- setMockBridgeUrl(url: string): void {
- this.mockBridgeUrl = normalizeMockBridgeUrl(url)
- if (this.mockBridge.connected || this.mockBridge.connecting) {
- this.mockBridgeStatusText = `已设置新地址,重连生效 (${this.mockBridgeUrl})`
- if (this.sourceMode === 'mock') {
- this.callbacks.onStatus('模拟心率源地址已更新,重连后生效')
- }
- } else {
- this.mockBridgeStatusText = `未连接 (${this.mockBridgeUrl})`
- if (this.sourceMode === 'mock') {
- this.callbacks.onStatus('模拟心率源地址已更新')
- }
- }
- 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') {
- this.callbacks.onStatus('模拟心率源已连接')
- }
- this.emitDebugState()
- return
- }
- const targetUrl = normalizeMockBridgeUrl(url === DEFAULT_MOCK_HEART_RATE_BRIDGE_URL ? this.mockBridgeUrl : url)
- this.mockBridgeUrl = targetUrl
- this.mockBridgeStatusText = `连接中 (${targetUrl})`
- if (this.sourceMode === 'mock') {
- this.callbacks.onStatus('模拟心率源连接中')
- }
- this.emitDebugState()
- this.mockBridge.connect(targetUrl)
- }
- disconnectMockBridge(): void {
- if (!this.mockBridge.connected && !this.mockBridge.connecting) {
- if (this.sourceMode === 'mock') {
- this.callbacks.onStatus('模拟心率源未连接')
- }
- this.emitDebugState()
- return
- }
- this.mockBridge.disconnect()
- this.mockBridgeStatusText = `未连接 (${this.mockBridge.url})`
- this.emitDebugState()
- }
- connectToDiscoveredDevice(deviceId: string): void {
- if (this.sourceMode !== 'real') {
- this.callbacks.onStatus('当前为模拟心率模式,无法连接真实心率带')
- this.emitDebugState()
- return
- }
- this.realController.connectToDiscoveredDevice(deviceId)
- }
- clearPreferredDevice(): void {
- this.realController.clearPreferredDevice()
- this.emitDebugState()
- }
- emitDebugState(): void {
- if (this.callbacks.onDebugStateChange) {
- this.callbacks.onDebugStateChange()
- }
- }
- }
|