- Custom Development
CommManager acts as an aggregator of multiple Device objects and serves as a wrapper for the Device objects. It also allows access to the shared global settings of DataLogger objects. It refers to unopened Devices by their port index (portIdx), but can refer to opened Devices by their device id (devId).
The Device class is where most of the plan stack logic is stored.
A Device can be in one of three states: NODEVICE, OPEN, and CONNECTED. NODEVICE is the default state, this is when the Device's serial port has not been opened and the Device has no data except for keeping track of its port index. OPEN is when the serial port has been opened. This causes the Device to actively process incoming data and send commands to the ActPack. CONNECTED is a special case of OPEN, where the Device is actively streaming commands to the ActPack.
A Device object performs four main actions:
Each of these actions is serviced by a separate thread. So for an opened Device (ConnectionState >= OPEN), there are four threads running for that Device. The streaming and logging threads will always be running even if they are not executing any tasks;
this can later be optimized with a condition variable This has been implemented.
Commands are packed into individual Message objects and pushed to a queue to be sent. From thorough testing, the queue never grows too large and the latency for sending commands is between 8 - 12ms. To ensure we send all messages (most importantly that we turn off the controller properly), the commandSender thread will not stop running until the queue has been cleared. For this reason, we must call the stopStreaming() function before the stopThreads() function. Note: if the frequency that we send setPosition commands from the script is extremely high ( > 2000Hz), the queue will back up and shutting down the device will not be immediate. If absolutely necessary, we can adjust the frequency that we clear the queue by changing the sleep duration in the commandSender thread (it's currently set at 500 microseconds).
Opening the Device, tryOpen(). tryOpen is how we establish a serial connection with the ActPack. The function will open the serial port and then send a CMD_SYSDATA_R to the ActPack. The next step in connecting to the ActPack is handled in the separate deviceReader thread. The deviceReader thread will process the ActPack metadata and populate the flexseaDevice and Device fields. Only then will the Device class get a devId. This is better visualized in the flowchart above.
The current thread management will start all the threads when the Device is opened and will not stop any of the threads until the Device is closed. The reason I did not incorporate more granularity is because it makes cleaning up the threads far more complicated and will make it much harder to maintain the code base later. The best way to reduce device idling is to incorporate a condition variable for the commandStreamer and deviceLogger threads as stated previously. Implemented
DataLogger writes Device data to a .csv file to be processed by the GUI. It reads the data from a FlexseaDevice object which is given upon construction. Each Device has its own DataLogger object. However, all DataLogger objects share a few parameters such as the folderpath and the additional variables to log. These shared parameters are set from the CommManager class. Because there are shared variables, these variables need to be initialized before any DataLogger objects are created.
FlexseaDevice is where the returned ActPack data is stored. This data can be accessed by CommWrapper::fxReadData(), DataLogger::logDevice(), and possibly directly by the GUI.
Might be good to change the name of FlexseaDevice to better reflect its function, I named the instance of it in the Device class as “serialDevice” .
FlexseaSerial handles all packing and unpacking for communicating with the ActPack. It inherits the serial open, close, read, and write from SerialDriver and also has additional functions to process the incoming serial data. This abstracts all the serial communication away from the Device class.
The issue we used to face when sending commands too quickly was due to the fact that there was a single shared outgoing MultiWrapper object per port. This caused the message in the MultiWrapper to be corrupted occasionally (this was discovered by printing out all the outgoing bytes to a local file). I addressed this issue by cycling through multiple MultiWrappers instead of a single one. This entailed changing
MultiWrapper out to
MultiWrapper out and making the appropriate changes inside flexsea_comm_multi.c to reflect this change.