NDEVR
API Documentation
HardwareCommandQueue.h
1/*--------------------------------------------------------------------------------------------
2Copyright (c) 2019, NDEVR LLC
3tyler.parke@ndevr.org
4 __ __ ____ _____ __ __ _______
5 | \ | | | __ \ | ___|\ \ / / | __ \
6 | \ | | | | \ \ | |___ \ \ / / | |__) |
7 | . \| | | |__/ / | |___ \ V / | _ /
8 | |\ |_|_____/__|_____|___\_/____| | \ \
9 |__| \__________________________________| \__\
10
11Subject to the terms of the Enterprise+ Agreement, NDEVR hereby grants
12Licensee a limited, non-exclusive, non-transferable, royalty-free license
13(without the right to sublicense) to use the API solely for the purpose of
14Licensee's internal development efforts to develop applications for which
15the API was provided.
16
17The above copyright notice and this permission notice shall be included in all
18copies or substantial portions of the Software.
19
20THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
21INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
22PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
23FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
24OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25DEALINGS IN THE SOFTWARE.
26
27Library: Hardware
28File: HardwareCommandQueue
29Included in API: True
30Author(s): Tyler Parke
31 *-----------------------------------------------------------------------------------------**/
32#pragma once
33#include "DLLInfo.h"
34#include <NDEVR/BaseValues.h>
35#include <NDEVR/String.h>
36#include <NDEVR/Dictionary.h>
37#include <NDEVR/RWLock.h>
38#include <NDEVR/Time.h>
39#include <NDEVR/TimeSpan.h>
40#include <NDEVR/Thread.h>
41#include <NDEVR/JSONNode.h>
42#include <NDEVR/InfoPipe.h>
43#include <NDEVR/LogMessage.h>
44#include <NDEVR/Exception.h>
45namespace NDEVR
46{
56
75
80 template<class t_type>
82 {
83 public:
86 {
87 t_type command = t_type(0);
91 void clear()
92 {
93 command = t_type(0);
94 args.clear();
95 regex.clear();
96 }
97 };
98
105
108 bool hasPendingCommand(t_type command) const
109 {
110 return m_pending_commands.hasKey(command);
111 }
112 static const uint04 s_buffer_size = 256000;
118 void postCommand(t_type command, const StringView& args = StringView(), const StringView& regex = StringView())
119 {
121 if (allowMultipleCommandsInStream(command, args) || !m_pending_commands.hasKey(command))
122 {
123 m_ordered_commands.add({ command, String(args), String(regex) });
124 if (!m_pending_commands.hasKey(command))
125 m_pending_commands[command] = 0;
126 m_pending_commands[command]++;
127 }
128 else
129 {
130 for (uint04 i = 0; i < m_ordered_commands.size(); i++)
131 {
132 if (m_ordered_commands[i].command == command)
133 {
134 m_ordered_commands[i].args = args;
135 m_ordered_commands[i].regex = regex;
136 }
137 }
138 }
139 }
140
142 void removeCommand(t_type command)
143 {
145 if (m_pending_commands.hasKey(command))
146 {
147 for (uint04 i = m_ordered_commands.size() - 1; IsValid(i); i--)
148 {
149 if (m_ordered_commands[i].command == command)
150 {
151 m_ordered_commands.removeIndex(i);
152 }
153 }
154 m_pending_commands.erase(command);
155 }
156 }
157
161 void addCommandToFront(t_type command, const String& args = String(), const String& regex = String())
162 {
164 if (allowMultipleCommandsInStream(command, args) || !m_pending_commands.hasKey(command))
165 {
166 m_ordered_commands.add(0, { command, args, regex });
167 if (!m_pending_commands.hasKey(command))
168 m_pending_commands[command] = 0;
169 m_pending_commands[command]++;
170 }
171 else
172 {
173 for (uint04 i = 0; i < m_ordered_commands.size(); i++)
174 {
175 if (m_ordered_commands[i].command == command)
176 {
177 m_ordered_commands[i].args = args;
178 m_ordered_commands[i].regex = args;
179 if (i != 0)
180 m_ordered_commands.swapIndices(0, i);
181 }
182
183 }
184 }
185 }
186
190 virtual bool allowMultipleCommandsInStream(t_type command, const StringView& args) const
191 {
192 UNUSED(command);
193 UNUSED(args);
194 return false;
195 }
196
199 {
200 //WLock current_command_lock(&m_current_command);
201 if (m_current_command.command == t_type(0))
202 {
203 {
205 if (m_pending_block_command.command != t_type(0))
206 {
211 return true;
212 }
213 }
214 {
216 if (m_ordered_commands.size() > 0)
217 {
219 m_ordered_commands.removeIndex(0);
221 if (m_pending_commands[m_current_command.command] == 0)
223 }
224 else
225 {
226 return false;
227 }
228 }
231 return true;
232 }
233 return false;
234 }
235
237 {
238 if (m_current_command.command != t_type(0))
239 {
240 //WLock lock(&m_current_command);
241 //if (m_current_command != t_type(0))
242 {
243 CommandRecord record;
245 record.end_time = Time::SystemTime();
246 record.command = m_current_command.command;
247 m_records.add(record);
248 m_command_info[m_current_command.command].total_time += (record.end_time - record.start_time);
249 m_command_info[m_current_command.command].number_of_attempts++;
250#ifdef _DEBUG
251 m_command_info[m_current_command.command].average_time = m_command_info[m_current_command.command].total_time.elapsedSeconds() / m_command_info[m_current_command.command].number_of_attempts;
252#endif
253 m_current_command.clear();
254 m_current_command_start_time = Constant<Time>::Invalid;
255
256
257 }
258 }
259 }
260
261 virtual void retryCommand()
262 {
263 if (m_current_command.command != t_type(0))
264 {
265 const CommandQueueItem command = m_current_command;
267 addCommandToFront(command.command, command.args, command.regex);
268 }
269 }
270
271 virtual void cancelCommand()
272 {
273 if (m_current_command.command != t_type(0))
274 {
275 CommandRecord record;
277 record.end_time = Time::SystemTime();
278 record.command = m_current_command.command;
279 m_records.add(record);
280 m_command_info[m_current_command.command].total_time += (record.end_time - record.start_time);
281 m_command_info[m_current_command.command].number_of_attempts++;
282#ifdef _DEBUG
283 m_command_info[m_current_command.command].average_time = m_command_info[m_current_command.command].total_time.elapsedSeconds() / m_command_info[m_current_command.command].number_of_attempts;
284#endif
285 m_current_command.clear();
286 m_current_command_start_time = Constant<Time>::Invalid;
287 }
288 }
289
291 virtual void onFailure(const t_type& command)
292 {
293 if (m_command_info[command].supports_command == CommandSupport::e_not_known)
294 m_command_info[command].supports_command = CommandSupport::e_not_supported;
295 m_command_info[command].number_of_failures++;
296 }
297
299 {
301 m_pending_commands.clear();
302 m_ordered_commands.clear();
303 WLock block_lock(&m_pending_block_command);
305 }
306
309 virtual void executeCommand(const t_type& command, const StringView& args) = 0;
313 void executeCommandBlocking(const t_type& command, const StringView& args = StringView())
314 {
315 bool finished = false;
316 for (;;)//wait to write pending command
317 {
318 {
319 WLock lock(&m_pending_block_command);//prevent new commands from being issued
320 if (m_pending_block_command.command == t_type(0))
321 {
322 m_pending_block_command.command = command;
323 m_pending_block_command.args = args;
324 finished = true;
325 }
326 }
327 if (finished)
328 break;
329 else
331 }
332 finished = false;
333 for (;;)//Wait for command to be executed
334 {
335 {
336 RLock lock(&m_pending_block_command);//prevent new commands from being issued
337 finished = m_pending_block_command.command != command;
338 }
339 if (finished)
340 break;
341 else
343 }
344 finished = false;
345 for (;;)//Wait for command execution to complete
346 {
347 {
348 //RLock lock(&m_current_command);//prevent new commands from being issued
349 finished = m_current_command.command != command;
350 }
351 if (finished)
352 break;
353 else
355 }
356 }
357
361 {
362 if (m_command_info.contains(command))
363 return m_command_info.get(command);
364 return CommandInformation();
365 }
366
369 virtual bool supportsCommand(t_type command) const
370 {
371 if (m_command_info.contains(command))
372 return m_command_info.get(command).supports_command != CommandSupport::e_not_supported;
373 return true;
374 }
375
377 void disableCommand(t_type command)
378 {
379 if (!m_command_info.contains(command))
380 {
381 m_command_info.add(command, CommandInformation());
382 }
383 for (uint04 i = 0; i < m_ordered_commands.size(); i++)
384 {
385 if (m_ordered_commands[i].command == command)
386 {
387 m_ordered_commands.removeIndex(i--);
388 m_pending_commands[command]--;
389 if (m_pending_commands[command] == 0)
390 m_pending_commands.erase(command);
391 }
392 }
393 m_command_info.get(command).supports_command = CommandSupport::e_not_supported;
394 }
395
398 void setSupport(t_type command, CommandSupport support)
399 {
400 m_command_info[command].supports_command = support;
401 }
402
405 void setSupported(t_type command, bool supported)
406 {
407 if (supported)
408 m_command_info[command].supports_command = CommandSupport::e_supported;
409 else
410 m_command_info[command].supports_command = CommandSupport::e_not_supported;
411 }
412
413 virtual void endCommandQueue()
414 {
415
416 }
417
420 {
421 if (m_current_command.command != t_type(0) && m_command_info.hasKey(m_current_command.command))
422 return m_command_info.get(m_current_command.command).response_timeout;
423 return TimeSpan(0);
424 }
425
428 {
429 if (m_current_command.command != t_type(0) && m_command_info.hasKey(m_current_command.command))
430 return m_command_info.get(m_current_command.command).tx_delay;
431 return TimeSpan(0);
432 }
433
436 {
437 if (m_current_command.command != t_type(0) && m_command_info.hasKey(m_current_command.command))
438 return m_command_info.get(m_current_command.command).rx_delay;
439 return TimeSpan(0);
440 }
441
445 void setFromJSONInfo(const JSONNode& root, bool allow_command_override, LogPtr log)
446 {
447 for (uint04 i = 0; i < Constant<uint04>::Invalid; i++)
448 {
449 t_type command = cast<t_type>(i);
451 if (display.size() == 0)
452 break;
453 try
454 {
456 if (m_command_info.contains(command))
457 {
458 info = m_command_info.get(command);
459 }
460 if (root.hasNode(display))
461 {
462 const JSONNode& node = root[display];
463 if (node.hasNode("Attempt Count"))
464 info.number_of_attempts = node["Attempt Count"].getAs<uint04>();
465 if (node.hasNode("Failure Count"))
466 info.number_of_failures = node["Failure Count"].getAs<uint04>();
467 if (node.hasNode("Total Response Time"))
468 info.total_time = TimeSpan(node["Total Response Time"].getAs<fltp08>());
469 if (allow_command_override)
470 {
471 if (node.hasNode("Supported"))
472 info.supports_command = node["Supported"].getAs<String>().getAs<CommandSupport>();
473 if (node.hasNode("Command"))
474 info.command = node["Command"].getAs<String>();
475 if (node.hasNode("Validation Regex"))
476 info.validation_regex = node["Validation Regex"].getAs<String>();
477 if (node.hasNode("Default Args"))
478 info.default_argument = node["Default Args"].getAs<String>();
479 if (node.hasNode("Transmission Delay"))
480 info.tx_delay = TimeSpan(node["Transmission Delay"].getAs<fltp08>());
481 if (node.hasNode("Receive Delay"))
482 info.rx_delay = TimeSpan(node["Receive Delay"].getAs<fltp08>());
483 if (node.hasNode("Response Timeout"))
484 info.response_timeout = TimeSpan(node["Response Timeout"].getAs<fltp08>());
485 }
486 }
487 m_command_info[command] = info;
488 }
489 catch (Exception& e)
490 {
491 log.addMessage(e.getMessage(), LogMessage::e_error);
492 }
493 }
494 }
495
498 JSONNode getCommandJSONInfo(bool only_supported) const
499 {
500 JSONNode root;
501 for (uint04 i = 0; i < Constant<uint04>::Invalid; i++)
502 {
503 t_type command = cast<t_type>(i);
504
506 if (display.size() == 0)
507 break;
509 if (m_command_info.contains(command))
510 {
511 info = m_command_info.get(command);
512 }
513 if (only_supported && info.supports_command == CommandSupport::e_not_supported)
514 continue;
515 JSONNode& node = root[display];
516 node["Command"] = info.command;
517 if (!only_supported)
518 node["Supported"] = String(info.supports_command);
519 node["Attempt Count"] = info.number_of_attempts;
520 node["Failure Count"] = info.number_of_failures;
521 node["Total Response Time"] = info.total_time.elapsedSeconds();
522 if (info.number_of_attempts > 0)
523 node["Average Response Time"] = info.total_time.elapsedSeconds() / info.number_of_attempts;
524 else
525 node["Average Response Time"] = 0;
526 node["Default Args"] = info.default_argument;
527 node["Transmission Delay"] = info.tx_delay;
528 node["Receive Delay"] = info.rx_delay;
529 node["Response Timeout"] = info.response_timeout;
530 node["Validation Regex"] = info.validation_regex;
531 }
532 return root;
533 }
534
539 String getCommandCSVInfo(bool only_supported, bool include_identity_info, char deliminator = ',') const
540 {
541 String del(deliminator);
542 String root;
543 root += "Command" + del;
544 if (!only_supported)
545 root += "Supported" + del;
546 root += "Attempt Count" + del;
547 root += "Failure Count" + del;
548 root += "Total Response Time" + del;
549 root += "Average Response Time" + del;
550 if (include_identity_info)
551 {
552 root += "Default Args" + del;
553 root += "Transmission Delay" + del;
554 root += "Receive Delay" + del;
555 root += "Response Timeout" + del;
556 root += "Validation Regex" + del;
557 }
558 for (uint04 i = 0; i < Constant<uint04>::Invalid; i++)
559 {
560 root.last() = '\n';
561 t_type command = cast<t_type>(i);
562
564 if (display.size() == 0)
565 break;
567 if (m_command_info.contains(command))
568 {
569 info = m_command_info.get(command);
570 }
571 if (only_supported && info.supports_command == CommandSupport::e_not_supported)
572 continue;
573 root += display + del;
574 if (!only_supported)
575 root += String(info.supports_command) + del;
576 root += String(info.number_of_attempts) + del;
577 root += String(info.number_of_failures) + del;
578 root += String(info.total_time.elapsedSeconds()) + del;
579 if (info.number_of_attempts > 0)
580 root += String(info.total_time.elapsedSeconds() / info.number_of_attempts) + del;
581 else
582 root += "0" + del;
583 if (include_identity_info)
584 {
585 root += info.default_argument + del;
586 root += String(info.tx_delay) + del;
587 root += String(info.rx_delay) + del;
588 root += String(info.response_timeout) + del;
589 root += info.validation_regex + del;
590 }
591 }
592 return root;
593 }
594
596 bool hasPending() const { return m_current_command.command != t_type(0) || m_pending_commands.size() > 0; };
598 t_type currentCommand() const { return m_current_command.command; }
600 const String& currentArgs() const { return m_current_command.args; }
602 const String& currentRegex() const { return m_current_command.regex; }
607 void setBufferOffset(uint04 buffer_offset) { m_buffer_offset = buffer_offset; }
608 protected:
613 Time m_current_command_start_time = Constant<Time>::Invalid;
617 };
618 template class HARDWARE_API StringStream<CommandSupport>;
619}
The equivelent of std::vector but with a bit more control.
Definition Buffer.hpp:58
A hash-based key-value store, useful for quick associative lookups.
Definition Dictionary.h:64
Provides consistent interface to handle errors through the throw expression.
Definition Exception.hpp:47
A base class for communicating with Hardware, typically firmware by the software.
static const uint04 s_buffer_size
The default buffer size for command data.
void removeCommand(t_type command)
Removes all instances of the given command from the queue.
void setSupport(t_type command, CommandSupport support)
Sets the support status for a command.
void postCommand(t_type command, const StringView &args=StringView(), const StringView &regex=StringView())
Posts a command to the end of the queue.
virtual void cancelCommand()
Cancels the currently executing command and records its statistics.
void addCommandToFront(t_type command, const String &args=String(), const String &regex=String())
Adds a command to the front of the queue so it executes next.
Time m_current_command_start_time
When the current command began executing.
JSONNode getCommandJSONInfo(bool only_supported) const
Serializes all command information to a JSON tree.
CommandQueueItem m_pending_block_command
A blocking command waiting to be executed with priority.
Dictionary< t_type, uint04 > m_pending_commands
Map of pending command types to their count in the queue.
Dictionary< t_type, CommandInformation > m_command_info
Map of command types to their configuration and statistics.
bool popCommandAndExecute()
Pops the next command from the queue and executes it.
void setBufferOffset(uint04 buffer_offset)
Sets the buffer offset for data reads.
void disableCommand(t_type command)
Disables a command, removing it from the queue and marking it as unsupported.
TimeSpan rxDelay() const
Returns the receive delay for the currently executing command.
virtual void onFailure(const t_type &command)
Called when a command fails.
uint04 bufferOffset() const
Returns the current buffer offset for data reads.
t_type currentCommand() const
Returns the currently executing command type.
virtual bool allowMultipleCommandsInStream(t_type command, const StringView &args) const
Returns whether multiple instances of the same command are allowed in the queue.
void setSupported(t_type command, bool supported)
Sets whether a command is supported or not.
CommandInformation commandInformation(t_type command)
Returns the stored statistics and configuration for the given command.
Buffer< CommandRecord > m_records
Historical records of executed commands.
void executeCommandBlocking(const t_type &command, const StringView &args=StringView())
Posts a command and blocks until it has been executed and completed.
String getCommandCSVInfo(bool only_supported, bool include_identity_info, char deliminator=',') const
Serializes all command information to a CSV-formatted string.
Buffer< CommandQueueItem > m_ordered_commands
The ordered list of commands waiting to be executed.
void clearCommands()
Removes all pending and blocking commands from the queue.
TimeSpan txDelay() const
Returns the transmission delay for the currently executing command.
bool hasPendingCommand(t_type command) const
Checks whether a specific command is pending in the queue.
virtual void executeCommand(const t_type &command, const StringView &args)=0
Executes a command with the given arguments.
bool hasPending() const
Returns whether there are any pending or currently executing commands.
CommandQueueItem m_current_command
The command currently being executed.
uint04 m_buffer_offset
The current offset into the data buffer.
virtual void retryCommand()
Cancels the current command and re-adds it to the front of the queue for retry.
TimeSpan commandTimeout() const
Returns the response timeout for the currently executing command.
virtual void endCommandQueue()
Called when the command queue is shutting down.
const String & currentRegex() const
Returns the validation regex for the currently executing command.
const String & currentArgs() const
Returns the arguments for the currently executing command.
virtual bool supportsCommand(t_type command) const
Checks whether the hardware supports the given command.
void setFromJSONInfo(const JSONNode &root, bool allow_command_override, LogPtr log)
Loads command configuration and statistics from a JSON tree.
void finishCommand()
Marks the current command as finished and records its execution statistics.
JavaScript Object Notation or JSON is an open - standard file format that uses human - readable text ...
Definition JSONParser.h:149
decltype(auto) getAs() const
Definition JSONParser.h:315
bool hasNode(const StringView &child_node) const
Checks whether a child node with the given name exists.
Definition JSONParser.h:401
@ e_error
Error indicating a failure in an operation.
Definition LogMessage.h:60
A light-weight wrapper that will be a no-op if there is not a valid log reference,...
Used to lock a particular variable for reading.
Definition RWLock.h:157
Logic for reading or writing to a string or a user friendly, TranslatedString.
The core String View class for the NDEVR API.
Definition StringView.h:58
The core String class for the NDEVR API.
Definition String.h:95
static TranslatedString DisplayString(const t_type &value)
Converts an object into a TranslatedString.
static void RequestSleep(const TimeSpan &interval)
Puts the current thread to sleep for a specified duration.
Stores a time span, or difference between two times, with an optional start time.
Definition TimeSpan.h:46
constexpr fltp08 elapsedSeconds() const
Returns the elapsed duration in seconds.
Definition TimeSpan.h:213
Represents a timestamp with utilities for manipulation and conversion.
Definition Time.h:62
static Time SystemTime()
Retrieves the current system time which is a combination of std::chrono::steady_clock to ensure smoot...
String englishTranslation() const
Returns the English translation of this string.
Used to lock a particular variable for writing.
Definition RWLock.h:272
The primary namespace for the NDEVR SDK.
float fltp04
Defines an alias representing a 4 byte floating-point number Bit layout is as follows: -Sign: 1 bit a...
static constexpr bool IsValid(const Angle< t_type > &value)
Checks whether the given Angle holds a valid value.
Definition Angle.h:398
CommandSupport
Describes whether a HardwareCommandQueue supports certain commands.
@ e_supported
The command is supported by the hardware.
@ e_not_known
Whether the command is supported has not yet been determined.
@ e_not_supported
The command is not supported by the hardware.
uint32_t uint04
-Defines an alias representing a 4 byte, unsigned integer -Can represent exact integer values 0 throu...
constexpr t_to cast(const Angle< t_from > &value)
Casts an Angle from one backing type to another.
Definition Angle.h:408
Stores long-term statistics for commands executed using HardwareCommandQueue.
TimeSpan rx_delay
The delay before reading the response to this command.
TimeSpan response_timeout
The maximum time to wait for a response.
TimeSpan tx_delay
The delay before transmitting this command.
String validation_regex
A regex pattern used to validate the command response.
String default_argument
The default argument to use when executing this command.
String command
The command string to send to the hardware.
uint04 number_of_failures
The total number of times this command has failed.
CommandSupport supports_command
Whether this command is supported by the hardware.
TimeSpan total_time
The cumulative time spent executing this command.
uint04 number_of_attempts
The total number of times this command has been attempted.
An item in the command queue containing the command, arguments, and validation regex.
void clear()
Resets this item to its default empty state.
String regex
A regex pattern for validating the command response.
String args
The arguments for the command.
t_type command
The command to execute.
A record of a command execution with timing information.
t_type command
The command that was executed.
Time start_time
When the command began executing.
Time end_time
When the command finished executing.