vexide_devices/smart/electromagnet.rs
1//! V5 Electromagnet
2//!
3//! The V5 electromagnet is a device unique to the V5 workcell kit. It is a simple
4//! device that produces a magnetic field at a provided power level.
5//! The power level does not have specific units.
6//!
7//! # Hardware Overview
8//!
9//! Not much information can be found on the V5 workcell electromagnet;
10//! however, the electromagnet is intended to be used to pick up V5 Workcell colored disks.
11//! We can assume that the lower bound on the electromagnet's strength is the weight of a V5 Workcell colored disk.
12//! Assuming that the plastic part of the disk is made of ABS plastic and the metal part is solid iron,
13//! the electromagnet can lift at least ≈0.24oz based off of the CAD model files for the V5 Workcell kit provided by VEX.
14
15use core::time::Duration;
16
17use vex_sdk::{
18 vexDeviceMagnetCurrentGet, vexDeviceMagnetPowerGet, vexDeviceMagnetPowerSet,
19 vexDeviceMagnetStatusGet, vexDeviceMagnetTemperatureGet, V5_DeviceT,
20};
21
22use super::{SmartDevice, SmartDeviceType, SmartPort};
23use crate::PortError;
24
25/// An electromagnet plugged into a smart port.
26#[derive(Debug, Eq, PartialEq)]
27pub struct Electromagnet {
28 port: SmartPort,
29 device: V5_DeviceT,
30}
31
32// SAFETY: Required because we store a raw pointer to the device handle to avoid it getting from the
33// SDK each device function. Simply sharing a raw pointer across threads is not inherently unsafe.
34unsafe impl Send for Electromagnet {}
35unsafe impl Sync for Electromagnet {}
36
37impl Electromagnet {
38 /// Maximum duration that the magnet can be powered for.
39 pub const MAX_POWER_DURATION: Duration = Duration::from_secs(2);
40
41 /// Creates a new electromagnet from a [`SmartPort`].
42 ///
43 /// # Examples
44 ///
45 /// ```
46 /// use vexide::prelude::*;
47 /// use core::time::Duration;
48 ///
49 /// #[vexide::main]
50 /// async fn main(peripherals: Peripherals) {
51 /// let mut electromagnet = Electromagnet::new(peripherals.port_1);
52 /// // Use the electromagnet
53 /// _ = electromagnet.set_power(1.0, Electromagnet::MAX_POWER_DURATION);
54 /// _ = electromagnet.set_power(-0.2, Duration::from_secs(1));
55 /// }
56 /// ```
57 #[must_use]
58 pub fn new(port: SmartPort) -> Self {
59 Self {
60 device: unsafe { port.device_handle() },
61 port,
62 }
63 }
64
65 /// Sets the power level of the magnet for a given duration.
66 ///
67 /// Power is expressed as a number from [-1.0, 1.0]. Larger power values will result
68 /// in a stronger force of attraction from the magnet.
69 ///
70 /// # Errors
71 ///
72 /// - A [`PortError::Disconnected`] error is returned if an electromagnet device was required but not connected.
73 /// - A [`PortError::IncorrectDevice`] error is returned if an electromagnet device was required but
74 /// something else was connected.
75 ///
76 /// # Examples
77 ///
78 /// ```
79 /// use vexide::prelude::*;
80 ///
81 /// #[vexide::main]
82 /// async fn main(peripherals: Peripherals) {
83 /// let mut electromagnet = Electromagnet::new(peripherals.port_1);
84 /// _ = electromagnet.set_power(1.0, Electromagnet::MAX_POWER_DURATION);
85 /// }
86 /// ```
87 pub fn set_power(&mut self, power: f64, duration: Duration) -> Result<(), PortError> {
88 self.validate_port()?;
89
90 let power = power.clamp(-1.0, 1.0);
91
92 unsafe {
93 vexDeviceMagnetPowerSet(self.device, (power * 100.0) as _, duration.as_millis() as _);
94 }
95
96 Ok(())
97 }
98
99 /// Returns the user-set power level as a number from [-1.0, 1.0].
100 ///
101 /// # Errors
102 ///
103 /// - A [`PortError::Disconnected`] error is returned if an electromagnet device was required but not connected.
104 /// - A [`PortError::IncorrectDevice`] error is returned if an electromagnet device was required but
105 /// something else was connected.
106 ///
107 /// # Examples
108 ///
109 /// ```
110 /// use vexide::prelude::*;
111 ///
112 /// #[vexide::main]
113 /// async fn main(peripherals: Peripherals) {
114 /// let mut electromagnet = Electromagnet::new(peripherals.port_1);
115 /// _ = electromagnet.set_power(0.5, Electromagnet::MAX_POWER_DURATION);
116 ///
117 /// if let Ok(power) = electromagnet.power() {
118 /// println!("Power: {}%", power * 100.0);
119 /// }
120 /// }
121 /// ```
122 pub fn power(&self) -> Result<f64, PortError> {
123 self.validate_port()?;
124
125 Ok(f64::from(unsafe { vexDeviceMagnetPowerGet(self.device) }) / 100.0)
126 }
127
128 /// Returns the magnet's electrical current in amps.
129 ///
130 /// # Errors
131 ///
132 /// - A [`PortError::Disconnected`] error is returned if an electromagnet device was required but not connected.
133 /// - A [`PortError::IncorrectDevice`] error is returned if an electromagnet device was required but
134 /// something else was connected.
135 ///
136 /// # Examples
137 ///
138 /// ```
139 /// use vexide::prelude::*;
140 ///
141 /// #[vexide::main]
142 /// async fn main(peripherals: Peripherals) {
143 /// let mut electromagnet = Electromagnet::new(peripherals.port_1);
144 /// _ = electromagnet.set_power(1.0, Electromagnet::MAX_POWER_DURATION);
145 ///
146 /// if let Ok(current) = electromagnet.current() {
147 /// println!("Current: {}A", current);
148 /// }
149 /// }
150 /// ```
151 pub fn current(&self) -> Result<f64, PortError> {
152 self.validate_port()?;
153
154 Ok(unsafe { vexDeviceMagnetCurrentGet(self.device) } / 1000.0)
155 }
156
157 /// Returns the internal temperature of the magnet in degrees celsius.
158 ///
159 /// # Errors
160 ///
161 /// - A [`PortError::Disconnected`] error is returned if an electromagnet device was required but not connected.
162 /// - A [`PortError::IncorrectDevice`] error is returned if an electromagnet device was required but
163 /// something else was connected.
164 ///
165 /// # Examples
166 ///
167 /// ```
168 /// use vexide::prelude::*;
169 ///
170 /// #[vexide::main]
171 /// async fn main(peripherals: Peripherals) {
172 /// let electromagnet = Electromagnet::new(peripherals.port_1);
173 ///
174 /// if let Ok(temperature) = electromagnet.temperature() {
175 /// println!("Temperature: {}°C", temperature);
176 /// }
177 /// }
178 /// ```
179 pub fn temperature(&self) -> Result<f64, PortError> {
180 self.validate_port()?;
181
182 Ok(unsafe { vexDeviceMagnetTemperatureGet(self.device) })
183 }
184
185 /// Returns the status code of the magnet.
186 ///
187 /// # Errors
188 ///
189 /// - A [`PortError::Disconnected`] error is returned if an electromagnet device was required but not connected.
190 /// - A [`PortError::IncorrectDevice`] error is returned if an electromagnet device was required but
191 /// something else was connected.
192 ///
193 /// # Examples
194 ///
195 /// ```
196 /// use vexide::prelude::*;
197 ///
198 /// #[vexide::main]
199 /// async fn main(peripherals: Peripherals) {
200 /// let electromagnet = Electromagnet::new(peripherals.port_1);
201 ///
202 /// if let Ok(status) = electromagnet.status() {
203 /// println!("Status: {:b}", status);
204 /// }
205 /// }
206 /// ```
207 pub fn status(&self) -> Result<u32, PortError> {
208 self.validate_port()?;
209
210 Ok(unsafe { vexDeviceMagnetStatusGet(self.device) })
211 }
212}
213
214impl SmartDevice for Electromagnet {
215 fn port_number(&self) -> u8 {
216 self.port.number()
217 }
218
219 fn device_type(&self) -> SmartDeviceType {
220 SmartDeviceType::Electromagnet
221 }
222}
223impl From<Electromagnet> for SmartPort {
224 fn from(device: Electromagnet) -> Self {
225 device.port
226 }
227}