ReduxLib C++ 2025.0.0-beta2
Loading...
Searching...
No Matches
CanandSettingsManager.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 "CanandSettings.h"
6#include "CanandDevice.h"
7#include "CanandUtils.h"
8#include <units/time.h>
9#include <mutex>
10#include <condition_variable>
11#include <vector>
12#include <concepts>
13#include <frc/Errors.h>
14
15namespace redux::canand {
16
17/**
18 * Setting result codes.
19 *
20 * Positive indicates codes returnable from the "report setting" packet, while negative
21 * indicates codes returnable from other causes (e.g. timeouts)
22 */
24 public:
25 enum : int16_t {
26 /** General invalid data */
28 /** Operation timeout */
30 /** General error returned by device */
31 kError = 0,
32 /** Success */
33 kOk = 1,
34 };
35};
36
37/**
38 * Result type of an individual settings operation outcome.
39 */
41 public:
42 /**
43 * Constructor.
44 * @param value the value (only lower 48 bits matter)
45 * @param result the result code
46 */
47 constexpr SettingResult(uint64_t value, int16_t result) : value{value}, result{result} {};
48
49 /**
50 * The setting value.
51 */
52 uint64_t value{0xffffffff'ffffffff};
53
54 /**
55 * The result code.
56 */
58 /**
59 * Returns true if the setting result is valid.
60 *
61 * @return true if the setting result is valid/ok
62 */
63 constexpr bool IsValid() { return result == SettingResultCode::kOk; }
64};
65
66/**
67 * Settings flags that can be sent to the device in settings sets.
68 */
70 public:
71 /** Specifies that the setting is to be set ephemeral and will not persist in flash. */
72 static constexpr uint8_t kEphemeral = 1;
73};
74/**
75 * Common logic for settings management for CanandDevices.
76 *
77 * This class holds a CanandSettings cache of known settings received from the CAN bus,
78 * and offers a series of helper functions that provide common logic for bulk settings operations.
79 */
80template<class T>
81requires std::is_base_of<CanandSettings, T>::value
83 public:
84 /**
85 * Constructor.
86 * @param dev the CanandDevice to associate with.
87 */
89
90 /**
91 * Fetches the device's current configuration in a blocking manner.
92 *
93 * This function will block for at least `timeout` seconds waiting for the device to reply, so
94 * it is best to put this in a teleop or autonomous init function, rather than the main loop.
95 *
96 * @param timeout maximum number of seconds to wait for settings before giving up
97 * @param missingTimeout maximum number of seconds to wait for each settings retry before giving up
98 * @param missingAttempts if >0, lists how many times to attempt fetching missing settings.
99 * @return CanandSettings representing what has been received of the device's configuration.
100 */
101 inline T GetSettings(units::second_t timeout, units::second_t missingTimeout, uint32_t missingAttempts) {
102
103 {
104 std::unique_lock<std::mutex> lock(knownSettingsLock);
105 if (timeout > 0_ms) {
106 knownSettings.GetMap().clear();
107 // send setting command
109 // wait all settings
110 knownSettingsCV.wait_for(lock, utils::toChronoSeconds(timeout),
111 [&]{return knownSettings.AllSettingsReceived();});
112 }
113 if (missingAttempts < 1 || missingTimeout <= 0_ms) { return T{knownSettings}; }
114 }
115 FetchMissingSettings(missingTimeout, missingAttempts);
116 return T{knownSettings};
117 }
118
119 /**
120 * Attempt to fill out the known settings with the set of settings it is missing.
121 *
122 * @param timeout maximum timeout per setting index (seconds)
123 * @param attempts number of attempts to fetch a setting index (should be at least 1)
124 * @return a std::vector of setting indexes that were not able to be received despite attempts/timeout
125 */
126 inline std::vector<uint8_t> FetchMissingSettings(units::second_t timeout, int attempts) {
127 std::vector<uint8_t> missingNow;
128 std::vector<uint8_t> missingFinal;
129 {
130 // Synch snapshot of missing keys.
131 std::unique_lock<std::mutex> lock(knownSettingsLock);
132 if (knownSettings.AllSettingsReceived()) return missingFinal;
133 for (uint8_t addr : knownSettings.SettingAddresses()) {
134 if (!knownSettings.GetMap().contains(addr)) {
135 missingNow.push_back(addr);
136 }
137 }
138 }
139
140 for (uint8_t addr : missingNow) {
142 for (int i = 0; i < attempts && !value.IsValid(); i++) {
143 value = FetchSetting(addr, timeout);
144 }
145 if (!value.IsValid()) {
146 missingFinal.push_back(addr);
147 }
148 }
149 return missingFinal;
150 }
151
152 /**
153 * Tells the device to begin transmitting its settings.
154 * Once they are all transmitted (typically after ~200-300ms),
155 * the values can be retrieved from GetKnownSettings()
156 */
157 inline void StartFetchSettings() {
158 std::unique_lock<std::mutex> lock(knownSettingsLock);
160 knownSettings.GetMap().clear();
161 }
162
163 /**
164 * Applies the settings from a CanandSettings to the device, with fine
165 * grained control over failure-handling.
166 *
167 * This overload allows specifiyng the number of retries per setting as well as the confirmation
168 * timeout. Additionally, it returns a CanandSettings object of settings that
169 * were not able to be successfully applied.
170 *
171 * @param settings the CanandSettings to update the device with
172 * @param timeout maximum time in seconds to wait for each setting to be confirmed. Set to 0 to
173 * not check (and not block).
174 * @param attempts the maximum number of attempts to write each individual setting
175 * @return a CanandSettings object of unsuccessfully set settings.
176 */
177 inline T SetSettings(T& settings, units::second_t timeout, int attempts) {
178 T missed_settings;
179 std::unordered_map<uint8_t, uint64_t> values = settings.FilteredMap();
180 int flags = 0;
181 if (settings.IsEphemeral()) {
183 }
184 for (auto& it: values) {
185 uint8_t addr = it.first;
186 {
187 std::lock_guard<std::mutex> guard(knownSettingsLock);
188 knownSettings.GetMap().erase(addr);
189 }
190 bool success = false;
191 for (int i = 0; i < attempts && !success; i++) {
192 success = ConfirmSetSetting(addr, (uint8_t*) &it.second, 6, timeout, flags).IsValid();
193 }
194 if (!success) {
195 // Add the missed setting to the missed settings map
196 missed_settings.GetMap()[addr] = values[addr];
197 }
198 }
199
200 return missed_settings;
201 }
202
203 /**
204 * Applies the settings from a CanandSettings to the device.
205 *
206 * @param settings the CanandSettings to update the device with
207 * @param timeout maximum time in seconds to wait for each setting to be confirmed. Set to 0 to
208 * not check (and not block).
209 * @return true if successful, false if a setting operation failed
210 */
211 inline bool SetSettings(T& settings, units::second_t timeout) {
212 T missed = SetSettings(settings, timeout, 3);
213 if (!missed.IsEmpty()) {
214 FRC_ReportError(frc::err::Error, "{} settings could not be applied to {}",
215 missed.GetMap().size(), dev.GetDeviceName());
216 return false;
217 }
218 return true;
219 }
220
221 /**
222 * Runs a setting command that may mutate all settings and trigger a response.
223 *
224 * Typically used with "reset factory default" type commands
225 * @param cmd setting index
226 * @param timeout total timeout for all settings to be returned
227 * @param clearKnown whether to clear the set of known settings
228 * @return the set of known settings.
229 */
230 inline T SendReceiveSettingCommand(uint8_t cmd, units::second_t timeout, bool clearKnown) {
231 std::unique_lock<std::mutex> guard(knownSettingsLock);
232 if (clearKnown) knownSettings.GetMap().clear();
234 if (timeout > 0_ms) {
235 knownSettingsCV.wait_for(guard, utils::toChronoSeconds(timeout),
236 [&]{return knownSettings.AllSettingsReceived();});
237 }
238 return T{knownSettings};
239 }
240
241
242 /**
243 * Return a CanandSettings of known settings.
244 * The object returned is a copy of this object's internal copy.
245 * @return known settings
246 */
247 inline T GetKnownSettings() {
248 // construct a blank object and switch out backing map for a clone of knownSettings
249 return T{knownSettings};
250 }
251
252 /**
253 * Setting handler to put in CanandDevice::HandleMessage
254 *
255 * @param msg the CanandMessage containing settings data.
256 */
257 inline void HandleSetting(CanandMessage& msg) {
258 uint8_t flags = 0;
259 uint8_t* data = msg.GetData();
260 uint32_t dataLength = msg.GetLength();
261 uint64_t settingValue = 0;
262 if (dataLength < 7) return;
263 else if (dataLength >= 8) {
264 flags = data[7];
265 }
266 memcpy(&settingValue, data + 1, 6);
267 // process knownSettings
268 bool allSettingsFound = false;
269 {
270 std::lock_guard<std::mutex> getSettingsGuard(knownSettingsLock);
271 knownSettings.GetMap()[data[0]] = settingValue;
272 allSettingsFound = knownSettings.AllSettingsReceived();
273 }
274 if (allSettingsFound) knownSettingsCV.notify_all();
275
276 // process settings recv
277 {
278 std::lock_guard<std::mutex> settingRecvGuard(settingRecvLock);
279 settingRecvCtr++;
280 settingRecvIdx = data[0];
281 settingRecvCode = flags;
282 settingRecvValue = 0;
283 memcpy(&settingRecvValue, data + 1, 6);
284 }
285 settingRecvCV.notify_all();
286 }
287
288 /**
289 * Directly sends a CAN message to the associated CanandDevice to set a setting by index.
290 * This function does not block nor check if a report settings message is sent in response.
291 *
292 * <p>
293 * Device subclasses will usually have a more user-friendly settings interface,
294 * eliminating the need to call this function directly in the vast majority of cases.
295 * </p>
296 *
297 * @param settingId the setting id
298 * @param value the raw numerical value. Only the first 6 bytes will be used.
299 * @param length the length of the buffer specified.
300 * @param flags optional flags to send to the device specifying how the setting will be set.
301 */
302 inline void SetSettingById(uint8_t settingId, uint8_t* value, uint8_t length, uint8_t flags) {
303 uint8_t data[8] = { 0 };
304 data[0] = settingId;
305 data[7] = flags;
306 if (length > 6) length = 6;
307 memcpy(data + 1, value, length);
309 }
310
311 /**
312 *
313 * Directly sends a CAN message to the associated CanandDevice to set a setting by index.
314 * This function does not block nor check if a report settings message is sent in response.
315 *
316 * <p>
317 * Device subclasses will usually have a more user-friendly settings interface,
318 * eliminating the need to call this function directly in the vast majority of cases.
319 * </p>
320 *
321 * @param settingId setting id to use
322 * @param value 48-bit long
323 * @param flags flags
324 */
325 inline void SetSettingById(uint8_t settingId, uint64_t value, uint8_t flags) {
326 // something soemthing undefined behavior
327 SetSettingById(settingId, (uint8_t*) &value, 6, flags);
328 }
329
330 /**
331 * Potentially blocking operation to send a setting and wait for a report setting message to be
332 * received to confirm the operation.
333 *
334 * @param settingIdx Setting index to set and listen for
335 * @param payload the bytes to send.
336 * @param length the length of the payload.
337 * @param timeout the timeout to wait before giving up in seconds. Passing in 0 will return
338 * instantly (not block)
339 * @param flags optional flags to send to the device specifying how the setting will be set.
340 * @return the value received by the report setting packet if existent or kTimeout otherwise.
341 * If timeout = 0, return "payload" (assume success)
342 */
343 inline SettingResult ConfirmSetSetting(uint8_t settingIdx, uint8_t* payload, uint8_t length,
344 units::second_t timeout, uint8_t flags) {
345
346 std::unique_lock<std::mutex> lock(settingRecvLock);
347 SetSettingById(settingIdx, payload, length, flags);
348 if (timeout <= 0_ms) {
349 uint64_t longPayload = 0;
350 memcpy(&longPayload, payload, std::min((uint8_t) 6, length));
351 return SettingResult{longPayload, SettingResultCode::kOk};
352 }
353 uint32_t prevCtr = settingRecvCtr;
354 if (!settingRecvCV.wait_for(lock, utils::toChronoSeconds(timeout), [&]{
355 // checks that the recv is both the correct idx and fresh
356 return this->settingRecvIdx == settingIdx && this->settingRecvCtr != prevCtr;
357 })) {
358 // timeout
360 }
361
362 return SettingResult{settingRecvValue, settingRecvCode};
363 }
364
365 /**
366 * Potentially blocking operation to send a setting and wait for a report setting message to be
367 * received to confirm the operation.
368 *
369 * @param settingIdx Setting index to set and listen for
370 * @param payload the 48 bits to send.
371 * @param timeout the timeout to wait before giving up in seconds. Passing in 0 will return
372 * instantly (not block)
373 * @param flags optional flags to send to the device specifying how the setting will be set.
374 * @return the value received by the report setting packet if existent or kTimeout otherwise.
375 * If timeout = 0, return "payload" (assume success)
376 */
377 inline SettingResult ConfirmSetSetting(uint8_t settingIdx, uint64_t payload,
378 units::second_t timeout, uint8_t flags) {
379
380 std::unique_lock<std::mutex> lock(settingRecvLock);
381 SetSettingById(settingIdx, payload, flags);
382 if (timeout <= 0_ms) {
383 return SettingResult{payload, SettingResultCode::kOk};
384 }
385 uint32_t prevCtr = settingRecvCtr;
386 if (!settingRecvCV.wait_for(lock, utils::toChronoSeconds(timeout), [&]{
387 // checks that the recv is both the correct idx and fresh
388 return this->settingRecvIdx == settingIdx && this->settingRecvCtr != prevCtr;
389 })) {
390 // timeout
392 }
393
394 return SettingResult{settingRecvValue, settingRecvCode};
395 }
396
397 /**
398 * Fetches a setting from the device and returns the received result.
399 * @param settingIdx Setting index to fetch
400 * @param timeout timeout to wait before giving up in seconds. Passing in 0 will return a timeout.
401 * @return SettingResult representing the setting result.
402 */
403 inline SettingResult FetchSetting(uint8_t settingIdx, units::second_t timeout) {
404 std::unique_lock<std::mutex> lock(settingRecvLock);
405 uint8_t buf[] = {details::SettingCommand::kFetchSettingValue, settingIdx};
407
408 if (timeout <= 0_ms) { return SettingResult{0, SettingResultCode::kInvalid}; }
409 uint32_t prevCtr = settingRecvCtr;
410 if (!settingRecvCV.wait_for(lock, utils::toChronoSeconds(timeout), [&]{
411 // checks that the recv is both the correct idx and fresh
412 return this->settingRecvIdx == settingIdx && this->settingRecvCtr != prevCtr;
413 })) {
414 // timeout
416 }
417
418 return SettingResult{settingRecvValue, settingRecvCode};
419 }
420
421 /**
422 * Sends a setting command with no arguments.
423 * @param settingCmdIdx the index of the setting command to send.
424 */
425 inline void SendSettingCommand(uint8_t settingCmdIdx) {
427 }
428
429 private:
430 T knownSettings;
431 std::mutex knownSettingsLock;
432 std::condition_variable knownSettingsCV;
433
434 std::mutex settingRecvLock;
435 std::condition_variable settingRecvCV;
436 uint32_t settingRecvCtr = 0;
437 uint8_t settingRecvIdx = 0;
438 uint8_t settingRecvCode = 0;
439 uint64_t settingRecvValue = 0;
440
441 CanandDevice& dev;
442};
443
444
445}
Definition: CanandDevice.h:35
bool SendCANMessage(uint8_t apiIndex, uint8_t *data, uint8_t length)
Definition: CanandDevice.h:119
Definition: CanandMessage.h:26
uint8_t * GetData()
Definition: CanandMessage.h:91
uint8_t GetLength()
Definition: CanandMessage.h:97
Definition: CanandSettingsManager.h:82
void HandleSetting(CanandMessage &msg)
Definition: CanandSettingsManager.h:257
SettingResult ConfirmSetSetting(uint8_t settingIdx, uint64_t payload, units::second_t timeout, uint8_t flags)
Definition: CanandSettingsManager.h:377
T GetSettings(units::second_t timeout, units::second_t missingTimeout, uint32_t missingAttempts)
Definition: CanandSettingsManager.h:101
bool SetSettings(T &settings, units::second_t timeout)
Definition: CanandSettingsManager.h:211
SettingResult ConfirmSetSetting(uint8_t settingIdx, uint8_t *payload, uint8_t length, units::second_t timeout, uint8_t flags)
Definition: CanandSettingsManager.h:343
void SendSettingCommand(uint8_t settingCmdIdx)
Definition: CanandSettingsManager.h:425
T SetSettings(T &settings, units::second_t timeout, int attempts)
Definition: CanandSettingsManager.h:177
T SendReceiveSettingCommand(uint8_t cmd, units::second_t timeout, bool clearKnown)
Definition: CanandSettingsManager.h:230
SettingResult FetchSetting(uint8_t settingIdx, units::second_t timeout)
Definition: CanandSettingsManager.h:403
CanandSettingsManager(CanandDevice &dev)
Definition: CanandSettingsManager.h:88
std::vector< uint8_t > FetchMissingSettings(units::second_t timeout, int attempts)
Definition: CanandSettingsManager.h:126
void SetSettingById(uint8_t settingId, uint64_t value, uint8_t flags)
Definition: CanandSettingsManager.h:325
void SetSettingById(uint8_t settingId, uint8_t *value, uint8_t length, uint8_t flags)
Definition: CanandSettingsManager.h:302
T GetKnownSettings()
Definition: CanandSettingsManager.h:247
void StartFetchSettings()
Definition: CanandSettingsManager.h:157
Definition: CanandSettingsManager.h:69
static constexpr uint8_t kEphemeral
Definition: CanandSettingsManager.h:72
Definition: CanandSettingsManager.h:23
@ kError
Definition: CanandSettingsManager.h:31
@ kTimeout
Definition: CanandSettingsManager.h:29
@ kOk
Definition: CanandSettingsManager.h:33
@ kInvalid
Definition: CanandSettingsManager.h:27
Definition: CanandSettingsManager.h:40
int16_t result
Definition: CanandSettingsManager.h:57
constexpr SettingResult(uint64_t value, int16_t result)
Definition: CanandSettingsManager.h:47
constexpr bool IsValid()
Definition: CanandSettingsManager.h:63
uint64_t value
Definition: CanandSettingsManager.h:52
static constexpr uint8_t kSetSetting
Definition: CanandDevice.h:158
static constexpr uint8_t kSettingCommand
Definition: CanandDevice.h:156
static constexpr uint8_t kFetchSettings
Definition: CanandDevice.h:173
static constexpr uint8_t kFetchSettingValue
Definition: CanandDevice.h:177
constexpr std::chrono::duration< double, std::ratio< 1LL, 1LL > > toChronoSeconds(units::second_t seconds)
Definition: CanandUtils.h:94
Definition: CanandMessage.h:10