81 lines
6.2 KiB
TypeScript
81 lines
6.2 KiB
TypeScript
import {useEffect,useRef,useState} from 'react';
|
||
import {Button,LoadingRegion,ResourceList,ResourceRow,SettingsCard,StatusBadge} from '@nodedc/ui-react';
|
||
import type {SensorDetailProps} from '../../../../packages/sensor-ui/src/extensions';
|
||
import {perform} from '../../../../packages/sensor-ui/src/contracts';
|
||
import {vescLabel,vescStatus,type VescTelemetry} from './model';
|
||
import {VescBackups} from './VescBackups';
|
||
import {VescMotor} from './VescMotor';
|
||
import {VescHall} from './VescHall';
|
||
import {VescCalibration} from './VescCalibration';
|
||
import {VescLink} from './VescLink';
|
||
|
||
const fields=[['input_voltage_v','Напряжение питания','В'],['input_current_a','Ток питания','А'],
|
||
['motor_current_a','Ток мотора','А'],['erpm','Электрические обороты','ERPM'],['duty','Заполнение PWM',''],
|
||
['mos_temperature_c','Температура контроллера','°C'],['motor_temperature_c','Температура мотора','°C'],
|
||
['fault_code','Код ошибки',''],['can_id','CAN ID',''],['timeout','Тайм-аут управления',''],
|
||
['kill_switch','Вход аварийного останова','']] as const;
|
||
|
||
export function VescDetail(props:SensorDetailProps){
|
||
const {device,transport,enabled,back,refresh,failure}=props;
|
||
const status=vescStatus(device);const label=vescLabel(device,enabled);
|
||
const [telemetry,setTelemetry]=useState<VescTelemetry|null>(status.telemetry);
|
||
const [pending,setPending]=useState<string|null>(null);
|
||
const [saved,setSaved]=useState<string|null>(null);
|
||
const [motorBusy,setMotorBusy]=useState(false);
|
||
const [hallBusy,setHallBusy]=useState(false);
|
||
const [calibrationBusy,setCalibrationBusy]=useState(false);
|
||
const [linkBusy,setLinkBusy]=useState(false);
|
||
const poweredBusy=motorBusy||hallBusy||calibrationBusy||linkBusy;
|
||
const running=useRef(false);const mounted=useRef(true);
|
||
const available=enabled&&device.online&&device.verified&&status.readable;
|
||
useEffect(()=>{mounted.current=true;return()=>{mounted.current=false;};},[]);
|
||
async function read(action:'vesc.telemetry.read'|'vesc.config.backup'){
|
||
if(running.current||poweredBusy||!available)return;
|
||
running.current=true;setPending(action);failure(null);
|
||
try{
|
||
const result=await perform<Record<string,unknown>>(transport,device,action);
|
||
if(!mounted.current)return;
|
||
if(action==='vesc.telemetry.read')setTelemetry(result as unknown as VescTelemetry);
|
||
else {
|
||
setSaved(String(result.observed_at));
|
||
}
|
||
await refresh();
|
||
}catch(error){if(mounted.current)failure(error);}
|
||
finally{running.current=false;if(mounted.current)setPending(null);}
|
||
}
|
||
const backupAt=saved??status.backup?.observed_at;
|
||
return <div className="sensor-content">
|
||
<div><Button onClick={back}>К устройствам</Button></div>
|
||
<SettingsCard title={`${device.name} · Настройка VESC`} description={`${device.model} · ${device.connection_label??'USB'}`}
|
||
actions={<StatusBadge tone={label.tone}>{label.label}</StatusBadge>}>
|
||
{!enabled||!device.online?<p>Нет свежей связи с контроллером.</p>:status.message?<p>{status.message}</p>:null}
|
||
{status.identity?<ResourceList aria-label="Контроллер VESC">
|
||
<li><ResourceRow title="Прошивка" description={status.identity.version}/></li>
|
||
<li><ResourceRow title="Аппаратная версия" description={status.identity.hardware}/></li>
|
||
<li><ResourceRow title="UUID" description={status.identity.uuid.toUpperCase()}/></li>
|
||
{status.identity.test_firmware!==null&&status.identity.test_firmware>0&&<li><ResourceRow title="Тестовая прошивка" description={String(status.identity.test_firmware)}/></li>}
|
||
</ResourceList>:<p>Аппаратный идентификатор ещё не подтверждён.</p>}
|
||
</SettingsCard>
|
||
<SettingsCard title="Показания контроллера" description={telemetry?`Снимок: ${new Date(telemetry.observed_at).toLocaleString()}`:'Получите текущие значения и код ошибки.'}
|
||
actions={<Button disabled={!available||pending!==null||poweredBusy} loading={pending==='vesc.telemetry.read'} onClick={()=>void read('vesc.telemetry.read')}>Обновить показания</Button>}>
|
||
<LoadingRegion loading={pending==='vesc.telemetry.read'&&!telemetry} label="Чтение показаний VESC">
|
||
{telemetry?<ResourceList aria-label="Показания VESC">{fields.map(([key,title,unit])=>{
|
||
const value=telemetry.values[key];if(value===undefined)return null;
|
||
return <li key={key}><ResourceRow title={title} description={typeof value==='boolean'?(value?'Активен':'Не активен'):`${Number(value).toLocaleString(undefined,{maximumFractionDigits:3})}${unit?' '+unit:''}`}/></li>;
|
||
})}</ResourceList>:<p>Показания ещё не прочитаны.</p>}
|
||
</LoadingRegion>
|
||
<p>ERPM — электрические обороты. Обороты вала зависят от числа пар полюсов мотора.</p>
|
||
</SettingsCard>
|
||
<SettingsCard title="Резервная копия конфигурации" description="Сохраните текущие параметры мотора и входов новой версией."
|
||
actions={<Button disabled={!available||pending!==null||poweredBusy} loading={pending==='vesc.config.backup'} onClick={()=>void read('vesc.config.backup')}>Сохранить версию</Button>}>
|
||
{backupAt&&<p>Последняя копия: {new Date(backupAt).toLocaleString()}</p>}
|
||
<p>Копия привязана к UUID и прошивке. Версии до и после калибровки остаются в истории.</p>
|
||
</SettingsCard>
|
||
<VescLink {...props} blocked={motorBusy||hallBusy||calibrationBusy||pending!==null} onBusyChange={setLinkBusy}/>
|
||
<VescCalibration {...props} blocked={linkBusy||motorBusy||hallBusy||pending!==null} onBusyChange={setCalibrationBusy}/>
|
||
<VescHall {...props} blocked={linkBusy||motorBusy||calibrationBusy||pending!==null} onBusyChange={setHallBusy}/>
|
||
<VescMotor {...props} blocked={linkBusy||hallBusy||calibrationBusy||pending!==null} onBusyChange={setMotorBusy}/>
|
||
<VescBackups deviceId={device.id} archive={transport.configurationArchive} revision={backupAt??null}/>
|
||
</div>;
|
||
}
|