feat(vesc): integrate native calibration diagnostics and configuration archives
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
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>;
|
||||
}
|
||||
Reference in New Issue
Block a user