Add support for Addon Motor 5 in Gluetex machine, including state management and UI integration

This commit is contained in:
Christian committed 2026-02-16 16:01:35 +01:00
1 parent a5da005f7b
commit d1f07e8d01
10 files changed
+222 -1

No files matched your search

+21
View File
@@ -866,6 +866,27 @@ export const gluetex: MachineProperties = {
},
],
},
{
role: 11,
role_label: "Addon Motor 5",
allowed_devices: [
{
vendor_id: 2,
product_id: 0x1b773052,
revision: 0x1a0000,
},
{
vendor_id: 2,
product_id: 0x1b773052,
revision: 0x190000,
},
{
vendor_id: 2,
product_id: 0x1b773052,
revision: 0x10001e,
},
],
},
],
};
@@ -27,6 +27,7 @@ export function GluetexAddonsPage() {
isDisabled,
setStepper3Mode,
setStepper4Mode,
setStepper5Mode,
setHeatingMode,
temperature1,
temperature2,
@@ -43,6 +44,8 @@ export function GluetexAddonsPage() {
setStepper3Slave,
setStepper4Master,
setStepper4Slave,
setStepper5Master,
setStepper5Slave,
} = useGluetex();
// Calculate max speed based on gear ratio (same as main puller)
@@ -98,6 +101,28 @@ export function GluetexAddonsPage() {
}}
/>
</Label>
<Label label="Stepper 5">
<SelectionGroup<StepperMode>
value={state?.stepper_state?.stepper5_mode}
disabled={isDisabled}
loading={isLoading}
onChange={setStepper5Mode}
orientation="horizontal"
className="grid grid-cols-2 gap-2"
options={{
Standby: {
children: "Standby",
icon: "lu:Power",
isActiveClassName: "bg-green-600",
},
Run: {
children: "Run",
icon: "lu:Play",
isActiveClassName: "bg-green-600",
},
}}
/>
</Label>
</ControlCard>
<ControlCard className="bg-red" title="Heating Mode">
@@ -320,6 +345,17 @@ export function GluetexAddonsPage() {
}}
/>
</Label>
<Label label="Stepper 5 Ratio">
<RatioInput
master={state?.motor_ratios_state?.stepper5_master}
slave={state?.motor_ratios_state?.stepper5_slave}
title="Stepper 5 Motor Ratio"
onRatioChange={(master, slave) => {
setStepper5Master(master);
setStepper5Slave(slave);
}}
/>
</Label>
</ControlCard>
<ControlCard title="Addon Tension Arm">
@@ -200,7 +200,7 @@ export function GluetexOverviewPage() {
{/* Addon Motors Status Grid */}
<div>
<h3 className="mb-2 text-lg font-semibold">Addon Motors</h3>
<div className="grid grid-cols-2 gap-2">
<div className="grid grid-cols-3 gap-2">
<div className="flex flex-col items-center gap-2 rounded-lg border p-3">
<span className="text-sm font-medium">Stepper 3</span>
<div
@@ -225,6 +225,18 @@ export function GluetexOverviewPage() {
{state?.stepper_state?.stepper4_mode || "Standby"}
</span>
</div>
<div className="flex flex-col items-center gap-2 rounded-lg border p-3">
<span className="text-sm font-medium">Stepper 5</span>
<div
className={cn(
"h-8 w-8 rounded-full",
getStepperColor(state?.stepper_state?.stepper5_mode),
)}
/>
<span className="text-xs">
{state?.stepper_state?.stepper5_mode || "Standby"}
</span>
</div>
</div>
</div>
</div>
@@ -305,6 +305,7 @@ export const stateEventDataSchema = z.object({
connected_machine_state: connectedMachineStateSchema,
addon_motor_3_state: addonMotorStateSchema,
addon_motor_4_state: addonMotorStateSchema,
addon_motor_5_state: addonMotorStateSchema,
slave_puller_state: slavePullerStateSchema,
addon_tension_arm_state: tensionArmStateSchema,
tension_arm_monitor_state: tensionArmMonitorStateSchema,
@@ -331,11 +332,14 @@ export type MotorRatiosState = {
stepper3_slave: number;
stepper4_master: number;
stepper4_slave: number;
stepper5_master: number;
stepper5_slave: number;
};
export type StepperState = {
stepper3_mode: StepperMode;
stepper4_mode: StepperMode;
stepper5_mode: StepperMode;
};
/**
@@ -344,12 +348,15 @@ export type StepperState = {
function getMotorRatiosFromBackend(
motor3: z.infer<typeof addonMotorStateSchema>,
motor4: z.infer<typeof addonMotorStateSchema>,
motor5: z.infer<typeof addonMotorStateSchema>,
): MotorRatiosState {
return {
stepper3_master: motor3.master_ratio,
stepper3_slave: motor3.slave_ratio,
stepper4_master: motor4.master_ratio,
stepper4_slave: motor4.slave_ratio,
stepper5_master: motor5.master_ratio,
stepper5_slave: motor5.slave_ratio,
};
}
@@ -371,6 +378,15 @@ function getStepper4ModeFromBackend(
return motor4.enabled ? "Run" : "Standby";
}
/**
* Helper to determine stepper5 mode from backend enabled state
*/
function getStepper5ModeFromBackend(
motor5: z.infer<typeof addonMotorStateSchema>,
): StepperMode {
return motor5.enabled ? "Run" : "Standby";
}
/**
* Helper to determine heating mode from backend enabled state
*/
@@ -516,10 +532,13 @@ const DEFAULT_ADDON_STATE = {
stepper3_slave: 1.0,
stepper4_master: 1.0,
stepper4_slave: 1.0,
stepper5_master: 1.0,
stepper5_slave: 1.0,
},
stepper_state: {
stepper3_mode: "Standby" as StepperMode,
stepper4_mode: "Standby" as StepperMode,
stepper5_mode: "Standby" as StepperMode,
},
heating_state: {
heating_mode: "Standby" as HeatingMode,
@@ -607,6 +626,7 @@ export function gluetexMessageHandler(
const motorRatiosState = getMotorRatiosFromBackend(
stateEvent.data.addon_motor_3_state,
stateEvent.data.addon_motor_4_state,
stateEvent.data.addon_motor_5_state,
);
// Derive stepper modes from backend enabled state
@@ -616,6 +636,9 @@ export function gluetexMessageHandler(
const stepper4Mode = getStepper4ModeFromBackend(
stateEvent.data.addon_motor_4_state,
);
const stepper5Mode = getStepper5ModeFromBackend(
stateEvent.data.addon_motor_5_state,
);
// Derive heating mode from backend enabled state
const heatingMode = getHeatingModeFromBackend(
@@ -631,6 +654,7 @@ export function gluetexMessageHandler(
stepper_state: {
stepper3_mode: stepper3Mode,
stepper4_mode: stepper4Mode,
stepper5_mode: stepper5Mode,
},
heating_state: {
heating_mode: heatingMode,
@@ -218,6 +218,17 @@ export function useGluetex() {
z.object({ SetAddonMotor4SlaveRatio: z.number() }),
);
// Addon Motor 5 mutations
const { request: requestAddonMotor5SetEnabled } = useMachineMutation(
z.object({ SetAddonMotor5Enabled: z.boolean() }),
);
const { request: requestAddonMotor5SetMasterRatio } = useMachineMutation(
z.object({ SetAddonMotor5MasterRatio: z.number() }),
);
const { request: requestAddonMotor5SetSlaveRatio } = useMachineMutation(
z.object({ SetAddonMotor5SlaveRatio: z.number() }),
);
// Slave Puller mutations
const { request: requestSlavePullerSetEnabled } = useMachineMutation(
z.object({ SetSlavePullerEnabled: z.boolean() }),
@@ -818,6 +829,47 @@ export function useGluetex() {
);
};
const setStepper5Mode = (mode: StepperMode) => {
const enabled = mode === "Run";
updateStateOptimistically(
(current) => {
current.data.stepper_state.stepper5_mode = mode;
},
() => {
requestAddonMotor5SetEnabled({
machine_identification_unique: machineIdentification,
data: { SetAddonMotor5Enabled: enabled },
});
},
);
};
const setStepper5Master = (value: number) => {
updateStateOptimistically(
(current) => {
current.data.motor_ratios_state.stepper5_master = value;
},
() =>
requestAddonMotor5SetMasterRatio({
machine_identification_unique: machineIdentification,
data: { SetAddonMotor5MasterRatio: value },
}),
);
};
const setStepper5Slave = (value: number) => {
updateStateOptimistically(
(current) => {
current.data.motor_ratios_state.stepper5_slave = value;
},
() =>
requestAddonMotor5SetSlaveRatio({
machine_identification_unique: machineIdentification,
data: { SetAddonMotor5SlaveRatio: value },
}),
);
};
// ========== Slave Puller Functions ==========
const setSlavePullerEnabled = (enabled: boolean) => {
@@ -1194,6 +1246,7 @@ export function useGluetex() {
// Addon action functions (local only)
setStepper3Mode,
setStepper4Mode,
setStepper5Mode,
setHeatingMode,
setTemperature1Min,
setTemperature1Max,
@@ -1203,5 +1256,7 @@ export function useGluetex() {
setStepper3Slave,
setStepper4Master,
setStepper4Slave,
setStepper5Master,
setStepper5Slave,
};
}
+1
View File
@@ -21,6 +21,7 @@ impl MachineAct for Gluetex {
// sync addon motor speeds
self.sync_addon_motor_3_speed(now);
self.sync_addon_motor_4_speed(now);
self.sync_addon_motor_5_speed(now);
// sync the traverse speed
self.sync_traverse_speed();
+17
View File
@@ -139,10 +139,13 @@ pub enum Mutation {
// Addon Motors
SetAddonMotor3Enabled(bool),
SetAddonMotor4Enabled(bool),
SetAddonMotor5Enabled(bool),
SetAddonMotor3MasterRatio(f64),
SetAddonMotor3SlaveRatio(f64),
SetAddonMotor4MasterRatio(f64),
SetAddonMotor4SlaveRatio(f64),
SetAddonMotor5MasterRatio(f64),
SetAddonMotor5SlaveRatio(f64),
// Slave Puller
SetSlavePullerEnabled(bool),
@@ -235,6 +238,8 @@ pub struct StateEvent {
pub addon_motor_3_state: AddonMotorState,
/// addon motor 4 state
pub addon_motor_4_state: AddonMotorState,
/// addon motor 5 state
pub addon_motor_5_state: AddonMotorState,
/// slave puller state
pub slave_puller_state: SlavePullerState,
/// addon tension arm state
@@ -563,6 +568,18 @@ impl MachineApi for Gluetex {
self.addon_motor_4_controller.set_slave_ratio(ratio);
self.emit_state();
}
Mutation::SetAddonMotor5Enabled(enabled) => {
self.addon_motor_5_controller.set_enabled(enabled);
self.emit_state();
}
Mutation::SetAddonMotor5MasterRatio(ratio) => {
self.addon_motor_5_controller.set_master_ratio(ratio);
self.emit_state();
}
Mutation::SetAddonMotor5SlaveRatio(ratio) => {
self.addon_motor_5_controller.set_slave_ratio(ratio);
self.emit_state();
}
Mutation::SetSlavePullerEnabled(enabled) => {
self.slave_puller_user_enabled = enabled;
// Re-apply the current mode to update the controller state
+5
View File
@@ -450,6 +450,11 @@ impl Gluetex {
master_ratio: self.addon_motor_4_controller.get_master_ratio(),
slave_ratio: self.addon_motor_4_controller.get_slave_ratio(),
},
addon_motor_5_state: api::AddonMotorState {
enabled: self.addon_motor_5_controller.is_enabled(),
master_ratio: self.addon_motor_5_controller.get_master_ratio(),
slave_ratio: self.addon_motor_5_controller.get_slave_ratio(),
},
slave_puller_state: api::SlavePullerState {
enabled: self.slave_puller_user_enabled,
forward: self.slave_puller_speed_controller.get_forward(),
+10
View File
@@ -144,6 +144,7 @@ pub struct Gluetex {
// addon motors
pub addon_motor_3: StepperVelocityEL70x1,
pub addon_motor_4: StepperVelocityEL70x1,
pub addon_motor_5: StepperVelocityEL70x1,
// controllers
pub traverse_controller: TraverseController,
@@ -152,6 +153,7 @@ pub struct Gluetex {
// addon motor controllers
pub addon_motor_3_controller: AddonMotorController,
pub addon_motor_4_controller: AddonMotorController,
pub addon_motor_5_controller: AddonMotorController,
// temperature controllers (PID-controlled heaters with temperature sensors)
pub temperature_controller_1: TemperatureController,
@@ -424,6 +426,14 @@ impl Gluetex {
.sync_motor_speed(&mut self.addon_motor_4, puller_angular_velocity);
}
/// Sync addon motor 5 speed based on puller angular velocity and ratio
/// called by `act`
pub fn sync_addon_motor_5_speed(&mut self, t: Instant) {
let puller_angular_velocity = self.puller_speed_controller.calc_angular_velocity(t);
self.addon_motor_5_controller
.sync_motor_speed(&mut self.addon_motor_5, puller_angular_velocity);
}
/// Sync slave puller speed based on master puller speed and slave tension arm
/// called by `act`
pub fn sync_slave_puller_speed(&mut self, t: Instant) {
+40
View File
@@ -333,6 +333,40 @@ impl MachineNewTrait for Gluetex {
device.0
};
// Role 11: Addon Motor 5 EL7031-0030
let el7031_addon5 = {
let device = get_ethercat_device::<EL7031_0030>(
hardware,
params,
11,
vec![EL7031_0030_IDENTITY_A, EL7031_IDENTITY_A, EL7031_IDENTITY_B],
)
.await?;
let el7031_0030_config = EL7031_0030Configuration {
stm_features: el7031_0030::coe::StmFeatures {
operation_mode: EL70x1OperationMode::DirectVelocity,
speed_range: shared_config::el70x1::EL70x1SpeedRange::Steps1000,
..Default::default()
},
stm_motor: StmMotorConfiguration {
max_current: 1500,
..Default::default()
},
pdo_assignment: EL7031_0030PredefinedPdoAssignment::VelocityControlCompact,
..Default::default()
};
device
.0
.write()
.await
.write_config(&device.1, &el7031_0030_config)
.await?;
device.0
};
// Digital outputs for SSR control (24V to external SSRs for 60W heaters)
let heater_ssr_1 = DigitalOutput::new(el2008.clone(), EL2008Port::DO1);
let heater_ssr_2 = DigitalOutput::new(el2008.clone(), EL2008Port::DO2);
@@ -468,6 +502,10 @@ impl MachineNewTrait for Gluetex {
el7031_addon4.clone(),
EL7031_0030StepperPort::STM1,
),
addon_motor_5: StepperVelocityEL70x1::new(
el7031_addon5,
EL7031_0030StepperPort::STM1,
),
addon_tension_arm: TensionArm::new(AnalogInput::new(
el7031_addon4,
EL7031_0030AnalogInputPort::AI1,
@@ -476,6 +514,8 @@ impl MachineNewTrait for Gluetex {
super::addon_motor_controller::AddonMotorController::new(200),
addon_motor_4_controller:
super::addon_motor_controller::AddonMotorController::new(200),
addon_motor_5_controller:
super::addon_motor_controller::AddonMotorController::new(200),
tension_arm: TensionArm::new(AnalogInput::new(
el7031_0030,
EL7031_0030AnalogInputPort::AI1,