WellNuo/services/ble/MockBLEManager.ts
Sergei d289dd79a1 Add comprehensive sensor health monitoring system
Implemented a full sensor health monitoring system for WP sensors that
tracks connectivity, WiFi signal strength, communication quality, and
overall device health.

Features:
- Health status calculation (excellent/good/fair/poor/critical)
- WiFi signal quality monitoring (RSSI-based)
- Communication statistics tracking (success rate, response times)
- BLE connection metrics (RSSI, attempt/failure counts)
- Time-based status (online/warning/offline based on last seen)
- Health data caching and retrieval

Components:
- Added SensorHealthMetrics types with detailed health indicators
- Implemented getSensorHealth() and getAllSensorHealth() in BLEManager
- Created SensorHealthCard UI component for health visualization
- Added API endpoints for health history and reporting
- Automatic communication stats tracking on every BLE command

Testing:
- 12 new tests for health monitoring functionality
- All 89 BLE tests passing
- No linting errors

This enables proactive monitoring of sensor health to identify
connectivity issues, poor WiFi signal, or failing devices before they
impact user experience.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-31 16:15:32 -08:00

304 lines
8.1 KiB
TypeScript

// Mock BLE Manager для iOS Simulator (Bluetooth недоступен)
import {
IBLEManager,
WPDevice,
WiFiNetwork,
WiFiStatus,
BLEConnectionState,
BLEDeviceConnection,
BLEEventListener,
BLEConnectionEvent,
SensorHealthMetrics,
SensorHealthStatus,
WiFiSignalQuality,
CommunicationHealth,
} from './types';
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
export class MockBLEManager implements IBLEManager {
private connectedDevices = new Set<string>();
private connectionStates = new Map<string, BLEDeviceConnection>();
private eventListeners: BLEEventListener[] = [];
private connectingDevices = new Set<string>(); // Track devices currently being connected
// Health monitoring state (mock)
private sensorHealthMetrics = new Map<string, SensorHealthMetrics>();
private communicationStats = new Map<string, CommunicationHealth>();
private mockDevices: WPDevice[] = [
{
id: 'mock-743',
name: 'WP_497_81a14c',
mac: '142B2F81A14C',
rssi: -55,
wellId: 497,
},
{
id: 'mock-769',
name: 'WP_523_81aad4',
mac: '142B2F81AAD4',
rssi: -67,
wellId: 523,
},
];
/**
* Update connection state and notify listeners
*/
private updateConnectionState(
deviceId: string,
state: BLEConnectionState,
deviceName?: string,
error?: string
): void {
const existing = this.connectionStates.get(deviceId);
const now = Date.now();
const connection: BLEDeviceConnection = {
deviceId,
deviceName: deviceName || existing?.deviceName || deviceId,
state,
error,
connectedAt: state === BLEConnectionState.CONNECTED ? now : existing?.connectedAt,
lastActivity: now,
};
this.connectionStates.set(deviceId, connection);
this.emitEvent(deviceId, 'state_changed', { state, error });
}
/**
* Emit event to all registered listeners
*/
private emitEvent(deviceId: string, event: BLEConnectionEvent, data?: any): void {
this.eventListeners.forEach((listener) => {
try {
listener(deviceId, event, data);
} catch {
// Listener error should not crash the app
}
});
}
/**
* Get current connection state for a device
*/
getConnectionState(deviceId: string): BLEConnectionState {
const connection = this.connectionStates.get(deviceId);
return connection?.state || BLEConnectionState.DISCONNECTED;
}
/**
* Get all active connections
*/
getAllConnections(): Map<string, BLEDeviceConnection> {
return new Map(this.connectionStates);
}
/**
* Add event listener
*/
addEventListener(listener: BLEEventListener): void {
if (!this.eventListeners.includes(listener)) {
this.eventListeners.push(listener);
}
}
/**
* Remove event listener
*/
removeEventListener(listener: BLEEventListener): void {
const index = this.eventListeners.indexOf(listener);
if (index > -1) {
this.eventListeners.splice(index, 1);
}
}
async scanDevices(): Promise<WPDevice[]> {
await delay(2000); // Simulate scan delay
return this.mockDevices;
}
stopScan(): void {
}
async connectDevice(deviceId: string): Promise<boolean> {
try {
// Check if connection is already in progress
if (this.connectingDevices.has(deviceId)) {
throw new Error('Connection already in progress for this device');
}
// Check if already connected
if (this.connectedDevices.has(deviceId)) {
this.updateConnectionState(deviceId, BLEConnectionState.READY);
this.emitEvent(deviceId, 'ready');
return true;
}
// Mark device as connecting
this.connectingDevices.add(deviceId);
this.updateConnectionState(deviceId, BLEConnectionState.CONNECTING);
await delay(500);
this.updateConnectionState(deviceId, BLEConnectionState.CONNECTED);
await delay(300);
this.updateConnectionState(deviceId, BLEConnectionState.DISCOVERING);
await delay(200);
this.connectedDevices.add(deviceId);
this.updateConnectionState(deviceId, BLEConnectionState.READY);
this.emitEvent(deviceId, 'ready');
return true;
} catch (error: any) {
const errorMessage = error?.message || 'Connection failed';
this.updateConnectionState(deviceId, BLEConnectionState.ERROR, undefined, errorMessage);
this.emitEvent(deviceId, 'connection_failed', { error: errorMessage });
return false;
} finally {
// Always remove from connecting set when done (success or failure)
this.connectingDevices.delete(deviceId);
}
}
async disconnectDevice(deviceId: string): Promise<void> {
this.updateConnectionState(deviceId, BLEConnectionState.DISCONNECTING);
await delay(500);
this.connectedDevices.delete(deviceId);
this.updateConnectionState(deviceId, BLEConnectionState.DISCONNECTED);
this.emitEvent(deviceId, 'disconnected');
}
isDeviceConnected(deviceId: string): boolean {
return this.connectedDevices.has(deviceId);
}
async sendCommand(deviceId: string, command: string): Promise<string> {
await delay(500);
// Simulate responses
if (command === 'pin|7856') {
return 'pin|ok';
}
if (command === 'w') {
return 'mac,142b2f81a14c|w|3|FrontierTower,-55|HomeNetwork,-67|TP-Link_5G,-75';
}
if (command === 'a') {
return 'mac,142b2f81a14c|a|FrontierTower,-67';
}
if (command.startsWith('W|')) {
return 'mac,142b2f81a14c|W|ok';
}
return 'ok';
}
async getWiFiList(deviceId: string): Promise<WiFiNetwork[]> {
await delay(1500);
return [
{ ssid: 'FrontierTower', rssi: -55 },
{ ssid: 'HomeNetwork', rssi: -67 },
{ ssid: 'TP-Link_5G', rssi: -75 },
{ ssid: 'Office-WiFi', rssi: -80 },
];
}
async setWiFi(
deviceId: string,
ssid: string,
password: string
): Promise<boolean> {
await delay(2000);
return true;
}
async getCurrentWiFi(deviceId: string): Promise<WiFiStatus | null> {
await delay(1000);
return {
ssid: 'FrontierTower',
rssi: -67,
connected: true,
};
}
async rebootDevice(deviceId: string): Promise<void> {
await delay(500);
this.connectedDevices.delete(deviceId);
}
/**
* Get sensor health metrics (mock implementation)
*/
async getSensorHealth(wellId: number, mac: string): Promise<SensorHealthMetrics | null> {
await delay(1000);
const deviceKey = `WP_${wellId}_${mac.slice(-6).toLowerCase()}`;
// Generate mock health metrics
const now = Date.now();
const mockMetrics: SensorHealthMetrics = {
deviceId: `mock-${wellId}`,
deviceName: deviceKey,
overallHealth: SensorHealthStatus.GOOD,
connectionStatus: 'online',
lastSeen: new Date(),
lastSeenMinutesAgo: 2,
wifiSignalQuality: WiFiSignalQuality.GOOD,
wifiRssi: -60,
wifiSsid: 'FrontierTower',
wifiConnected: true,
communication: {
successfulCommands: 45,
failedCommands: 2,
averageResponseTime: 350,
lastSuccessfulCommand: now - 120000, // 2 minutes ago
lastFailedCommand: now - 3600000, // 1 hour ago
},
bleRssi: -55,
bleConnectionAttempts: 10,
bleConnectionFailures: 1,
lastHealthCheck: now,
lastBleConnection: now - 120000,
};
this.sensorHealthMetrics.set(deviceKey, mockMetrics);
return mockMetrics;
}
/**
* Get all cached sensor health metrics (mock)
*/
getAllSensorHealth(): Map<string, SensorHealthMetrics> {
return new Map(this.sensorHealthMetrics);
}
/**
* Cleanup all BLE connections and state
* Should be called on app logout to properly release resources
*/
async cleanup(): Promise<void> {
// Disconnect all connected devices
const deviceIds = Array.from(this.connectedDevices);
for (const deviceId of deviceIds) {
await this.disconnectDevice(deviceId);
}
this.connectedDevices.clear();
this.connectionStates.clear();
this.connectingDevices.clear();
// Clear health monitoring data
this.sensorHealthMetrics.clear();
this.communicationStats.clear();
this.eventListeners = [];
}
}