Add addon motor support with state management and control functions, remove cutting unit
This commit is contained in:
1 parent
315ed1b31c
commit
eb085fa80c
9 files changed
+530
-72
No files matched your search
@@ -25,9 +25,8 @@ export function GluetexAddonsPage() {
|
||||
defaultState,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
setStepper2Mode,
|
||||
setStepper34Mode,
|
||||
setCuttingUnitMode,
|
||||
setStepper3Mode,
|
||||
setStepper4Mode,
|
||||
setHeatingMode,
|
||||
temperature1,
|
||||
temperature2,
|
||||
@@ -53,12 +52,12 @@ export function GluetexAddonsPage() {
|
||||
<Page>
|
||||
<ControlGrid>
|
||||
<ControlCard className="bg-red" title="Motors">
|
||||
<Label label="Stepper 2">
|
||||
<Label label="Stepper 3">
|
||||
<SelectionGroup<StepperMode>
|
||||
value={state?.stepper_state?.stepper2_mode}
|
||||
value={state?.stepper_state?.stepper3_mode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
onChange={setStepper2Mode}
|
||||
onChange={setStepper3Mode}
|
||||
orientation="horizontal"
|
||||
className="grid grid-cols-2 gap-2"
|
||||
options={{
|
||||
@@ -75,34 +74,12 @@ export function GluetexAddonsPage() {
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Stepper 3&4">
|
||||
<Label label="Stepper 4">
|
||||
<SelectionGroup<StepperMode>
|
||||
value={state?.stepper_state?.stepper34_mode}
|
||||
value={state?.stepper_state?.stepper4_mode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
onChange={setStepper34Mode}
|
||||
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>
|
||||
<Label label="Cutting Unit">
|
||||
<SelectionGroup<StepperMode>
|
||||
value={state?.stepper_state?.cutting_unit_mode}
|
||||
disabled={isDisabled}
|
||||
loading={isLoading}
|
||||
onChange={setCuttingUnitMode}
|
||||
onChange={setStepper4Mode}
|
||||
orientation="horizontal"
|
||||
className="grid grid-cols-2 gap-2"
|
||||
options={{
|
||||
|
||||
@@ -213,6 +213,15 @@ export const heatingStatesSchema = z.object({
|
||||
zone_6: heatingStateSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* Addon motor state schema (from backend)
|
||||
*/
|
||||
export const addonMotorStateSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
master_ratio: z.number(),
|
||||
slave_ratio: z.number(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Consolidated state event schema (state changes only) - from backend
|
||||
*/
|
||||
@@ -226,6 +235,8 @@ export const stateEventDataSchema = z.object({
|
||||
spool_automatic_action_state: spoolAutomaticActionStateSchema,
|
||||
heating_states: heatingStatesSchema,
|
||||
connected_machine_state: connectedMachineStateSchema,
|
||||
addon_motor_3_state: addonMotorStateSchema,
|
||||
addon_motor_4_state: addonMotorStateSchema,
|
||||
});
|
||||
|
||||
// ========== Event Schemas with Wrappers ==========
|
||||
@@ -253,11 +264,34 @@ export type MotorRatiosState = {
|
||||
};
|
||||
|
||||
export type StepperState = {
|
||||
stepper2_mode: StepperMode;
|
||||
stepper34_mode: StepperMode;
|
||||
cutting_unit_mode: StepperMode;
|
||||
stepper3_mode: StepperMode;
|
||||
stepper4_mode: StepperMode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to convert backend addon motor state to frontend MotorRatiosState
|
||||
*/
|
||||
function getMotorRatiosFromBackend(
|
||||
motor3: z.infer<typeof addonMotorStateSchema>,
|
||||
motor4: 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,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to determine stepper4 mode from backend enabled state
|
||||
*/
|
||||
function getStepper4ModeFromBackend(
|
||||
motor4: z.infer<typeof addonMotorStateSchema>,
|
||||
): StepperMode {
|
||||
return motor4.enabled ? "Run" : "Standby";
|
||||
}
|
||||
|
||||
export type HeatingState = {
|
||||
heating_mode: HeatingMode;
|
||||
};
|
||||
@@ -388,9 +422,8 @@ const DEFAULT_ADDON_STATE = {
|
||||
stepper4_slave: 1.0,
|
||||
},
|
||||
stepper_state: {
|
||||
stepper2_mode: "Standby" as StepperMode,
|
||||
stepper34_mode: "Standby" as StepperMode,
|
||||
cutting_unit_mode: "Standby" as StepperMode,
|
||||
stepper3_mode: "Standby" as StepperMode,
|
||||
stepper4_mode: "Standby" as StepperMode,
|
||||
},
|
||||
heating_state: {
|
||||
heating_mode: "Standby" as HeatingMode,
|
||||
@@ -472,19 +505,32 @@ export function gluetexMessageHandler(
|
||||
const stateEvent = stateEventSchema.parse(event);
|
||||
|
||||
updateStore((state) => {
|
||||
// Extend backend state with addon state
|
||||
// Derive motor ratios from backend addon motor state
|
||||
const motorRatiosState = getMotorRatiosFromBackend(
|
||||
stateEvent.data.addon_motor_3_state,
|
||||
stateEvent.data.addon_motor_4_state,
|
||||
);
|
||||
|
||||
// Derive stepper4 mode from backend enabled state
|
||||
const stepper4Mode = getStepper4ModeFromBackend(
|
||||
stateEvent.data.addon_motor_4_state,
|
||||
);
|
||||
|
||||
// Extend backend state with addon state (some local, some derived from backend)
|
||||
const extendedData: ExtendedStateEventData = {
|
||||
...stateEvent.data,
|
||||
// Preserve existing addon state or use defaults
|
||||
// Preserve existing local-only addon state
|
||||
slave_puller_state:
|
||||
state.state?.data.slave_puller_state ||
|
||||
DEFAULT_ADDON_STATE.slave_puller_state,
|
||||
motor_ratios_state:
|
||||
state.state?.data.motor_ratios_state ||
|
||||
DEFAULT_ADDON_STATE.motor_ratios_state,
|
||||
stepper_state:
|
||||
state.state?.data.stepper_state ||
|
||||
DEFAULT_ADDON_STATE.stepper_state,
|
||||
// Derive from backend addon motor state
|
||||
motor_ratios_state: motorRatiosState,
|
||||
stepper_state: {
|
||||
stepper3_mode:
|
||||
state.state?.data.stepper_state.stepper3_mode ||
|
||||
DEFAULT_ADDON_STATE.stepper_state.stepper3_mode,
|
||||
stepper4_mode: stepper4Mode,
|
||||
},
|
||||
heating_state:
|
||||
state.state?.data.heating_state ||
|
||||
DEFAULT_ADDON_STATE.heating_state,
|
||||
|
||||
@@ -178,6 +178,28 @@ export function useGluetex() {
|
||||
}),
|
||||
);
|
||||
|
||||
// Addon Motor 3 mutations
|
||||
const { request: requestAddonMotor3SetEnabled } = useMachineMutation(
|
||||
z.object({ SetAddonMotor3Enabled: z.boolean() }),
|
||||
);
|
||||
const { request: requestAddonMotor3SetMasterRatio } = useMachineMutation(
|
||||
z.object({ SetAddonMotor3MasterRatio: z.number() }),
|
||||
);
|
||||
const { request: requestAddonMotor3SetSlaveRatio } = useMachineMutation(
|
||||
z.object({ SetAddonMotor3SlaveRatio: z.number() }),
|
||||
);
|
||||
|
||||
// Addon Motor 4 mutations
|
||||
const { request: requestAddonMotor4SetEnabled } = useMachineMutation(
|
||||
z.object({ SetAddonMotor4Enabled: z.boolean() }),
|
||||
);
|
||||
const { request: requestAddonMotor4SetMasterRatio } = useMachineMutation(
|
||||
z.object({ SetAddonMotor4MasterRatio: z.number() }),
|
||||
);
|
||||
const { request: requestAddonMotor4SetSlaveRatio } = useMachineMutation(
|
||||
z.object({ SetAddonMotor4SlaveRatio: z.number() }),
|
||||
);
|
||||
|
||||
// ========== Helper Functions ==========
|
||||
|
||||
// Helper function for optimistic updates using produce
|
||||
@@ -596,22 +618,25 @@ export function useGluetex() {
|
||||
|
||||
// ========== Action Functions (Addon Features - Local Only) ==========
|
||||
|
||||
const setStepper2Mode = (mode: StepperMode) => {
|
||||
const setStepper3Mode = (mode: StepperMode) => {
|
||||
updateStateLocally((current) => {
|
||||
current.data.stepper_state.stepper2_mode = mode;
|
||||
current.data.stepper_state.stepper3_mode = mode;
|
||||
});
|
||||
};
|
||||
|
||||
const setStepper34Mode = (mode: StepperMode) => {
|
||||
updateStateLocally((current) => {
|
||||
current.data.stepper_state.stepper34_mode = mode;
|
||||
});
|
||||
};
|
||||
|
||||
const setCuttingUnitMode = (mode: StepperMode) => {
|
||||
updateStateLocally((current) => {
|
||||
current.data.stepper_state.cutting_unit_mode = mode;
|
||||
});
|
||||
const setStepper4Mode = (mode: StepperMode) => {
|
||||
const enabled = mode === "Run";
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.stepper_state.stepper4_mode = mode;
|
||||
},
|
||||
() => {
|
||||
requestAddonMotor4SetEnabled({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetAddonMotor4Enabled: enabled },
|
||||
});
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const setHeatingMode = (mode: HeatingMode) => {
|
||||
@@ -645,27 +670,55 @@ export function useGluetex() {
|
||||
};
|
||||
|
||||
const setStepper3Master = (value: number) => {
|
||||
updateStateLocally((current) => {
|
||||
current.data.motor_ratios_state.stepper3_master = value;
|
||||
});
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.motor_ratios_state.stepper3_master = value;
|
||||
},
|
||||
() =>
|
||||
requestAddonMotor3SetMasterRatio({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetAddonMotor3MasterRatio: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setStepper3Slave = (value: number) => {
|
||||
updateStateLocally((current) => {
|
||||
current.data.motor_ratios_state.stepper3_slave = value;
|
||||
});
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.motor_ratios_state.stepper3_slave = value;
|
||||
},
|
||||
() =>
|
||||
requestAddonMotor3SetSlaveRatio({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetAddonMotor3SlaveRatio: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setStepper4Master = (value: number) => {
|
||||
updateStateLocally((current) => {
|
||||
current.data.motor_ratios_state.stepper4_master = value;
|
||||
});
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.motor_ratios_state.stepper4_master = value;
|
||||
},
|
||||
() =>
|
||||
requestAddonMotor4SetMasterRatio({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetAddonMotor4MasterRatio: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const setStepper4Slave = (value: number) => {
|
||||
updateStateLocally((current) => {
|
||||
current.data.motor_ratios_state.stepper4_slave = value;
|
||||
});
|
||||
updateStateOptimistically(
|
||||
(current) => {
|
||||
current.data.motor_ratios_state.stepper4_slave = value;
|
||||
},
|
||||
() =>
|
||||
requestAddonMotor4SetSlaveRatio({
|
||||
machine_identification_unique: machineIdentification,
|
||||
data: { SetAddonMotor4SlaveRatio: value },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// ========== Machine Filtering ==========
|
||||
@@ -767,9 +820,8 @@ export function useGluetex() {
|
||||
disconnectMachine,
|
||||
|
||||
// Addon action functions (local only)
|
||||
setStepper2Mode,
|
||||
setStepper34Mode,
|
||||
setCuttingUnitMode,
|
||||
setStepper3Mode,
|
||||
setStepper4Mode,
|
||||
setHeatingMode,
|
||||
setTemperature1Min,
|
||||
setTemperature1Max,
|
||||
|
||||
@@ -15,6 +15,10 @@ impl MachineAct for Gluetex {
|
||||
// sync the puller speed
|
||||
self.sync_puller_speed(now);
|
||||
|
||||
// sync addon motor speeds
|
||||
self.sync_addon_motor_3_speed(now);
|
||||
self.sync_addon_motor_4_speed(now);
|
||||
|
||||
// sync the traverse speed
|
||||
self.sync_traverse_speed();
|
||||
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
use control_core::converters::angular_step_converter::AngularStepConverter;
|
||||
use ethercat_hal::io::stepper_velocity_el70x1::StepperVelocityEL70x1;
|
||||
use units::angular_velocity::revolution_per_second;
|
||||
use units::f64::AngularVelocity;
|
||||
|
||||
/// Controller for addon motors that follow the puller speed with a configurable ratio
|
||||
#[derive(Debug)]
|
||||
pub struct AddonMotorController {
|
||||
/// Whether the motor is enabled (Run mode)
|
||||
enabled: bool,
|
||||
/// Master ratio value (e.g., 2 in "2:1")
|
||||
master_ratio: f64,
|
||||
/// Slave ratio value (e.g., 1 in "2:1")
|
||||
slave_ratio: f64,
|
||||
/// Converter for angular velocity to steps
|
||||
converter: AngularStepConverter,
|
||||
}
|
||||
|
||||
impl AddonMotorController {
|
||||
/// Create a new addon motor controller
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `steps_per_revolution` - Number of steps per revolution for the stepper motor
|
||||
pub fn new(steps_per_revolution: i16) -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
master_ratio: 1.0,
|
||||
slave_ratio: 1.0,
|
||||
converter: AngularStepConverter::new(steps_per_revolution),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the motor is enabled (Run mode)
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
|
||||
/// Get whether the motor is enabled
|
||||
pub const fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
/// Set the master ratio value
|
||||
///
|
||||
/// The master ratio represents the puller rotations in the ratio.
|
||||
/// For example, in a 2:1 ratio, the master is 2.
|
||||
pub fn set_master_ratio(&mut self, master: f64) {
|
||||
self.master_ratio = master.max(0.1); // Prevent division issues
|
||||
}
|
||||
|
||||
/// Set the slave ratio value
|
||||
///
|
||||
/// The slave ratio represents the motor rotations in the ratio.
|
||||
/// For example, in a 2:1 ratio, the slave is 1.
|
||||
pub fn set_slave_ratio(&mut self, slave: f64) {
|
||||
self.slave_ratio = slave.max(0.1); // Prevent division issues
|
||||
}
|
||||
|
||||
/// Get the master ratio value
|
||||
pub const fn get_master_ratio(&self) -> f64 {
|
||||
self.master_ratio
|
||||
}
|
||||
|
||||
/// Get the slave ratio value
|
||||
pub const fn get_slave_ratio(&self) -> f64 {
|
||||
self.slave_ratio
|
||||
}
|
||||
|
||||
/// Calculate the motor angular velocity based on puller angular velocity and ratio
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `puller_angular_velocity` - The current angular velocity of the puller
|
||||
///
|
||||
/// # Returns
|
||||
/// The angular velocity this motor should run at
|
||||
///
|
||||
/// # Example
|
||||
/// If puller is rotating at 10 rev/s and ratio is 2:1 (master=2, slave=1),
|
||||
/// then motor should rotate at 5 rev/s (10 * 1/2)
|
||||
fn calculate_motor_velocity(
|
||||
&self,
|
||||
puller_angular_velocity: AngularVelocity,
|
||||
) -> AngularVelocity {
|
||||
if !self.enabled {
|
||||
return AngularVelocity::new::<revolution_per_second>(0.0);
|
||||
}
|
||||
|
||||
// Motor velocity = puller velocity * (slave_ratio / master_ratio)
|
||||
// For 2:1, motor rotates at half the puller speed (1/2)
|
||||
// For 1:2, motor rotates at twice the puller speed (2/1)
|
||||
let ratio = self.slave_ratio / self.master_ratio;
|
||||
puller_angular_velocity * ratio
|
||||
}
|
||||
|
||||
/// Update the motor speed based on the puller angular velocity
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `motor` - The stepper motor to control
|
||||
/// * `puller_angular_velocity` - The current angular velocity of the puller
|
||||
pub fn sync_motor_speed(
|
||||
&self,
|
||||
motor: &mut StepperVelocityEL70x1,
|
||||
puller_angular_velocity: AngularVelocity,
|
||||
) {
|
||||
if !self.enabled {
|
||||
// If disabled, ensure motor is not enabled
|
||||
if motor.is_enabled() {
|
||||
motor.set_enabled(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable motor if not already enabled
|
||||
if !motor.is_enabled() {
|
||||
motor.set_enabled(true);
|
||||
}
|
||||
|
||||
// Calculate target velocity
|
||||
let target_velocity = self.calculate_motor_velocity(puller_angular_velocity);
|
||||
|
||||
// Convert to steps per second and set motor speed
|
||||
let steps_per_second = self.converter.angular_velocity_to_steps(target_velocity);
|
||||
let _ = motor.set_speed(steps_per_second);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use units::angular_velocity::revolution_per_second;
|
||||
|
||||
#[test]
|
||||
fn test_addon_motor_controller_disabled() {
|
||||
let controller = AddonMotorController::new(200);
|
||||
let puller_velocity = AngularVelocity::new::<revolution_per_second>(10.0);
|
||||
|
||||
let motor_velocity = controller.calculate_motor_velocity(puller_velocity);
|
||||
|
||||
assert_eq!(
|
||||
motor_velocity.get::<revolution_per_second>(),
|
||||
0.0,
|
||||
"Motor should be stopped when disabled"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addon_motor_controller_ratio_2_to_1() {
|
||||
let mut controller = AddonMotorController::new(200);
|
||||
controller.set_enabled(true);
|
||||
controller.set_master_ratio(2.0);
|
||||
controller.set_slave_ratio(1.0);
|
||||
|
||||
let puller_velocity = AngularVelocity::new::<revolution_per_second>(10.0);
|
||||
let motor_velocity = controller.calculate_motor_velocity(puller_velocity);
|
||||
|
||||
// For every 2 puller rotations, motor should rotate 1 time
|
||||
// So motor speed should be half of puller speed
|
||||
assert_eq!(
|
||||
motor_velocity.get::<revolution_per_second>(),
|
||||
5.0,
|
||||
"Motor should run at half speed with 2:1 ratio"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addon_motor_controller_ratio_1_to_2() {
|
||||
let mut controller = AddonMotorController::new(200);
|
||||
controller.set_enabled(true);
|
||||
controller.set_master_ratio(1.0);
|
||||
controller.set_slave_ratio(2.0);
|
||||
|
||||
let puller_velocity = AngularVelocity::new::<revolution_per_second>(10.0);
|
||||
let motor_velocity = controller.calculate_motor_velocity(puller_velocity);
|
||||
|
||||
// For every 1 puller rotation, motor should rotate 2 times
|
||||
// So motor speed should be twice the puller speed
|
||||
assert_eq!(
|
||||
motor_velocity.get::<revolution_per_second>(),
|
||||
20.0,
|
||||
"Motor should run at twice speed with 1:2 ratio"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addon_motor_controller_ratio_3_to_2() {
|
||||
let mut controller = AddonMotorController::new(200);
|
||||
controller.set_enabled(true);
|
||||
controller.set_master_ratio(3.0);
|
||||
controller.set_slave_ratio(2.0);
|
||||
|
||||
let puller_velocity = AngularVelocity::new::<revolution_per_second>(9.0);
|
||||
let motor_velocity = controller.calculate_motor_velocity(puller_velocity);
|
||||
|
||||
// For every 3 puller rotations, motor should rotate 2 times
|
||||
// So motor speed should be 2/3 of puller speed
|
||||
// 9 * (2/3) = 6.0
|
||||
assert_eq!(
|
||||
motor_velocity.get::<revolution_per_second>(),
|
||||
6.0,
|
||||
"Motor should run at 2/3 speed with 3:2 ratio"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addon_motor_controller_ratio_1_to_1() {
|
||||
let mut controller = AddonMotorController::new(200);
|
||||
controller.set_enabled(true);
|
||||
controller.set_master_ratio(1.0);
|
||||
controller.set_slave_ratio(1.0);
|
||||
|
||||
let puller_velocity = AngularVelocity::new::<revolution_per_second>(10.0);
|
||||
let motor_velocity = controller.calculate_motor_velocity(puller_velocity);
|
||||
|
||||
// 1:1 ratio means motor runs at same speed as puller
|
||||
assert_eq!(
|
||||
motor_velocity.get::<revolution_per_second>(),
|
||||
10.0,
|
||||
"Motor should run at same speed with 1:1 ratio"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,14 @@ pub enum Mutation {
|
||||
|
||||
// Disconnect Machine
|
||||
DisconnectMachine(MachineIdentificationUnique),
|
||||
|
||||
// Addon Motors
|
||||
SetAddonMotor3Enabled(bool),
|
||||
SetAddonMotor4Enabled(bool),
|
||||
SetAddonMotor3MasterRatio(f64),
|
||||
SetAddonMotor3SlaveRatio(f64),
|
||||
SetAddonMotor4MasterRatio(f64),
|
||||
SetAddonMotor4SlaveRatio(f64),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
@@ -198,6 +206,10 @@ pub struct StateEvent {
|
||||
pub heating_states: HeatingStates,
|
||||
/// Is a Machine Connected?
|
||||
pub connected_machine_state: MachineCrossConnectionState,
|
||||
/// addon motor 3 state
|
||||
pub addon_motor_3_state: AddonMotorState,
|
||||
/// addon motor 4 state
|
||||
pub addon_motor_4_state: AddonMotorState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
@@ -298,6 +310,16 @@ pub struct SpoolSpeedControllerState {
|
||||
pub forward: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct AddonMotorState {
|
||||
/// is motor enabled (running)
|
||||
pub enabled: bool,
|
||||
/// master ratio value (e.g., 2 in "2:1")
|
||||
pub master_ratio: f64,
|
||||
/// slave ratio value (e.g., 1 in "2:1")
|
||||
pub slave_ratio: f64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct HeatingStates {
|
||||
pub zone_1: HeatingState,
|
||||
@@ -435,6 +457,30 @@ impl MachineApi for Gluetex {
|
||||
//main_sender.try_send(crate::AsyncThreadMessage::ConnectOneWayRequest(crate::CrossConnection { src: self.get_machine_identification_unique(), dest: machine_identification_unique }))?;
|
||||
self.emit_state();
|
||||
}
|
||||
Mutation::SetAddonMotor3Enabled(enabled) => {
|
||||
self.addon_motor_3_controller.set_enabled(enabled);
|
||||
self.emit_state();
|
||||
}
|
||||
Mutation::SetAddonMotor4Enabled(enabled) => {
|
||||
self.addon_motor_4_controller.set_enabled(enabled);
|
||||
self.emit_state();
|
||||
}
|
||||
Mutation::SetAddonMotor3MasterRatio(ratio) => {
|
||||
self.addon_motor_3_controller.set_master_ratio(ratio);
|
||||
self.emit_state();
|
||||
}
|
||||
Mutation::SetAddonMotor3SlaveRatio(ratio) => {
|
||||
self.addon_motor_3_controller.set_slave_ratio(ratio);
|
||||
self.emit_state();
|
||||
}
|
||||
Mutation::SetAddonMotor4MasterRatio(ratio) => {
|
||||
self.addon_motor_4_controller.set_master_ratio(ratio);
|
||||
self.emit_state();
|
||||
}
|
||||
Mutation::SetAddonMotor4SlaveRatio(ratio) => {
|
||||
self.addon_motor_4_controller.set_slave_ratio(ratio);
|
||||
self.emit_state();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -378,6 +378,16 @@ impl Gluetex {
|
||||
},
|
||||
},
|
||||
connected_machine_state: cross_conn,
|
||||
addon_motor_3_state: api::AddonMotorState {
|
||||
enabled: self.addon_motor_3_controller.is_enabled(),
|
||||
master_ratio: self.addon_motor_3_controller.get_master_ratio(),
|
||||
slave_ratio: self.addon_motor_3_controller.get_slave_ratio(),
|
||||
},
|
||||
addon_motor_4_state: api::AddonMotorState {
|
||||
enabled: self.addon_motor_4_controller.is_enabled(),
|
||||
master_ratio: self.addon_motor_4_controller.get_master_ratio(),
|
||||
slave_ratio: self.addon_motor_4_controller.get_slave_ratio(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod act;
|
||||
pub mod adaptive_spool_speed_controller;
|
||||
pub mod addon_motor_controller;
|
||||
pub mod api;
|
||||
pub mod clamp_revolution;
|
||||
pub mod emit;
|
||||
@@ -17,6 +18,7 @@ use units::f64::ThermodynamicTemperature;
|
||||
use units::thermodynamic_temperature::degree_celsius;
|
||||
|
||||
mod gluetex_imports {
|
||||
pub use super::addon_motor_controller::AddonMotorController;
|
||||
pub use super::api::GluetexNamespace;
|
||||
pub use super::api::SpoolAutomaticActionMode;
|
||||
pub use super::puller_speed_controller::PullerSpeedController;
|
||||
@@ -118,10 +120,18 @@ pub struct Gluetex {
|
||||
pub tension_arm: TensionArm,
|
||||
pub laser: DigitalOutput,
|
||||
|
||||
// addon motors
|
||||
pub addon_motor_3: StepperVelocityEL70x1,
|
||||
pub addon_motor_4: StepperVelocityEL70x1,
|
||||
|
||||
// controllers
|
||||
pub traverse_controller: TraverseController,
|
||||
pub traverse_end_stop: DigitalInput,
|
||||
|
||||
// addon motor controllers
|
||||
pub addon_motor_3_controller: AddonMotorController,
|
||||
pub addon_motor_4_controller: AddonMotorController,
|
||||
|
||||
// temperature controllers (PID-controlled heaters with temperature sensors)
|
||||
pub temperature_controller_1: TemperatureController,
|
||||
pub temperature_controller_2: TemperatureController,
|
||||
@@ -359,6 +369,22 @@ impl Gluetex {
|
||||
.angular_velocity_to_steps(angular_velocity);
|
||||
let _ = self.puller.set_speed(steps_per_second);
|
||||
}
|
||||
|
||||
/// Sync addon motor 3 speed based on puller angular velocity and ratio
|
||||
/// called by `act`
|
||||
pub fn sync_addon_motor_3_speed(&mut self, t: Instant) {
|
||||
let puller_angular_velocity = self.puller_speed_controller.calc_angular_velocity(t);
|
||||
self.addon_motor_3_controller
|
||||
.sync_motor_speed(&mut self.addon_motor_3, puller_angular_velocity);
|
||||
}
|
||||
|
||||
/// Sync addon motor 4 speed based on puller angular velocity and ratio
|
||||
/// called by `act`
|
||||
pub fn sync_addon_motor_4_speed(&mut self, t: Instant) {
|
||||
let puller_angular_velocity = self.puller_speed_controller.calc_angular_velocity(t);
|
||||
self.addon_motor_4_controller
|
||||
.sync_motor_speed(&mut self.addon_motor_4, puller_angular_velocity);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -230,6 +230,74 @@ impl MachineNewTrait for Gluetex {
|
||||
.await?
|
||||
.0;
|
||||
|
||||
// Role 9: Addon Motor 3 EL7031
|
||||
let el7031_addon3 = {
|
||||
let device = get_ethercat_device::<EL7031>(
|
||||
hardware,
|
||||
params,
|
||||
9,
|
||||
vec![EL7031_IDENTITY_A, EL7031_IDENTITY_B],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let el7031_config = EL7031Configuration {
|
||||
stm_features: shared_config::el70x1::StmFeatures {
|
||||
operation_mode: EL70x1OperationMode::DirectVelocity,
|
||||
speed_range: shared_config::el70x1::EL70x1SpeedRange::Steps1000,
|
||||
..Default::default()
|
||||
},
|
||||
stm_motor: StmMotorConfiguration {
|
||||
max_current: 1500,
|
||||
..Default::default()
|
||||
},
|
||||
pdo_assignment: EL7031PredefinedPdoAssignment::VelocityControlCompact,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
device
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.write_config(&device.1, &el7031_config)
|
||||
.await?;
|
||||
|
||||
device.0
|
||||
};
|
||||
|
||||
// Role 10: Addon Motor 4 EL7031
|
||||
let el7031_addon4 = {
|
||||
let device = get_ethercat_device::<EL7031>(
|
||||
hardware,
|
||||
params,
|
||||
10,
|
||||
vec![EL7031_IDENTITY_A, EL7031_IDENTITY_B],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let el7031_config = EL7031Configuration {
|
||||
stm_features: shared_config::el70x1::StmFeatures {
|
||||
operation_mode: EL70x1OperationMode::DirectVelocity,
|
||||
speed_range: shared_config::el70x1::EL70x1SpeedRange::Steps1000,
|
||||
..Default::default()
|
||||
},
|
||||
stm_motor: StmMotorConfiguration {
|
||||
max_current: 1500,
|
||||
..Default::default()
|
||||
},
|
||||
pdo_assignment: EL7031PredefinedPdoAssignment::VelocityControlCompact,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
device
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.write_config(&device.1, &el7031_config)
|
||||
.await?;
|
||||
|
||||
device.0
|
||||
};
|
||||
|
||||
// Digital outputs for SSR control (24V to external SSRs for 60W heaters)
|
||||
let heater_ssr_1 = DigitalOutput::new(el2004_1.clone(), EL2004Port::DO1);
|
||||
let heater_ssr_2 = DigitalOutput::new(el2004_1.clone(), EL2004Port::DO2);
|
||||
@@ -355,6 +423,14 @@ impl MachineNewTrait for Gluetex {
|
||||
EL7031_0030StepperPort::STM1,
|
||||
),
|
||||
spool: StepperVelocityEL70x1::new(el7041, EL7041_0052Port::STM1),
|
||||
addon_motor_3: StepperVelocityEL70x1::new(el7031_addon3, EL7031StepperPort::STM1),
|
||||
addon_motor_4: StepperVelocityEL70x1::new(el7031_addon4, EL7031StepperPort::STM1),
|
||||
addon_motor_3_controller: super::addon_motor_controller::AddonMotorController::new(
|
||||
200,
|
||||
),
|
||||
addon_motor_4_controller: super::addon_motor_controller::AddonMotorController::new(
|
||||
200,
|
||||
),
|
||||
tension_arm: TensionArm::new(AnalogInput::new(
|
||||
el7031_0030,
|
||||
EL7031_0030AnalogInputPort::AI1,
|
||||
|
||||
Reference in new issue
Block a user