70 lines
5.0 KiB
TypeScript
70 lines
5.0 KiB
TypeScript
import type {Sensor} from '../../../../packages/sensor-ui/src/contracts';
|
||
|
||
export interface VescIdentity {uuid:string;hardware:string;version:string;test_firmware:number|null;hardware_type:number|null}
|
||
export interface VescTelemetry {observed_at:string;values:Record<string,number|boolean>}
|
||
export interface DriveProfile {layout:null|'1x1'|'2x2';revision:number;bindings:Record<string,{device_id:string;uuid:string}>}
|
||
export function drivePositions(layout:DriveProfile['layout']):Record<string,string> {
|
||
return layout==='2x2'
|
||
? {'left.1':'Левый передний','left.2':'Левый задний','right.1':'Правый передний','right.2':'Правый задний'}
|
||
: {'left.1':'Левый','right.1':'Правый'};
|
||
}
|
||
export interface VescStatus {
|
||
board_settings_supported?:boolean;
|
||
group_test_supported?:boolean;
|
||
link_check_supported?:boolean;
|
||
foc_calibration?:{min_power_loss_w:number;max_power_loss_w:number;interruptible:boolean};
|
||
speed_limits?:{min_erpm:number;max_erpm:number;duration_basis:string;reverse_supported?:boolean;standstill_confirmation_required?:boolean};
|
||
hall_measurement?:{current_a:number;interruptible:boolean;standstill_confirmation_required?:boolean};
|
||
drive_profile?:DriveProfile;
|
||
test_limits?:{min_current_a:number;max_current_a:number;min_duration_s:number;max_duration_s:number;current_ramp_a_per_s?:number;continuous_current?:boolean;max_erpm?:number;max_duty?:number;stall_current_a?:number;stall_timeout_s?:number};
|
||
identity:VescIdentity|null; readable:boolean; message:string|null; telemetry:VescTelemetry|null;
|
||
backup:{observed_at:string;operation_id:string;configs:Record<string,{bytes:number;sha256:string}>}|null;
|
||
}
|
||
|
||
export function driveTestIds(profile:DriveProfile|undefined,selected:string):string[] {
|
||
if(!profile?.layout)return [];
|
||
const slots=Object.keys(drivePositions(profile.layout));
|
||
if(Object.keys(profile.bindings).length!==slots.length||slots.some(slot=>!profile.bindings[slot]))return [];
|
||
const ids=slots.map(slot=>profile.bindings[slot].device_id);
|
||
return new Set(ids).size===ids.length&&ids.includes(selected)?ids:[];
|
||
}
|
||
|
||
export function speedInput(speed:string,limits:VescStatus['speed_limits']) {
|
||
const erpm=Number(speed);
|
||
const error=!limits?'Для удержания скорости требуется обновление профиля VESC на борту.':
|
||
speed.trim()===''||!Number.isFinite(erpm)?'Введите скорость.':
|
||
erpm<limits.min_erpm||erpm>limits.max_erpm?`Скорость должна быть от ${limits.min_erpm} до ${limits.max_erpm} ERPM.`:null;
|
||
return {erpm,error};
|
||
}
|
||
|
||
export function rotationResult(value:{outcome?:string;rotation_s?:number;release_confirmed?:boolean;limits_restored?:boolean}) {
|
||
const time=(value.rotation_s??0).toLocaleString('ru-RU',{maximumFractionDigits:1});
|
||
const outcome=value.outcome==='duration'?'Заданное время вращения набрано.':value.outcome==='stopped'?'Проверка остановлена.':String(value.outcome);
|
||
return `${outcome} Вращение на заданной скорости по данным VESC: ${time} с. ${value.release_confirmed?'Снятие тока подтверждено.':'Снятие тока не подтверждено.'}${value.limits_restored?' Исходные токовые пределы восстановлены.':''}`;
|
||
}
|
||
export function vescStatus(device:Sensor):VescStatus {
|
||
return {identity:null,readable:false,message:null,telemetry:null,backup:null,...device.vesc_status} as VescStatus;
|
||
}
|
||
export function vescLabel(device:Sensor,fresh:boolean):{label:string;tone:'neutral'|'success'|'warning'|'danger'} {
|
||
const status=vescStatus(device);
|
||
if(!fresh||!device.online)return {label:'Нет связи',tone:'neutral'};
|
||
if(!device.prepared)return {label:'Требуется подготовка',tone:'neutral'};
|
||
if(!status.identity)return {label:'Не определён',tone:'warning'};
|
||
if(!status.readable)return {label:'Прошивка не поддерживается',tone:'warning'};
|
||
return {label:'Готов к чтению',tone:'success'};
|
||
}
|
||
|
||
export function testInput(current:string,duration:string,limits:VescStatus['test_limits']) {
|
||
const currentA=Number(current),durationS=Number(duration);
|
||
const number=(value:number)=>value.toLocaleString('ru-RU');
|
||
const currentError=!limits?null:current.trim()===''||!Number.isFinite(currentA)
|
||
? 'Введите ток мотора.'
|
||
: currentA<limits.min_current_a||currentA>limits.max_current_a
|
||
? `Ток должен быть от ${number(limits.min_current_a)} до ${number(limits.max_current_a)} А.`:null;
|
||
const durationError=!limits?null:duration.trim()===''||!Number.isFinite(durationS)
|
||
? 'Введите длительность проверки.'
|
||
: durationS<limits.min_duration_s||durationS>limits.max_duration_s
|
||
? `Длительность должна быть от ${number(limits.min_duration_s)} до ${number(limits.max_duration_s)} с.`:null;
|
||
return {currentA,durationS,currentError,durationError,valid:!!limits&&!currentError&&!durationError};
|
||
}
|