// // main.cpp // BrainStem2Example // ///////////////////////////////////////////////////////////////////// // // // Copyright (c) 2022 Acroname Inc. - All Rights Reserved // // // // This file is part of the BrainStem release. See the license.txt // // file included with this package or go to // // https://acroname.com/software/brainstem-development-kit // // for full license details. // ///////////////////////////////////////////////////////////////////// // // main.cpp // BrainStem2Example // // Created by Acroname Inc. on 1/16/22. // Copyright (c) 2015 Acroname Inc. All rights reserved. // #include // standard input / output functions #include #include //Simple Format class. #include #include #include #include "BrainStem2/BrainStem-all.h" static const uint32_t MAX_STREAM_STATUS_BUFFER = 500; using namespace std::chrono; using namespace Acroname::BrainStem; //Formatting class to help with 64bit values across different architectures. template class SimpleFormat { public: static std::string formatValueDecimal(const T& numberValue, uint8_t padding) { std::stringstream ss; ss << std::setfill('0') << std::setw(padding) << numberValue; return ss.str(); } static std::string formatValueHex(const T& numberValue, uint8_t padding) { std::stringstream ss; ss << "0x" << std::uppercase << std::setfill('0') << std::setw(padding) << std::hex << numberValue; return ss.str(); } }; //Function Prototypes: Examples void Example_StreamingSpeedComparison(aUSBHub3c& cHub); void Example_StreamingStatus(aUSBHub3c& cHub); void Example_StreamingAsyc(aUSBHub3c& cHub); void Example_StreamingAsycWithUserRef(aUSBHub3c& cHub); void Example_StreamingAsycWildcards(aUSBHub3c& cHub); void Example_StreamingEnableAll(aUSBHub3c& cHub); //Function Prototypes: Asyc callbacks. aErr _callback_PortVoltage(const aPacket* packet, void* pRef); aErr _callback_PortVoltageQueue(const aPacket* packet, void* pRef); //BrainStem streaming comes in 2x flavors. //1. Synchronous: Simply enable the entity you are interested in and start // executing API calls just like you always have. The BrainStem library will // automatically buffer these values and provide you with the most recent value // when requested through an API call. //2. Asynchronous: The user provides a callback and an optional reference to // the BrainStem library. When an update is available the users callback // will be executed. //Note 1: Not all API's are capable of streaming. //Note 2: The Asynchronous option provides a dedicated worker thread for all // user callbacks. In other words if you block or take too much time in // the callback the BrainStem library will not be affected, but all user // callbacks will be. //Note 3: Subindex's are typically only used in the PowerDeliveryClass. This class // provides helper functions to decode these. // PowerDeliveryClass::packDataObjectAttributes // PowerDeliveryClass::unpackDataObjectAttributes int main(int argc, const char* argv[]) { //Connect //////////////////////////////////////////////////////////////// aErr err = aErrNone; aUSBHub3c cHub; err = cHub.discoverAndConnect(USB); if (err != aErrNone) { printf("Failed to connect to USBHub3c. Error: %d", err); return 1; } //////////////////////////////////////////////////////////////// //Examples - Enable one at a time to observe the different //streaming behaviors. //////////////////////////////////////////////////////////////// Example_StreamingSpeedComparison(cHub); // Example_StreamingStatus(cHub); // Example_StreamingAsyc(cHub); // Example_StreamingAsycWithUserRef(cHub); // Example_StreamingAsycWildcards(cHub); // Example_StreamingEnableAll(cHub); //////////////////////////////////////////////////////////////// cHub.disconnect(); return 0; } ////////////////////////////////////////////////////////////////// //Examples ////////////////////////////////////////////////////////////////// //This example highlights the differences in speed between streaming and non-streaming //Below we will enable streaming on 2 of the USBHub3c ports while leaving the //remaining 6 ports non-streaming (default). We will then fetch the VBus voltage //as quickly as possible displaying their value and the amount of time it //took to execute. //Note: Once streaming is enabled the hardware will transmit values as fast //as they become available. Once a value is read it is flagged as stale //Subsequent calls will provide the stale value, but aErrStreamingStale //will be returned until a new value is received. void Example_StreamingSpeedComparison(aUSBHub3c& cHub) { aErr err = aErrNone; //Enables streaming for ALL API's that support streaming within port[0]. err = cHub.hub.port[0].setStreamEnabled(true); if (err != aErrNone) { printf("Could not enable streaming: %d;\n", err); } //Enables streaming for ALL API's that support streaming within port[1]. err = cHub.hub.port[1].setStreamEnabled(true); if (err != aErrNone) { printf("Could not enable streaming: %d;\n", err); } aTime_MSSleep(1000); //Allow streaming to start coming in. int count = 0; auto start = high_resolution_clock::now(); auto stop = high_resolution_clock::now(); auto duration = duration_cast(stop - start); while (count++ < 10) { printf("Loop: %d\n", count); for (int x = 0; x < aUSBHUB3C_NUM_USB_PORTS; x++) { int32_t voltage = 0; start = high_resolution_clock::now(); err = cHub.hub.port[x].getVbusVoltage(&voltage); //Execute API's like normal. stop = high_resolution_clock::now(); duration = duration_cast(stop - start); //aErrStreamStale is expected when a value is requested before //a new value has been received. The old value is returned, but flagged as stale. std::string errString = std::to_string(err); std::string s = (err == aErrStreamStale) ? (errString + " (aErrStreamStale - Expected)") : (errString); printf("Port: %d, Voltage: %8d(uV) - Duration: %s(uS) - Err: %s\n", x, voltage, SimpleFormat::formatValueDecimal(duration.count(), 5).c_str(), s.c_str()); } printf("\n"); }//End while err = cHub.hub.port[0].setStreamEnabled(false); if (err != aErrNone) { printf("Could not disable streaming: %d;\n", err); } err = cHub.hub.port[1].setStreamEnabled(false); if (err != aErrNone) { printf("Could not disable streaming: %d;\n", err); } } //In this example we will enable streaming for all the ports. Enabling //Through the Entity layer enables all options and subindex. Option //codes can be found in aProtocoldefs.h. //Once enabled we will then request the status of all streaming value //from both the Entity and Link layers. void Example_StreamingStatus(aUSBHub3c& cHub) { aErr err = aErrNone; //Enable streaming for all ports. printf("\n"); for (int x = 0; x < aUSBHUB3C_NUM_USB_PORTS; x++) { err = cHub.hub.port[x].setStreamEnabled(true); if (err != aErrNone) { printf("Could not enable streaming on port: %d, err: %d;\n", x, err); } } //Wait while stuff happens. aTime_MSSleep(1000); //Entity Layer: Get Status - Fetched based on port[x] Acroname::BrainStem::Link::StreamStatusEntry_t status[MAX_STREAM_STATUS_BUFFER]; memset(status, 0, sizeof(Acroname::BrainStem::Link::StreamStatusEntry_t) * MAX_STREAM_STATUS_BUFFER); printf("\n"); printf("EntityClass::getStreamStatus - Raw\n"); printf("-------------------------------------------------------\n"); for (int x = 0; x < aUSBHUB3C_NUM_USB_PORTS; x++) { size_t unloadedLength = 0; err = cHub.hub.port[x].getStreamStatus(status, MAX_STREAM_STATUS_BUFFER, &unloadedLength); //Print status in raw form. for(size_t y = 0; y < unloadedLength; y++) { printf("Port: %d, Stream Key: %s, Value: %d\n", x, SimpleFormat::formatValueHex(status[y].key, 16).c_str(), status[y].value); } printf("\n"); } printf("\n"); printf("EntityClass::getStreamStatus - Decoded\n"); printf("-------------------------------------------------------\n"); for (int x = 0; x < aUSBHUB3C_NUM_USB_PORTS; x++) { size_t unloadedLength = 0; err = cHub.hub.port[x].getStreamStatus(status, MAX_STREAM_STATUS_BUFFER, &unloadedLength); //Print status in extracted form. for (size_t y = 0; y < unloadedLength; y++) { printf("Port Entity: Port: %d, subindex: %d, option: %d, Value: %d\n", x, Link::getStreamKeyElement(status[y].key, Link::STREAM_KEY_SUBINDEX), Link::getStreamKeyElement(status[y].key, Link::STREAM_KEY_OPTION), status[y].value); } printf("\n"); } //Link Layer: Get Streaming Status printf("\n"); if (Link* link = cHub.getLink()) { //Get ALL status by using the wildcards (Link::STREAM_WILDCARD) Acroname::BrainStem::Link::StreamStatusEntry_t allStatus[MAX_STREAM_STATUS_BUFFER]; memset(allStatus, 0, sizeof(Acroname::BrainStem::Link::StreamStatusEntry_t) * MAX_STREAM_STATUS_BUFFER); size_t unloadedLength = 0; err = link->getStreamStatus(cHub.getModuleAddress(), Link::STREAM_WILDCARD, Link::STREAM_WILDCARD, Link::STREAM_WILDCARD, Link::STREAM_WILDCARD, allStatus, MAX_STREAM_STATUS_BUFFER, &unloadedLength); if(err == aErrNone) { //Print status in raw form. printf("LinkClass::getStreamStatus - Raw\n"); printf("-------------------------------------------------------\n"); for(size_t x = 0; x < unloadedLength; x++) { printf("Stream Key: %s, Value: %d\n", SimpleFormat::formatValueHex(allStatus[x].key, 16).c_str(), allStatus[x].value); } printf("\n"); //Print status in extracted form. printf("LinkClass::getStreamStatus - Decoded\n"); printf("-------------------------------------------------------\n"); for (size_t x = 0; x < unloadedLength; x++) { printf("module: %d, cmd: %2d, option: %2d, index: %d, subindex: %3d, value %d\n", Link::getStreamKeyElement(allStatus[x].key, Link::STREAM_KEY_MODULE_ADDRESS), Link::getStreamKeyElement(allStatus[x].key, Link::STREAM_KEY_CMD), Link::getStreamKeyElement(allStatus[x].key, Link::STREAM_KEY_OPTION), Link::getStreamKeyElement(allStatus[x].key, Link::STREAM_KEY_INDEX), Link::getStreamKeyElement(allStatus[x].key, Link::STREAM_KEY_SUBINDEX), allStatus[x].value); } } else { printf("Error: %d, getting status.", err); } } //Disable Streaming for all ports. //Note: Once streaming is disable, get status calls are no longer available. printf("\n"); for (int x = 0; x < aUSBHUB3C_NUM_USB_PORTS; x++) { err = cHub.hub.port[x].setStreamEnabled(false); if (err != aErrNone) { printf("Could not disable streaming on port: %d, err: %d;\n", x, err); } } } //In this example we show how the streaming functionality can be used asynchronously //by providing a function callback. void Example_StreamingAsyc(aUSBHub3c& cHub) { aErr err = aErrNone; //Register callback. err = cHub.hub.port[0].registerOptionCallback(portVbusVoltage, //Option code for cmdPORT true, //Enable callback _callback_PortVoltage, //Callback function NULL); //No ref if (err != aErrNone) { printf("Could not register callback: %d;\n", err); } //Wait while stuff happens. aTime_MSSleep(1000); //Unregister callback err = cHub.hub.port[0].registerOptionCallback(portVbusVoltage, //Option code false, //Disable streaming NULL, //No callback NULL); //No ref if (err != aErrNone) { printf("Could not unregister callback: %d;\n", err); } } //This example is exactly the same as the previous one except we provide //reference parameter. This is helpful for gathering information to be handled //later. void Example_StreamingAsycWithUserRef(aUSBHub3c& cHub) { aErr err = aErrNone; //Thread-safe queue when used by a single producer (thread A - BrainStem Worker Thread) //and single consumer (thread B - Main Thread (this)) Acroname::LocklessQueue_SPSC queue; //Register callback. err = cHub.hub.port[0].registerOptionCallback(portVbusVoltage, //Option code true, //Enable _callback_PortVoltageQueue, //Callback (void*)&queue); //user ref if (err != aErrNone) { printf("Could not register callback: %d;\n", err); } int count = 0; while (count++ < 10) { printf("Loop: %d\n", count); //Wait while stuff happens. aTime_MSSleep(200); //Process/consume queue. bool success = true; while (success) { int32_t voltage = 0; success = queue.pop(&voltage); if (success) { printf("Voltage: %.6f\n", double(voltage) / 1000000); } } } //Unregister callback err = cHub.hub.port[0].registerOptionCallback(portVbusVoltage, //Option code false, //Disable NULL, //No callback NULL); //No ref if (err != aErrNone) { printf("Could not unregister callback: %d;\n", err); } } //Here we show how you can use the link later for even more customization. //The benefit of this option is that you can use wildcards to preform a mass //registration. In this case we will register a callback for portVbusVoltage //and for ALL ports. This means we will need to interrogate the packet for which //port index the packet refers too. void Example_StreamingAsycWildcards(aUSBHub3c& cHub) { //Bulk registration - Specific cmd and option, but all indexes (Link::STREAM_WILDCARD) if (Link* link = cHub.getLink()) { link->registerStreamCallback(cHub.getModuleAddress(), //address cmdPORT, //cmd portVbusVoltage, //option Link::STREAM_WILDCARD, //index (ie ALL) true, //Enable _callback_PortVoltage, //Callback NULL); //No ref. } //Wait while stuff happens. aTime_MSSleep(1000); //Bulk un-registration callbacks. if (Link* link = cHub.getLink()) { //In the future this will also call enable/disable. link->registerStreamCallback(cHub.getModuleAddress(), //address cmdPORT, //cmd portVbusVoltage, //option Link::STREAM_WILDCARD, //index (ie ALL) false, //disable NULL, //Callback NULL); //Callback ref. } aTime_MSSleep(10); //Allow callbacks to finish up. } //Last but not least is the enable all command. Once executed //the device will begin streaming all values it is capable of streaming. //These values can be accessed through any normal API call or through //the bulk getStatus command as shown in this example. //Note: Not all commands are capable of streaming. void Example_StreamingEnableAll(aUSBHub3c& cHub) { aErr err = aErrNone; //Enable All if (Link* link = cHub.getLink()) { link->enableStream(cHub.getModuleAddress(), Link::STREAM_WILDCARD, Link::STREAM_WILDCARD, Link::STREAM_WILDCARD, true); //Wait for data to come in. aTime_MSSleep(1000); //Get ALL status. Acroname::BrainStem::Link::StreamStatusEntry_t allStatus[MAX_STREAM_STATUS_BUFFER]; size_t unloadedLength = 0; err = link->getStreamStatus(cHub.getModuleAddress(), Link::STREAM_WILDCARD, Link::STREAM_WILDCARD, Link::STREAM_WILDCARD, Link::STREAM_WILDCARD, allStatus, MAX_STREAM_STATUS_BUFFER, &unloadedLength); if(err == aErrNone) { for(size_t x = 0; x < unloadedLength; x++) { printf("module: %d, cmd: %2d, option: %2d, index: %d, subindex: %3d, value %d\n", Link::getStreamKeyElement(allStatus[x].key, Link::STREAM_KEY_MODULE_ADDRESS), Link::getStreamKeyElement(allStatus[x].key, Link::STREAM_KEY_CMD), Link::getStreamKeyElement(allStatus[x].key, Link::STREAM_KEY_OPTION), Link::getStreamKeyElement(allStatus[x].key, Link::STREAM_KEY_INDEX), Link::getStreamKeyElement(allStatus[x].key, Link::STREAM_KEY_SUBINDEX), allStatus[x].value); } } else { printf("Error: %d, getting status.", err); } //Disable All link->enableStream(cHub.getModuleAddress(), Link::STREAM_WILDCARD, Link::STREAM_WILDCARD, Link::STREAM_WILDCARD, false); } } ////////////////////////////////////////////////////////////////// //End Examples ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// //Callback functions ////////////////////////////////////////////////////////////////// //Basic Asynchronous callback that simpley prints out the data it receives. aErr _callback_PortVoltage(const aPacket* packet, void* pRef) { uint64_t timestamp = 0; uint32_t seconds = 0; uint32_t uSeconds = 0; int32_t voltage = 0; aErr err = Link::getStreamSample(packet, ×tamp, (uint32_t*)&voltage); //The timestamp can be further decoded with helper functions. (variables not used) Link::getTimestampParts(timestamp, &seconds, &uSeconds); if (err == aErrNone) { uint8_t index = 0; err = aPacket_GetIndex(packet, &index); if (err == aErrNone) { printf("Port: %d, TS: %d:%06d (s:uS), Voltage: %d\n", index, seconds, uSeconds, voltage); } else { printf("Error %d, in _callback_PortVoltage::aPacket_GetIndex", err); } } else { printf("Error %d, in _callback_PortVoltage::getStreamSample", err); } return aErrNone; } //Advanced Asynchronous callback that decodes a user provide reference //and stores the data within it. aErr _callback_PortVoltageQueue(const aPacket* packet, void* pRef) { //Fetch user reference. User MUST know the type that was passed in! You have been warned! Acroname::LocklessQueue_SPSC* queue = (Acroname::LocklessQueue_SPSC*) pRef; //Fetch sample uint64_t timestamp = 0; int32_t voltage = 0; aErr err = Link::getStreamSample(packet, ×tamp, (uint32_t*)&voltage); //If successful, store the sample if (err == aErrNone) { queue->push(voltage); //Store sample } else { printf("Error %d, in _callback_PortVoltageQueue::getStreamSample", err); } return aErrNone; } ////////////////////////////////////////////////////////////////// //End Callback functions //////////////////////////////////////////////////////////////////