ReduxLib C++ 2025.0.0-beta2
Loading...
Searching...
No Matches
Canandmag.h
1// Copyright (c) Redux Robotics and other contributors.
2// This is open source and can be modified and shared under the 3-clause BSD license.
3
4#pragma once
5#include <cinttypes>
6#include <unordered_map>
7#include <mutex>
8#include <condition_variable>
9#include <optional>
10#include <units/angle.h>
11#include <units/angular_velocity.h>
12#include <units/time.h>
13#include <units/temperature.h>
14#include "redux/canand/CanandDevice.h"
15#include "redux/canand/CanandEventLoop.h"
16#include "redux/canand/CanandSettingsManager.h"
17#include "redux/canand/CooldownWarning.h"
18#include "redux/frames/Frame.h"
19
20#include "redux/sensors/canandmag/CanandmagDetails.h"
21#include "redux/sensors/canandmag/CanandmagFaults.h"
22#include "redux/sensors/canandmag/CanandmagSettings.h"
23#include "redux/sensors/canandmag/CanandmagStatus.h"
24
25/**
26 * Namespace for all classes relating to the Canandmag
27*/
29
30/**
31 * Class for the CAN interface of the <a href="https://docs.reduxrobotics.com/canandmag/index.html">Canandmag.</a>
32 *
33 * <p>
34 * If you are using a Canandmag with Spark Max or Talon with the PWM output, see
35 * <a href="https://docs.reduxrobotics.com/canandmag/spark-max.html">our Spark Max docs</a>
36 * or
37 * <a href="https://docs.reduxrobotics.com/canandmag/talon-srx.html">our Talon SRX docs</a>
38 * for information on how to use the encoder with the Rev and CTRE APIs.
39 * </p>
40 *
41 * <p>
42 * The C++ vendordep uses the <a href="https://docs.wpilib.org/en/stable/docs/software/basic-programming/cpp-units.html">units library</a>
43 * for all dimensioned values, including settings.
44 * </p>
45 *
46 * <p>
47 * Operations that receive data from the device (position, velocity, faults, temperature) generally do not block.
48 * The object receives data asynchronously from the CAN packet receive thread and reads thus return the last data received.
49 * </p>
50 * <p>
51 * Operations that set settings or change offsets will generally wait for up to 20ms by default as they will usually
52 * wait for a confirmation packet to be received in response -- unless the blocking timeout is set to zero, in which case
53 * the operation swill not block.
54 * </p>
55 *
56 * Example code:
57 * ```cpp
58 * Canandmag canandmag{0}; // instantiates with encoder id 0
59 *
60 * // Reading the Canandmag
61 * canandmag.GetPosition(); // returns a multi-turn relative position, in rotations (turns)
62 * canandmag.GetAbsPosition(); // returns an absolute position bounded from [0..1) over one rotation
63 * canandmag.GetVelocity(); // returns measured velocity in rotations per second
64 *
65 * // Updating position
66 * canandmag.SetPosition(-3.5_tr); // sets the relative position to -3.5 turns (does not persist on reboot)
67 * canandmag.SetAbsPosition(330_deg, 0_s); // sets the absolute position to 330 degrees without blocking for confirmation (persists on reboot)
68 * canandmag.ZeroAll(); // sets both the relative and absolute position to zero
69 *
70 * // Changing configuration
71 * CanandmagSettings settings;
72 * settings.SetVelocityFilterWidth(25_ms); // sets the velocity filter averaging period to 25 ms
73 * settings.SetInvertDirection(true); // make positive be clockwise instead of ccw opposite the sensor face
74 * canandmag.SetSettings(settings, 20_ms); // apply the new settings to the device, with maximum 20 ms timeout per settings operation
75 *
76 * // Faults
77 * canandmag.ClearStickyFaults(); // clears all sticky faults (including the power cycle flag). This call does not block.
78 *
79 * // this flag will always be true on boot until the sticky faults have been cleared,
80 * // so if this prints true the encoder has rebooted sometime between ClearStickyFaults and now.
81 * CanandmagFaults faults = canandmag.GetStickyFaults(); // fetches faults
82 * fmt::print("Encoder rebooted: {}\n", faults.powerCycle);
83 *
84 * // Timestamped data
85 * redux::frames::FrameData<units::turn_t> posFrameData = canandmag.GetPositionFrame().GetFrameData(); // gets current position + timestamp together
86 * posFrameData.GetValue(); // fetched position in rotations
87 * posFrameData.GetTimestamp(); // timestamp of the previous position
88 * ```
89 *
90 */
92 public:
93 /**
94 * Constructor with the device's id. This object will be constant with respect to whatever CAN id assigned to it,
95 * so if a device changes id it may change which device this object reads from.
96 * @param canID the device id to use
97 */
98 Canandmag(int canID);
100 // functions related to core functionality
101
102 /**
103 * Gets the current integrated position in rotations.
104 *
105 * <p> This value does not wrap around, so turning a sensed axle multiple rotations will return multiple sensed rotations of position.
106 * By default, positive is in the counter-clockwise direction from the sensor face.
107 * </p>
108 * <p> On encoder power-on, unlike the absolute value, this value will always initialize to zero. </p>
109 * @return signed relative position in rotations (range [-131072.0..131071.999938396484])
110 */
111 units::turn_t GetPosition();
112
113 /**
114 * Gets the current absolute position of the encoder, in a scaled value from 0 inclusive to 1 exclusive.
115 * By default, higher values are in the counter-clockwise direction from the sensor face.
116 * <p> This value will persist across encoder power cycles making it appropriate for swerves/arms/etc. </p>
117 * @return absolute position in fraction of a rotation [0..1)
118 */
119 units::turn_t GetAbsPosition();
120
121 /**
122 * Sets the new relative (multi-turn) position of the encoder to the given value.
123 *
124 * <p>
125 * Note that this does not update the absolute position, and this value is lost on a power cycle. To update the absolute position,
126 * use Canandmag::SetAbsPosition
127 * </p>
128 * @param newPosition new position in rotations
129 * @param timeout maximum time to wait for the operation to be confirmed (default 0.020 seconds). Set to 0 to not check (and not block).
130 * @return true on success, false on timeout
131 */
132 bool SetPosition(units::turn_t newPosition, units::second_t timeout = 20_ms);
133
134 /**
135 * Sets the new absolute position value for the encoder which will (by default) persist across reboots.
136 *
137 * @param newPosition new absolute position in fraction of a rotation (acceptable range [0..1))
138 * @param timeout maximum time to wait for the operation to be confirmed (default 0.020 seconds). Set to 0 to not check (and not block).
139 * @param ephemeral if true, set the setting ephemerally -- the new zero offset will not persist on power cycle.
140 * @return true on success, false on timeout
141 */
142 bool SetAbsPosition(units::turn_t newPosition, units::second_t timeout = 20_ms, bool ephemeral = false);
143
144 /**
145 * Sets both the current absolute and relative encoder position to 0 -- generally equivalent to pressing the physical zeroing button on the encoder.
146 * @param timeout maximum time in seconds to wait for each operation to be confirmed (there are 2 ops for zeroing both absolute and relative positions,
147 * so the wait is up to 2x timouet). Set to 0 to not check (and not block).
148 * @return true on success, false on timeout
149 */
150 bool ZeroAll(units::second_t timeout = 20_ms);
151
152 /**
153 * Returns the measured velocity in rotations per second.
154 * @return velocity, in rotations (turns) per second
155 */
156 units::turns_per_second_t GetVelocity();
157
158 /**
159 * Returns whether the encoder magnet is in range of the sensor or not.
160 * This can be seen visually on the sensor -- a green LED is in range, whereas
161 * a red LED is out of range.
162 *
163 * @return whether the output shaft magnet is in range.
164 */
166
167 // functions related to diagonstic data
168
169 /**
170 * Fetches sticky faults.
171 * Sticky faults are the active faults, except once set they do not become unset until ClearStickyFaults() is called.
172 *
173 * @return CanandmagFaults of the sticky faults
174 */
176
177 /**
178 * Fetches active faults.
179 * Active faults are only active for as long as the error state exists.
180 *
181 * @return CanandmagFaults of the active faults
182 */
184
185 /**
186 * Get onboard encoder temperature readings in degrees Celsius.
187 * @return temperature in degrees Celsius
188 */
189 units::celsius_t GetTemperature();
190
191 /**
192 * Get the contents of the previous status packet, which includes active faults, sticky faults, and temperature.
193 * @return device status as a status struct
194 */
195 inline CanandmagStatus GetStatus() { return status.GetValue(); }
196
197 /**
198 * Clears sticky faults.
199 *
200 * <p>It is recommended to clear this during initialization, so one can check if the encoder loses power during operation later. </p>
201 * <p>This call does not block, so it may take up to the next status frame (default every 1000 ms) for the sticky faults to be updated.</p>
202 */
204
205 /**
206 * Controls "party mode" -- an encoder identification tool that blinks the onboard LED
207 * various colors at a user-specified strobe period.
208 * The strobe period of the LED will be (50 milliseconds * level). Setting this to 0 disables party mode.
209 *
210 * This function does not block.
211 *
212 * @param level the party level value to set.
213 */
214 void SetPartyMode(uint8_t level);
215
216 // functions relating to settings
217
218 /**
219 * Fetches the Canandmag's current configuration in a blocking manner.
220 * This function will need to block for at least 0.2-0.3 seconds waiting for the encoder to reply, so it is best
221 * to put this in an init function, rather than the main loop.
222 *
223 * <p> <b>Note that unlike v2023, this function will always return a settings object,
224 * but they may be incomplete settings!</b> </p>
225 *
226 * You will need to do something like this to unwrap/verify the result:
227 *
228 * ```cpp
229 * // device declaration
230 * Canandmag canandmag{0};
231 *
232 * // in your init/other sequence
233 * CanandmagSettings stg = canandmag.GetSettings();
234 * if (stg.AllSettingsReceived()) {
235 * // do your thing here
236 * } else {
237 * // handle missing settings
238 * }
239 * ```
240 *
241 * <p>Advanced users can use this function to retry settings missed from StartFetchSettings: </p>
242 * ```cpp
243 * // device declaration
244 * Canandmag canandmag{0};
245 * enc.StartFetchSettings(); // send a "fetch settings command"
246 *
247 * // wait some amount of time
248 * CanandmagSettings stg = enc.GetSettingsAsync();
249 * stg.AllSettingsReceived(); // may or may not be true
250 *
251 * stg = enc.GetSettings(0_ms, 20_ms, 3); // Retry getitng the missing settings.
252 * stg.AllSettingsReceived(); // far more likely to be true
253 *
254 * @param timeout maximum number of seconds to wait for a settings operation before timing out (default 350_ms)
255 * @param missingTimeout maximum number of seconds to wait for each settings retry before giving up
256 * @param attempts number of attempts to try and fetch values missing from the first pass
257 * @return Received set of CanandmagSettings of device configuration.
258 */
259 inline CanandmagSettings GetSettings(units::second_t timeout = 350_ms, units::second_t missingTimeout = 20_ms, uint32_t attempts = 3) {
260 return stg.GetSettings(timeout, missingTimeout, attempts);
261 };
262
263 /**
264 * Tells the Canandmag to begin transmitting its settings; once they are all transmitted (after ~200-300ms),
265 * the values can be retrieved through the Canandmag::GetSettingsAsync() function call
266 */
267 inline void StartFetchSettings() { return stg.StartFetchSettings(); }
268
269 /**
270 * Non-blockingly returns a {@link CanandmagSettings} object of the most recent known settings values received from the encoder.
271 *
272 * <p> <b>Most users will probably want to use Canandmag::GetSettings() instead. </b> </p>
273 *
274 * One can call this after a Canandmag::StartFetchSettings() call, and use CanandmagSettings::AllSettingsReceived()
275 * to check if/when all values have been seen. As an example:
276 *
277 * ```cpp
278 *
279 * // device declaration
280 * Canandmag enc{0};
281 *
282 * // somewhere in an init function
283 * enc.StartFetchSettings();
284 *
285 * // ...
286 * // somewhere in a loop function
287 *
288 * CanandmagSettings stg = enc.GetSettingsAsync();
289 * if (stg.AllSettingsReceived()) {
290 * // do something with the returned settings
291 * fmt::print("Encoder velocity frame period: {}\n", *stg.GetVelocityFramePeriod());
292 * }
293 * ```
294 *
295 *
296 * If this is called after Canandmag::SetSettings(), this method will return a settings object where only
297 * the fields where the encoder has echoed the new values back will be populated. To illustrate this, consider the following:
298 * ```cpp
299 * // device declaration
300 * Canandmag enc{0};
301 *
302 * // somewhere in a loop
303 * CanandmagSettings stg_set;
304 * stg_set.SetVelocityFramePeriod(100_ms);
305 * enc.SetSettings(stg_set);
306 * CanandmagSettings stg_get = enc.GetSettingsAsync();
307 *
308 * // will likely return std::nullopt, as the device likely hasn't already responded to the settings set request
309 * stg_get.GetVelocityFramePeriod();
310 *
311 * // after up to 100 ms...
312 * stg_get = enc.GetSettingsAsync();
313 *
314 * // will likely be a value equivalent to 100_ms, may still be std::nullopt if the device is disconnected, so be careful of blind dereferences
315 * stg_get.GetVelocityFramePeriod();
316 * ```
317 *
318 * @return CanandmagSettings of currently known settings
319 */
320 inline CanandmagSettings GetSettingsAsync() { return stg.GetKnownSettings(); }
321
322 /**
323 * Applies the settings from a CanandmagSettings object to the Canandmag.
324 * For more information, see the CanandmagSettings class documentation.
325 *
326 * Example:
327 * ```cpp
328 * CanandmagSettings stg;
329 * Canandmag enc{0};
330 * // After configuring the settings object...
331 *
332 * CanandmagSettings failed = enc.SetSettings(stg);
333 * if (failed.IsEmpty()) {
334 * // success
335 * } else {
336 * // handle failed settings
337 * }
338 * ```
339 *
340 * @param settings the CanandmagSettings to update the encoder with
341 * @param timeout maximum time in seconds to wait for each setting to be confirmed. (default 0.020s, set to 0 to not check and not block).
342 * @param attempts the maxinum number of attempts to write each individual settings
343 * @return CanandmagSettings object of unsuccessfully set settings.
344 */
345 inline CanandmagSettings SetSettings(CanandmagSettings& settings, units::second_t timeout = 20_ms, uint32_t attempts = 3) {
346 return stg.SetSettings(settings, timeout, attempts);
347 }
348
349 /**
350 * Resets the encoder to factory defaults, and then wait for all settings to be broadcasted
351 * back.
352 * @param clearZero whether to clear the zero offset from the encoder's memory as well
353 * @param timeout how long to wait for the new settings to be confirmed by the encoder in
354 * seconds (suggested at least 0.35 seconds)
355 * @return CanandmagSettings object of received settings.
356 * Use CanandmagSettings.AllSettingsReceived() to verify success.
357 */
358 inline CanandmagSettings ResetFactoryDefaults(bool clearZero = false, units::second_t timeout = 350_ms) {
359 uint8_t val = ((clearZero) ? details::SettingCommand::kResetFactoryDefault
361 return stg.SendReceiveSettingCommand(val, timeout, true);
362 }
363
364 /**
365 * Returns the CanandSettingsManager associated with this device.
366 *
367 * The CanandSettingsManager is an internal helper object.
368 * Teams are typically not expected to use it except for advanced cases (e.g. custom settings
369 * wrappers)
370 * @return internal settings manager handle
371 */
372 inline redux::canand::CanandSettingsManager<CanandmagSettings>& GetInternalSettingsManager() {
373 return stg;
374 }
375
376 /**
377 * Returns the current relative position frame, which includes CAN timestamp data.
378 * redux::canand::FrameData objects are immutable.
379 * @return the current position frame, which will hold the current position in the same units as Canandmag::GetPosition()
380 */
381 inline redux::frames::Frame<units::turn_t>& GetPositionFrame() { return position; }
382
383 /**
384 * Returns the current absolute position frame, which includes CAN timestamp data.
385 * @return the current position frame, which will hold the current position in the same units as Canandmag::getAbsPosition()
386 */
387 inline redux::frames::Frame<units::turn_t>& GetAbsPositionFrame() { return absPosition; }
388
389 /**
390 * Returns the current velocity frame, which includes CAN timestamp data.
391 * @return the current velocity frame, which will hold the current velocity in the same units as Canandmag::getVelocity()
392 */
393 inline redux::frames::Frame<units::turns_per_second_t>& GetVelocityFrame() { return velocity; }
394
395 /**
396 * Returns a handle to the current status frame, which includes CAN timestamp data.
397 * @return the current status frame, as a CanandmagStatus record.
398 */
399 inline redux::frames::Frame<CanandmagStatus>& GetStatusFrame() { return status; }
400
401
402 // functions that directly modify settings
405 inline std::string GetDeviceClassName() override { return "Canandmag"; };
407 return redux::canand::CanandFirmwareVersion{2024, 2, 0};
408 }
409
410 /** number of encoder ticks per rotation */
411 static constexpr double kCountsPerRotation = 16384;
412
413 /** number of velocity ticks per rotation per second */
414 static constexpr double kCountsPerRotationPerSecond = 1024;
415
416 protected:
417
418 /** internal Frame variable holding current relative position state */
419 redux::frames::Frame<units::turn_t> position{0.0_tr, 0_ms};
420
421 /** internal Frame variable holding current absolute position state */
422 redux::frames::Frame<units::turn_t> absPosition{0.0_tr, 0_ms};
423
424 /** internal Frame variable holding current velocity state */
426
427 /** internal Frame variable holding current status value state */
428 redux::frames::Frame<CanandmagStatus> status{CanandmagStatus{0, 0, false, 30_degC, false}, 0_ms};
429
430 /** internal settings manager */
432 private:
433
434 bool dataRecvOnce{false};
435 units::second_t lastMessageTime{0_s};
436 redux::canand::CooldownWarning setAbsPositionWarning{1_s, 5};
438
439};
440
441
442}
Definition: CanandAddress.h:62
Definition: CanandDevice.h:35
virtual CanandAddress & GetAddress()=0
virtual CanandFirmwareVersion GetMinimumFirmwareVersion()
Definition: CanandDevice.h:109
virtual void HandleMessage(CanandMessage &msg)=0
virtual std::string GetDeviceClassName()
Definition: CanandDevice.h:74
Definition: CanandMessage.h:26
Definition: CanandSettingsManager.h:82
Definition: CooldownWarning.h:15
Definition: Frame.h:89
Definition: CanandmagFaults.h:13
Definition: CanandmagSettings.h:54
Definition: Canandmag.h:91
bool SetPosition(units::turn_t newPosition, units::second_t timeout=20_ms)
bool ZeroAll(units::second_t timeout=20_ms)
CanandmagStatus GetStatus()
Definition: Canandmag.h:195
units::turns_per_second_t GetVelocity()
bool SetAbsPosition(units::turn_t newPosition, units::second_t timeout=20_ms, bool ephemeral=false)
@ kResetFactoryDefaultKeepZero
Definition: CanandmagDetails.h:76
@ kResetFactoryDefault
Definition: CanandmagDetails.h:72
void RemoveCANListener(CanandDevice *device)
Definition: CanandmagSettings.h:12
Definition: CanandFirmwareVersion.h:17
Definition: CanandmagStatus.h:13