Squidstat API User Manual
Loading...
Searching...
No Matches
Advanced Control Flow

This page assumes familiarity with concepts covered in the basics. This shows how to run a sequence of experiments and controlling when to stop an experiment and start another based on external conditions. For simplicity, we are assuming having a single device connected and we are running on a single channel. So, we will not have to keep track of devices and channels. We will just focus on running and controlling the workflow of different experiments.

First we will set the environment and create the experiments:

// environment setup: creating the app
char** test = nullptr;
int args;
QCoreApplication app(args, test);
// constructing a constant potential element with required arguments
5, // voltage: 5v
1, // sampling interval: 1s
10 // duration: 10s
);
// constructing a constant current element with required arguments
0.002, // current: 0.002A
1, // sampling interval: 1s
10 // duration: 10s
);
auto experimentA = std::make_shared<AisExperiment>(); // create a custom experiment
experimentA->appendElement(cvElement, 1); // append to experimentA, the created CV element and set it to run 1 time
auto experimentB = std::make_shared<AisExperiment>(); // create a second experiment
experimentB->appendElement(ccElement, 1); // append to experimentB, the created CC element and set it to run 1 time
auto experimentC = std::make_shared<AisExperiment>(); // create a third experiment
experimentC->appendElement(cvElement, 2); // append to experimentC, the created CV element and set it to run 2 times
an experiment that simulates a constant current flow with more advance options for stopping the exper...
Definition: AisConstantCurrentElement.h:15
an experiment that simulates a constant applied voltage.
Definition: AisConstantPotElement.h:15

Now we have the experiments set up. Next we will create the logic for the sequence of experiments. We will be using timers as external conditions to control the workflow. You may substitute that with your own conditions.

The following lambda function creates a logic and assigns it to the given handler. We will call this function after the AisDeviceTracker::newDeviceConnected signal has been emitted and a handler has been created. The workflow will be as follows:

  • Start the first timer
  • Once the timer times out, start Experiment A
  • Once Experiment A completes, start the second timer
  • Once the second timer times out, start Experiment B
  • Start a third timer to stop Experiment B early
  • Once the third timer times out, stop Experiment B
  • Start a fourth timer
  • Once the fourth timer times out, start Experiment C
  • Once Experiment C completes, start Experiment B
// Lambda function
auto createLogic = [&](AisInstrumentHandler* handler) {
QTimer* timer1 = new QTimer(); // the first timer is used in lieu of the first external condition
timer1->setSingleShot(true);
timer1->start(1000);
QObject::connect(timer1, &QTimer::timeout, [=]() {
qDebug() << "Initial condition met. Starting Experiment A ";
handler->uploadExperimentToChannel(0, experimentA);
handler->startUploadedExperiment(0);
// once the first experiment is completed (Experiment A), start the next experiment (Experiment B).
// this signal will be emitted for any experiment not just A so, we will track of the sequence with experimentStep
// once an experiment has completed or has been stopped, continue to the next experimentStep
QObject::connect(handler, &AisInstrumentHandler::experimentStopped, [&](uint8_t channel) {
static int experimentStep = 0;
qDebug() << "Experiment Step " << experimentStep << " Completed";
experimentStep++; //increment the experiment step
if (experimentStep == 1) {
// Wait for external start condition
QTimer* timer = new QTimer(); // the timer is used in lieu of an external condition
timer->setSingleShot(true);
timer->start(10000); // when this timer times out, the next experiment (Experiment B) will start
// Create an external condition that will stop the upcoming experiment early
QTimer* StopEarlyTimer = new QTimer();
StopEarlyTimer->setSingleShot(true);
QObject::connect(StopEarlyTimer, &QTimer::timeout, [&]() {
qDebug() << "External early stop condition met";
handler->StopExperiment(0); // Once the external condition is met, experiment B will stop, and the experimentCompleted signal will be emitted
});
QObject::connect(timer, &QTimer::timeout, [&,StopEarlyTimer]() {
qDebug() << "External condition met, starting experiment B";
handler->uploadExperimentToChannel(0, experimentB); // start Experiment B
handler->startUploadedExperiment(0);
StopEarlyTimer->start(2000);
});
} else if (experimentStep == 2) {
QTimer* timer = new QTimer(); // the timer is used in lieu of an external condition
timer->setSingleShot(true);
timer->start(10000); // when this timer times out, the next experiment (Experiment C) will start
QObject::connect(timer, &QTimer::timeout, [&]() {
qDebug() << "External condition met, starting Experiment C ";
handler->uploadExperimentToChannel(0, experimentC); // start Experiment C
handler->startUploadedExperiment(0);
});
} else if (experimentStep == 3) {
QTimer* timer = new QTimer(); // the timer is used in lieu of an external condition
timer->setSingleShot(true);
timer->start(10000); // when this timer times out, the next experiment (Experiment B) will start
QObject::connect(timer, &QTimer::timeout, [&]() {
qDebug() << "External condition met, starting Experiment B ";
handler->uploadExperimentToChannel(0, experimentB); // start Experiment B
handler->startUploadedExperiment(0);
});
}
});
});
};
this class provides control of the device including starting, pausing, resuming and stopping an exper...
Definition: AisInstrumentHandler.h:24
void experimentStopped(uint8_t channel)
a signal that is emitted whenever an experiment was stopped manually or has completed.

This logic we have shown demonstrates how to start and stop experiments based on external conditions/variables, and how to do so based on the behavior of other experiments as well.

We then connect the tracker's signals as we have explained in more details before.

// this is a signal-slot connection where the slot assigns the logic to the device handler when newDeviceConnected signal is emitted.
auto tracker = AisDeviceTracker::Instance(); // create a tracker that tracks connected devices
QObject::connect(tracker, &AisDeviceTracker::newDeviceConnected, &app, [&](const QString& deviceName) {
auto handler = tracker->getInstrumentHandler(deviceName);
createLogic(handler); // controlling experiments is to be done only after a device handler has been assigned. That is why it is placed inside this slot.
});
// here we have a signal and slot for when a device has been disconnected
QObject::connect(tracker, &AisDeviceTracker::deviceDisconnected, &app, [=](const QString& deviceName) {
qDebug() << deviceName << "is disconnected ";
});
tracker->connectToDeviceOnComPort("COM3"); // change the port number to your device. For example, in windows, you can find it from the device manager
void deviceDisconnected(const QString &deviceName)
a signal to be emitted whenever a device has been disconnected.
void newDeviceConnected(const QString &deviceName)
a signal to be emitted whenever a new connection has been successfully established with a device.

Finally, you can start the application as follows:

app.exec();

Note however that this will hold your execution thread. That would be fine if this is your main application or if you have previously spawned a thread specifically for this application. Alternatively, you can start the application as follows:

// process events while channel 0 is busy
while (handler.isChannelBusy(0)) {
app.processEvents();
}
app.processEvents();

You can learn more about Qt app execution here: https://doc.qt.io/qt-5/qcoreapplication.html#static-public-members