Files
usda-vision/vision-system-remote/src/services/api.ts
salirezav 73849b40a8 Add MQTT publish request and response models, and implement publish route
- Introduced MQTTPublishRequest and MQTTPublishResponse models for handling MQTT message publishing.
- Implemented a new POST route for publishing MQTT messages, including error handling and logging.
- Enhanced the StandaloneAutoRecorder with improved logging during manual recording start.
- Updated the frontend to include an MQTT Debug Panel for better monitoring and debugging capabilities.
2025-12-01 13:07:36 -05:00

347 lines
9.3 KiB
TypeScript

// Vision System API Client for vision-system-remote
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - Vite provides import.meta.env
const VISION_API_BASE_URL = (import.meta.env?.VITE_VISION_API_URL as string | undefined) || 'http://exp-dash:8000'
// Types (simplified - we'll use the same types from the original)
export interface SystemStatus {
system_started: boolean
mqtt_connected: boolean
last_mqtt_message: string
machines: Record<string, MachineStatus>
cameras: Record<string, CameraStatus>
active_recordings: number
total_recordings: number
uptime_seconds: number
}
export interface MachineStatus {
name: string
state: string
last_updated: string
last_message?: string
mqtt_topic?: string
}
export interface CameraStatus {
name?: string
status: string
is_recording: boolean
last_checked: string
last_error?: string | null
device_info?: {
friendly_name?: string
serial_number?: string
port_type?: string
model?: string
firmware_version?: string
}
current_recording_file?: string | null
recording_start_time?: string | null
last_frame_time?: string
frame_rate?: number
auto_recording_enabled: boolean
auto_recording_active: boolean
auto_recording_failure_count: number
auto_recording_last_attempt?: string
auto_recording_last_error?: string
}
export interface RecordingInfo {
camera_name: string
filename: string
start_time: string
state: string
end_time?: string
file_size_bytes?: number
frame_count?: number
duration_seconds?: number
error_message?: string | null
}
export interface StorageStats {
base_path: string
total_files: number
total_size_bytes: number
cameras: Record<string, {
file_count: number
total_size_bytes: number
}>
disk_usage: {
total: number
used: number
free: number
}
}
export interface MqttStatus {
connected: boolean
broker_host: string
broker_port: number
subscribed_topics: string[]
last_message_time: string
message_count: number
error_count: number
uptime_seconds: number
}
export interface MqttEvent {
machine_name: string
topic: string
payload: string
normalized_state: string
timestamp: string
message_number: number
}
export interface MqttEventsResponse {
events: MqttEvent[]
total_events: number
last_updated: string | null
}
export interface StartRecordingResponse {
success: boolean
message: string
filename: string
}
export interface StopRecordingResponse {
success: boolean
message: string
}
export interface StreamStopResponse {
success: boolean
message: string
}
export interface CameraConfig {
name: string
machine_topic: string
storage_path: string
enabled: boolean
auto_start_recording_enabled: boolean
auto_recording_max_retries: number
auto_recording_retry_delay_seconds: number
exposure_ms: number
gain: number
target_fps: number
video_format: string
video_codec: string
video_quality: number
sharpness: number
contrast: number
saturation: number
gamma: number
noise_filter_enabled: boolean
denoise_3d_enabled: boolean
auto_white_balance: boolean
color_temperature_preset: number
wb_red_gain: number
wb_green_gain: number
wb_blue_gain: number
anti_flicker_enabled: boolean
light_frequency: number
bit_depth: number
hdr_enabled: boolean
hdr_gain_mode: number
}
export interface CameraConfigUpdate {
exposure_ms?: number
gain?: number
target_fps?: number
sharpness?: number
contrast?: number
saturation?: number
gamma?: number
noise_filter_enabled?: boolean
denoise_3d_enabled?: boolean
auto_white_balance?: boolean
color_temperature_preset?: number
wb_red_gain?: number
wb_green_gain?: number
wb_blue_gain?: number
anti_flicker_enabled?: boolean
light_frequency?: number
hdr_enabled?: boolean
hdr_gain_mode?: number
auto_start_recording_enabled?: boolean
auto_recording_max_retries?: number
auto_recording_retry_delay_seconds?: number
}
export interface CameraConfigUpdateResponse {
success: boolean
message: string
updated_settings: string[]
}
export interface CameraRecoveryResponse {
success: boolean
message: string
camera_name: string
operation: string
}
export interface StreamStartResponse {
success: boolean
message: string
}
class VisionApiClient {
private baseUrl: string
constructor(baseUrl: string = VISION_API_BASE_URL) {
this.baseUrl = baseUrl
}
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const url = `${this.baseUrl}${endpoint}`
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`)
}
return response.json()
}
async getSystemStatus(): Promise<SystemStatus> {
return this.request('/system/status')
}
async getCameras(): Promise<Record<string, CameraStatus>> {
return this.request('/cameras')
}
async getRecordings(): Promise<Record<string, RecordingInfo>> {
return this.request('/recordings')
}
async getStorageStats(): Promise<StorageStats> {
return this.request('/storage/stats')
}
async getMqttStatus(): Promise<MqttStatus> {
return this.request('/mqtt/status')
}
async getMqttEvents(limit: number = 50): Promise<MqttEventsResponse> {
return this.request(`/mqtt/events?limit=${limit}`)
}
async publishMqttMessage(topic: string, payload: string, qos: number = 0, retain: boolean = false): Promise<{ success: boolean; message: string; topic: string; payload: string }> {
return this.request('/mqtt/publish', {
method: 'POST',
body: JSON.stringify({ topic, payload, qos, retain }),
})
}
async startRecording(cameraName: string, filename?: string): Promise<StartRecordingResponse> {
return this.request(`/cameras/${cameraName}/start-recording`, {
method: 'POST',
body: JSON.stringify({ filename }),
})
}
async stopRecording(cameraName: string): Promise<StopRecordingResponse> {
return this.request(`/cameras/${cameraName}/stop-recording`, {
method: 'POST',
})
}
async stopStream(cameraName: string): Promise<StreamStopResponse> {
return this.request(`/cameras/${cameraName}/stop-stream`, {
method: 'POST',
})
}
getStreamUrl(cameraName: string): string {
return `${this.baseUrl}/cameras/${cameraName}/stream`
}
async startStream(cameraName: string): Promise<StreamStartResponse> {
return this.request(`/cameras/${cameraName}/start-stream`, {
method: 'POST',
})
}
async getCameraConfig(cameraName: string): Promise<CameraConfig> {
return this.request(`/cameras/${cameraName}/config`)
}
async updateCameraConfig(cameraName: string, config: CameraConfigUpdate): Promise<CameraConfigUpdateResponse> {
return this.request(`/cameras/${cameraName}/config`, {
method: 'PUT',
body: JSON.stringify(config),
})
}
async applyCameraConfig(cameraName: string): Promise<{ success: boolean; message: string }> {
return this.request(`/cameras/${cameraName}/apply-config`, {
method: 'POST',
})
}
async reinitializeCamera(cameraName: string): Promise<CameraRecoveryResponse> {
return this.request(`/cameras/${cameraName}/reinitialize`, {
method: 'POST',
})
}
async fullResetCamera(cameraName: string): Promise<CameraRecoveryResponse> {
return this.request(`/cameras/${cameraName}/full-reset`, {
method: 'POST',
})
}
}
export const visionApi = new VisionApiClient()
// Utility functions
export const formatBytes = (bytes: number): string => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
export const formatDuration = (seconds: number): string => {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = Math.floor(seconds % 60)
if (hours > 0) {
return `${hours}h ${minutes}m ${secs}s`
} else if (minutes > 0) {
return `${minutes}m ${secs}s`
} else {
return `${secs}s`
}
}
export const formatUptime = (seconds: number): string => {
const days = Math.floor(seconds / 86400)
const hours = Math.floor((seconds % 86400) / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
if (days > 0) {
return `${days}d ${hours}h ${minutes}m`
} else if (hours > 0) {
return `${hours}h ${minutes}m`
} else {
return `${minutes}m`
}
}