import {z} from 'zod' export type GetApiApiV1ReelHeartbeatOptions = {baseUrl: string, OAuth2PasswordBearer?: string | undefined, fetch?: typeof fetch | undefined}; export const safeguardViolationsSchema = z.object({cable_position_below_zero_point: z.boolean().nullable().optional(), cable_position_exceeds_max_length: z.boolean().nullable().optional(), no_cable_movement_detected: z.boolean().nullable().optional()}); export const roboclawStatusSchema = z.object({estop_active: z.boolean().optional(), temp_error: z.boolean().optional(), main_voltage_high: z.boolean().optional(), logic_voltage_high: z.boolean().optional(), logic_voltage_low: z.boolean().optional(), m1_driver_fault: z.boolean().optional(), m1_speed_error: z.boolean().optional(), m1_position_error: z.boolean().optional(), m1_current_error: z.boolean().optional(), m1_over_current: z.boolean().optional(), main_voltage_low: z.boolean().optional(), temp_warning: z.boolean().optional(), s4_signal_triggered: z.boolean().optional(), s5_signal_triggered: z.boolean().optional(), speed_error_limit_warning: z.boolean().optional(), position_error_limit_warning: z.boolean().optional()}); export const roboclawStateSchema = z.object({driver_type: z.literal("roboclaw").optional(), input_voltage: z.number().nullable().optional(), firmware_version: z.string().nullable().optional(), statuses: roboclawStatusSchema.optional(), active_errors: z.array(z.string()).optional()}); export const motoronStatusSchema = z.object({reset: z.boolean().optional(), error_active: z.boolean().optional(), motor_output_enabled: z.boolean().optional(), motor_driving: z.boolean().optional(), no_power: z.boolean().optional(), motor_faulting: z.boolean().optional()}); export const motoronStateSchema = z.object({driver_type: z.literal("motoron").optional(), input_voltage: z.number().nullable().optional(), statuses: motoronStatusSchema.optional()}); export const reelStateSchema = z.object({loop_control_mode: z.number().int().gte(0).lte(1), cable_position: z.number(), cable_position_pulses: z.number().int(), cable_speed_mps: z.number().nullable().optional(), reel_direction: z.union([z.number().int(), z.string()]), speed_percentage: z.number().int().gte(0).lte(100), reel_safeguards_enabled: z.boolean(), is_remote_active: z.boolean(), reed_switch_closed: z.boolean().nullable().optional(), is_encoder_disconnected: z.boolean(), is_cable_counter_calibrating: z.boolean(), motor_driver_unresponsive: z.boolean(), motor_driver_error: z.boolean(), motor_driver_error_msgs: z.array(z.string()), motor_encoder_position: z.number().nullable().optional(), is_jogging: z.boolean(), is_maintaining_position: z.boolean().nullable().optional(), estop_active: z.boolean().nullable().optional(), command_lockout_enabled: z.boolean(), safeguard_violations: safeguardViolationsSchema.optional(), motor_driver: z.union([roboclawStateSchema, motoronStateSchema]).optional()}); export const motorDecelerationStatus = z.object({active: z.boolean(), stage: z.string(), distance_to_limit: z.number().nullable().optional(), original_speed: z.number().int().gte(0).lte(100).nullable().optional(), current_safe_speed: z.number().int().gte(0).lte(100).nullable().optional(), reason: z.string().nullable().optional()}); export const cablePositionCalibrationStatus = z.object({active: z.boolean()}); export const blocksMovementSchema = z.object({directions: z.array(z.string()).optional(), origins: z.array(z.string()).optional()}); export const ignoreSafeguardViolationsSchema = z.object({cable_position_below_zero_point: z.boolean().optional(), cable_position_exceeds_max_length: z.boolean().optional(), no_cable_movement_detected: z.boolean().optional()}); export const operationContextSchema = z.object({operation: z.string(), started_at: z.number(), blocks_movement: blocksMovementSchema.optional(), ignore_safeguard_violations: ignoreSafeguardViolationsSchema.optional(), suppresses_position_hold: z.boolean().optional(), expects_reed_switch_activity: z.boolean().optional(), owner: z.string().nullable().optional(), timeout_seconds: z.number().nullable().optional()}); export const activeOperationsSchema = z.object({deceleration: motorDecelerationStatus.nullable().optional(), cable_position_calibration: cablePositionCalibrationStatus.nullable().optional(), operation_context: operationContextSchema.nullable().optional()}); export const amlEnduraChargerStatusSchema = z.object({status: z.string(), state_value: z.number().int().gte(-1).lte(7)}); export const dockingState = z.enum(["idle", "docking_winding", "docking_wait_open", "docking_reversing", "docked", "undocking_winding", "undocking_wait_close", "undocking_wait_open", "undocking_completing"]); export const dockingPluginStatusSchema = z.object({state: dockingState, is_docked: z.boolean(), is_moving: z.boolean(), reed_switch_state: z.boolean().nullable().optional()}); export const powerSheaveMotorTelemetry = z.object({current: z.number().nullable().optional(), voltage: z.number().nullable().optional(), speed: z.number().nullable().optional(), position: z.number().nullable().optional(), encoder_count: z.number().int().nullable().optional(), temperature: z.number().nullable().optional(), last_update: z.number().nullable().optional()}); export const powerSheaveStatusSchema = z.object({enabled: z.boolean(), motor_connected: z.boolean(), control_mode: z.string(), speed_differential_percentage: z.number(), reverse_direction: z.boolean(), telemetry: powerSheaveMotorTelemetry.nullable().optional()}); export const heartbeatResponse = z.object({reel_state: reelStateSchema, active_operations: activeOperationsSchema.optional(), plugins: z.record(z.string(), z.union([amlEnduraChargerStatusSchema, dockingPluginStatusSchema, powerSheaveStatusSchema, z.record(z.string(), z.unknown())])).optional(), timestamp: z.string()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Get Real-time Reel System Status * * GET /api/v1/reel/heartbeat */ export const getApiApiV1ReelHeartbeat = async (options: GetApiApiV1ReelHeartbeatOptions) => { const {baseUrl, fetch: fetchFn = fetch, OAuth2PasswordBearer} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (OAuth2PasswordBearer !== undefined) headers.set('authorization', `Bearer ${OAuth2PasswordBearer}`) const url = `${baseUrl.replace(/\/+$/, '')}/api/v1/reel/heartbeat${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(heartbeatResponse, await res.json()) };