In my application i have to play sound when i get notification. For playing audio i have implemented following code:
private static final short BFlat = 466; //466.16
private static final short AFlat = 415; //415.30
private static final short A = 440; //440.00
private static final short GFlat = 370; //369.99
private static final short DFlat = 554; //554.37
private static final short C = 523; //523.25
private static final short F = 349; //349.32
// Duration of a 16th note, arbitrary, in ms.
private static final short TEMPO = 125;
// Duration of a 16th note, arbitrary, in ms.
private static final short d16 = 1 * TEMPO;
// Duration of an eighth note, arbitrary, in ms.
private static final short d8 = d16 << 1;
// 10 ms pause.
private static final short dpause = 10;
// Zero frequency pause.
private static final short pause = 0;
private static final short[] TUNE = new short[] {
BFlat, d16, pause, dpause,
BFlat, d16, pause, dpause,
BFlat, d16, pause, dpause,
BFlat, d16, pause, dpause,
A, d16, pause, dpause,
BFlat, d16, pause, dpause,
GFlat, d16, pause, dpause,
GFlat, d16, pause, dpause,
A, d16, pause, dpause,
BFlat, d16, pause, dpause,
DFlat, d16, pause, dpause,
C, d16, pause, dpause, //bar 1
AFlat, d16, pause, dpause,
AFlat, d16, pause, dpause,
AFlat, d16, pause, dpause,
AFlat, d16, pause, dpause,
F, d16, pause, dpause,
GFlat, d16, pause, dpause,
AFlat, d16, pause, dpause,
BFlat, d16, pause, dpause,
AFlat, d16, pause, dpause,
F, d8 + d16 //bar 2
};
public MyScreen()
{
// Set the displayed title of the screen
setTitle("MyTitle");
// % volume
final int VOLUME = 80;
if ( Alert.isAudioSupported())
{
System.out.println("------alert is audio supported-------------"+ Alert.isAudioSupported());
Alert.startAudio(TUNE, 100);
//Alert.startVibrate(5000);
}
Alert.startAudio() is not playing sound at all. But I am able to vibrate phone. Please let me know where i am missing something or how i can use array of short in startAudio.
Please suggest me something or any idea.
Thanks
******EDIT************
To display dialog and notification i have used following. I am able to display dialog box and play vibration and ringtone but volume of ringtone is very low, no one can hear unless i dnt keep phone near to my ear.
try{
Application.getApplication().invokeAndWait(new Runnable() {
public void run()
{
}
});
final Dialog screen = new Dialog(Dialog.D_OK_CANCEL, " "+text,
Dialog.OK,
//mImageGreen.getBitmap(),
null, Manager.VERTICAL_SCROLL);
final UiEngine ui = Ui.getUiEngine();
Application.getApplication().invokeAndWait(new Runnable() {
public void run() {
NotificationsManager.triggerImmediateEvent(0x749cb23a76c66e2dL, 0, null, null);
ui.pushGlobalScreen(screen, 0, UiEngine.GLOBAL_QUEUE);
}
});
System.out.println("-----IN PUSH MESSAGE READER----"+screen.getSelectedValue());
if(screen.getSelectedValue()==1)
{
System.out.println("-----I1-"+screen.getSelectedValue());
ApplicationDescriptor[] appDescriptors =CodeModuleManager.getApplicationDescriptors(CodeModuleManager.getModuleHandle("BB_push"));
ApplicationDescriptor appDescriptor = new ApplicationDescriptor(appDescriptors[0], new String[] {"BB_push"});
try {
ApplicationManager.getApplicationManager().runApplication(appDescriptor);
}catch (ApplicationManagerException e)
{
// TODO Auto-generated catch block
System.out.println("in notification exception----"+e);
e.printStackTrace();
}
}
// screen.setDialogClosedListener(new MyDialogClosedListener());
}
catch (Exception e) {
}
I want to open app on click of ok button but its not working.
Have you considered using the Notification API? This way, the user would be able to setup your app's notification sound, and have it change appropriately when the notification profile is changed from the home screen - think "Normal" "Vibrate Only" and "All Alerts Off"
Take a look at NotificationsManager and the methods triggerImmediateEvent and registerSource
Related
I'm trying to make switch 1 when on turns LEDs 1,2,3 on with a delay of 2 sec. When switch 2 is on, LEDs 4,5,6 go on with delay of 2 sec. When switch 3 is on, it changes the delay of leds to .5 sec. Im having trouble with delay time. Here is code so far.
#include<lpc21xx.h>
#define switch_1 (IO0PIN&0X01)
#define switch_2 (IO0PIN&0X02)
#define switch_3 (IO0PIN&0X04)
void delay(int);
int main()
{
PINSEL0=0X00000000;
IO0DIR=0Xfffffff8;
IO0CLR =0x000001F8;
while(1)
{
if(switch_1==1)
{
IO0SET=0x00000038;
delay(2000);
IO0CLR=0x00000038;
delay(2000);
}
if(switch_2==2)
{
IO0SET=0x000001C0;
delay(2000);
IO0CLR=0x000001C0;
delay(2000);
}
if(switch_3==4)
{
delay(500);
}
}
}
void delay(int time)
{
int i,j;
for(i=0;i<100;i++)
for(j=0;j<2000;j++);
}
I tried doing that code and didn't give me the delay time of 2 seconds for switch 1 and switch 2 nor .5 seconds for switch 3.
I have been trying to make a robotic "gutter cleaner" for a school project. I decided to use an Arduino Uno to control the motors. It's only a forward/reverse drive and therefore only has one motor controlling the motion of the robot. There is another motor controlling the "blades" (for lack of a better word) coming out the front to fling the dirt out of the gutter.
I am using a HC-05 Bluetooth module to accept input from a smartphone and a L9110 H-bridge to control the motors separately. There are five functions: forward motion, reverse motion, blades on, stop and "autonomous". Autonomous involves the blades being on with the robot moving forward for 20 seconds, backwards for 10 seconds, repeating until the stop function is called.
The problem is that when I call the function for the autonomous, the HC-06 seems to stop receiving data and the debug println("auto fwd") spams the Serial Monitor. The "auto rev" code debug code isn't even reached. The stop function cannot run as it appears no data is being received so an infinite loop is created.
I've tried using BlinkWithoutDelay here and I honestly have no idea why this isn't working.
#include <SoftwareSerial.h> //Include the "SoftwareSerial" software in the program
#define M1A 5 //tells the software compiler to assign these varibales to these outputs on the Arduino board
#define M1B 9 //M1 motors are controlling the motion
#define M2A 4 //M2 motors control the blades
#define M2B 10
SoftwareSerial BT(3, 2); //Tells the program to assign pins 2 and 3 on the Arduino to send and receive data
void fw(); //Denoting Functions to be used
void bw();
void Stop();
void autonomous();
void bladesOn();
boolean autonom = false; //Variables
boolean blades = false;
unsigned long currentMillis = millis();
unsigned long previousMillis = 0;
const long fwdTime = 20000;
const long revTime = fwdTime/2;
void setup() {
// put your setup code here, to run once:
TCCR1B = (TCCR1B & 0b11111000) | 0x04;
BT.begin(9600);
Serial.begin(9600);
pinMode(M1A, OUTPUT);
pinMode(M1B, OUTPUT);
pinMode(M2A, OUTPUT);
pinMode(M2B, OUTPUT);
}
void loop() {
if (BT.available()) {
char input = BT.read(); //Read the incoming BlueTooth signal
Serial.println(input); //Print the BT signal to the memory
switch (input) { //IF input is 1, do this, IF input is 2, do that
case '1':
fw();
break;
case '2':
bw();
break;
case '3':
autonomous();
blades = 1;
autonom = true;
break;
case '4':
bladesOn();
blades = true;
break;
case '0':
Stop();
autonom = false;
blades = false;
break;
}
}
}
void bw() {
digitalWrite(M1A, 0); //Give an output to the M1A pin
analogWrite(M1B, 255); //Give an output to the M1B pin
digitalWrite(M2A, 0);
analogWrite(M2B, 255);
Serial.println("Backwards");
}
void fw() {
digitalWrite(M1A, 1);
analogWrite(M1B, (255 - 255));
digitalWrite(M2A, 1);
analogWrite(M2B, (255 - 255));
Serial.println("Forwards");
}
void Stop() {
digitalWrite(M1A, 0);
analogWrite(M1B, 0);
digitalWrite(M2A, 0);
analogWrite(M2B, 0);
Serial.println("Stop");
}
void autonomous() {
while (autonom == true) {
if (currentMillis - previousMillis <= fwdTime) {
//When time between last repeat of forwards/reverse and now is less than Time1, go forward
digitalWrite(M1A, 1);
analogWrite(M1B, (255 - 255));
digitalWrite(M2A, 1);
analogWrite(M2B, (255 - 255));
Serial.println("auto fwd");
}
if (currentMillis - previousMillis <= revTime) {
//When time between last repeat of forwards/reverse and now is less than Time2, go reverse
digitalWrite(M1A, 0);
analogWrite(M1B, 255);
digitalWrite(M2A, 0);
analogWrite(M2B, 255);
Serial.println("auto rev");
}
if (currentMillis - previousMillis == revTime) { //Set previoustime to currenttime
previousMillis = currentMillis;
Serial.println("Autonom");
}
}
}
void bladesOn() {
blades = true;
digitalWrite(M2A, 1);
analogWrite(M2B, 0);
Serial.println("Blades");
}
I know this is probably too long to read for some people, but any help would be very much appreciated. If you need more information, don't hesitate to ask.
PS. I am using "Arduino BT Joystick" as the Android app to control the robot, if that helps.
Thank you,
Craig.
Your logic for the autonomous() function is wrong. The arduino will get stuck on the while loop on the second call of the function, as noted by DigitalNinja, as the autonom variable is only updated outside this loop.
Even if it wasn't the case, the currentMillis variable is not being updated anywhere in the code, so the test currentMillis - previousMillis <= fwdTime will always be true.
(Ps: sorry to answer like this, I don't have enough reputation to comment.)
Problems:
autonom() contains a loop while(autonom == true) { ... } which does never terminate because autonom is never set to false within the loop, so the control never returns to caller void loop() { ... } and you never listen to bluetooth commands again.
You do not update currentMillis anywhere, thus your robot would end being stuck trying to go forward/backward forever.
It is inappropriate to write currentMillis - previousMillis == revTime, because generally speaking currentMillis might become larger than previousMillis + revTime without ever being equal. You should write currentMillis - previousMillis >= revTime instead.
Although not really complicated, your code is quite long and I don't have much time to spend over an answer, so I'll try to set you in the right direction by giving you the mock-up of a proper design in pseudo-code. I am confident that you'll be able to use it to your needs.
Please note that in this example '1' sets the autonomous movement forward and '2' sets the autonomous movement backward, and '3' is no longer used. This is because I think your communication protocol should be enriched so to allow for command parameters, e.g.: the duration of movement. Note that you can easily reproduce your previous behaviour for '1' and '2' by setting time = 0.
enum {
STAY_STILL,
MOVE_FORWARD,
MOVE_BACKWARD,
}
#define FORWARD_TIME 20000
#define BACKWARD_TIME (FORWARD_TIME / 2)
byte state = STAY_STILL;
unsigned long startMillis;
void loop() {
currentMillis = millis();
if (BT.available()) {
char input = BT.read();
switch (input) {
case '1':
state = MOVE_FORWARD;
time = FORWARD_TIME;
startMillis = currentMillis;
break;
case '2':
state = MOVE_BACKWARD;
time = BACKWARD_TIME;
startMillis = currentMillis;
break;
// case '3' is pointless: instead of duplicating functionality
// you should modify your communication protocol so to allow
// setting both a direction and the amount of time you want
// the robot to move in such direction. A value of 0 could
// stand for 'forever', all other values could be interpreted
// as seconds.
case '4':
start_blades();
break;
case '5':
stop_blades();
break;
case '0':
state = STAY_STILL;
break;
}
}
if (state == MOVE_FORWARD || state == MOVE_BACKWARD) {
move_robot();
} else if (state == STAY_STILL) {
stop_blades();
stop_robot();
}
delay(10);
}
void start_blades() {
digitalWrite(M2A, 1);
analogWrite(M2B, 0);
Serial.println("start blades");
}
void stop_blades() {
digitalWrite(M2A, 0);
analogWrite(M2B, 0);
Serial.println("stop blades");
}
void stop_robot() {
digitalWrite(M1A, 0);
analogWrite(M1B, 0);
Serial.println("stop wheels");
}
void move_robot() {
// time == 0 : move forever in one direction
// else : move up until deadline
if (time == 0 || currentMillis - startMillis <= time) {
if (state == MOVE_FORWARD) {
fw();
} else if (state == MOVE_BACKWARD) {
bw();
}
} else {
// this will turn off both blades and
// wheels at the next iteration of the loop()
state = STAY_STILL;
}
}
...
// your fw() and bw() functions
...
To conclude, a minor suggestion. In your place, I would change functions like fw(), bw(), start_blades(), stop_blades(), stop_robot() to perform an action only when it is necessary, instead of setting the values and printing stuff mindlessly. In addition to reducing the overhead of long functions, it also avoids printing tons of messages through serial, making debugging easier. This can be achieved by enriching the robot's state with more information, but it falls beyond the scope of the question and this answer.
Well, in the end, I had a variable that was turning itself on and off apparently on its own. So when the due date came in for the project I took out the Auto function completely. Thanks for all your help guys, I mightn't have gotten it, but I learned a bit.
I am just starting out in using both TinyCore Linux and GTK+3 and am reading through and trying a bunch of different tutorials. I am trying the sample code from the GTK website (https://developer.gnome.org/gtk3/stable/ch01s03.html) and it is not working. I am able to compile, with one warning popping up:
warning: 'gdk_window_get_pointer' is deprecated (declared at /usr/local/include/gtk-3.0/gdk/gdkwindow.h:837): Use 'gdk_window_get_device_position' instead [-Wdeprecated-declaration]
When I run the program the window pops up, but there is no drawing area. I took the same code and compiled it on an Ubuntu machine and it worked just fine, it didn't even show the warning about the depreciated function. Any ideas about what may be causing the drawing area to not display would be greatly appreciated. Thanks for taking the time to read this far.
#include <gtk/gtk.h>
/* Surface to store current scribbles */
static cairo_surface_t *surface = NULL;
static void
clear_surface (void)
{
cairo_t *cr;
cr = cairo_create (surface);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_paint (cr);
cairo_destroy (cr);
}
/* Create a new surface of the appropriate size to store our scribbles */
static gboolean
configure_event_cb (GtkWidget *widget,
GdkEventConfigure *event,
gpointer data)
{
if (surface)
cairo_surface_destroy (surface);
surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
CAIRO_CONTENT_COLOR,
gtk_widget_get_allocated_width (widget),
gtk_widget_get_allocated_height (widget));
/* Initialize the surface to white */
clear_surface ();
/* We've handled the configure event, no need for further processing. */
return TRUE;
}
/* Redraw the screen from the surface. Note that the ::draw
* signal receives a ready-to-be-used cairo_t that is already
* clipped to only draw the exposed areas of the widget
*/
static gboolean
draw_cb (GtkWidget *widget,
cairo_t *cr,
gpointer data)
{
cairo_set_source_surface (cr, surface, 0, 0);
cairo_paint (cr);
return FALSE;
}
/* Draw a rectangle on the surface at the given position */
static void
draw_brush (GtkWidget *widget,
gdouble x,
gdouble y)
{
cairo_t *cr;
/* Paint to the surface, where we store our state */
cr = cairo_create (surface);
cairo_rectangle (cr, x - 3, y - 3, 6, 6);
cairo_fill (cr);
cairo_destroy (cr);
/* Now invalidate the affected region of the drawing area. */
gtk_widget_queue_draw_area (widget, x - 3, y - 3, 6, 6);
}
/* Handle button press events by either drawing a rectangle
* or clearing the surface, depending on which button was pressed.
* The ::button-press signal handler receives a GdkEventButton
* struct which contains this information.
*/
static gboolean
button_press_event_cb (GtkWidget *widget,
GdkEventButton *event,
gpointer data)
{
/* paranoia check, in case we haven't gotten a configure event */
if (surface == NULL)
return FALSE;
if (event->button == GDK_BUTTON_PRIMARY)
{
draw_brush (widget, event->x, event->y);
}
else if (event->button == GDK_BUTTON_SECONDARY)
{
clear_surface ();
gtk_widget_queue_draw (widget);
}
/* We've handled the event, stop processing */
return TRUE;
}
/* Handle motion events by continuing to draw if button 1 is
* still held down. The ::motion-notify signal handler receives
* a GdkEventMotion struct which contains this information.
*/
static gboolean
motion_notify_event_cb (GtkWidget *widget,
GdkEventMotion *event,
gpointer data)
{
/* paranoia check, in case we haven't gotten a configure event */
if (surface == NULL)
return FALSE;
if (event->state & GDK_BUTTON1_MASK)
draw_brush (widget, event->x, event->y);
/* We've handled it, stop processing */
return TRUE;
}
static void
close_window (void)
{
if (surface)
cairo_surface_destroy (surface);
gtk_main_quit ();
}
int
main (int argc,
char *argv[])
{
GtkWidget *window;
GtkWidget *frame;
GtkWidget *da;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
gtk_container_set_border_width (GTK_CONTAINER (window), 8);
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
gtk_container_add (GTK_CONTAINER (window), frame);
da = gtk_drawing_area_new ();
/* set a minimum size */
gtk_widget_set_size_request (da, 100, 100);
gtk_container_add (GTK_CONTAINER (frame), da);
/* Signals used to handle the backing surface */
g_signal_connect (da, "draw",
G_CALLBACK (draw_cb), NULL);
g_signal_connect (da,"configure-event",
G_CALLBACK (configure_event_cb), NULL);
/* Event signals */
g_signal_connect (da, "motion-notify-event",
G_CALLBACK (motion_notify_event_cb), NULL);
g_signal_connect (da, "button-press-event",
G_CALLBACK (button_press_event_cb), NULL);
/* Ask to receive events the drawing area doesn't normally
* subscribe to. In particular, we need to ask for the
* button press and motion notify events that want to handle.
*/
gtk_widget_set_events (da, gtk_widget_get_events (da)
| GDK_BUTTON_PRESS_MASK
| GDK_POINTER_MOTION_MASK);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
I did a fresh install and the warning message disappeared, but the drawing area still refused to show up. I eventually tried changing from the default color depth to 16 bit color depth using the boot code "xvesa=1280x1024x16" and it started showing up. I assume this means it was a driver issue, but I'm not entirely sure.
My setup looks like this:
Main Window
|- QHBoxLayout
|- Some Widgets
|- QScrollArea
| |- QHBoxLayout
| |- QGridLayout
| |- Widgets dynamically loaded in at runtime
| |- QVBoxLayout
|- Some Widgets
After I added in Widgets I want the Scroll Area to completely scroll down.
I did this by:
this->ui->gridLayout_table->addWidget(/*Widget*/); // this works perfectly nice
this->ui->gridLayout_table->addWidget(/*Widget*/); // this also works perfectly nice
this->ui->scrollArea->verticalScrollBar()->setValue(this->ui->scrollArea->verticalScrollBar()->maximum());
At the last command it scrolls to maximum minus the height of the newly added widgets. Is there any way to flush the changes before scrolling?
Thanks in advance
Lukas
Python version for the rangeChanged method:
scroll_area = QtGui.QScrollArea()
scroll_bar = scroll_area.verticalScrollBar()
scroll_bar.rangeChanged.connect(lambda: scroll_bar.setValue(scroll_bar.maximum()))
By connecting the rangeChanged method to the scrolldown, it ensures that each time there is a change in range the scrollbar will scroll down to the max.
Source
I've had the same problem. I found a work-around, which might be useful for you as well.
Using scrollBar->setValue(scrollBar->maximum()); does not work when being used right after having added widgets to the scrolled area. The maximum value of the scroll bar simply is not updated yet.
However it does work if you have a button that the user has to click just to adjust the scroll bar's position to the maximum. It seems that the scroll area notifies its scroll bar asynchronously (through signal) only. So I simply added a hidden button pb_AdjustScrollBar to my widget and simulated a click, using the animateClick(int) method after having added widgets to the scrolled area. Once the signal clicked(bool) is received for the AdjustScrollBar button, I then trigger the scrollBar to use the maximum position with:
scrollBarPtr->triggerAction(QAbstractSlider::SliderToMaximum);
Notes: the animateClick timeout must be set to something like 100ms (default). Putting the timeout much shorter has caused the scrollBar not to update to its real maximum. However I guess 100ms is fast enough for user interactions anyhow.
Still I would be curious to know, how this issue can be solved in a smarter way.
I found out the way. After add a widget put:
scroll->widget()->resize(scroll->widget()->sizeHint());
qApp->processEvents();
One workaround is to use the rangeChanged(int,int) signal of the vertical scroll bar.
#include <QWidget>
#include <QBoxLayout>
#include <QScrollArea>
#include <QScrollBar>
class myClass
{
Q_OBJECT
public:
myClass();
private:
QScrollArea *scrollArea;
int scrollBarMax = 0;
private slots:
void scrollToBottom(int min, int max);
};
myClass::myClass()
{
QVBoxLayout *myLayout = new QVBoxLayout;
QWidget *myWidget = new QWidget;
myWidget->setLayout(myLayout);
scrollArea = new QScrollArea;
scrollArea->setWidget(myWidget);
scrollArea->setWidgetResizeable(true);
QObject::connect(scrollArea->verticalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(scrollToBottom(int,int)));
}
void myClass::scrollToBottom(int min, int max)
{
(void) min;
if (max > scrollBarMax) scrollArea->verticalScrollBar()->setValue(max);
scrollBarMax = max;
}
Hm, I don't have an answer and the same problem. I however can supply a small reproducing example:
#include <QtGui>
class TestWidget : public QWidget
{
Q_OBJECT
public:
TestWidget(QWidget * parent = 0);
private slots:
void addButton();
private:
QScrollArea * scrollArea;
QWidget * contents;
QVBoxLayout * contentsLayout;
};
TestWidget::TestWidget(QWidget * parent)
: QWidget(parent)
{
QVBoxLayout * layout = new QVBoxLayout(this);
scrollArea = new QScrollArea(this);
layout->addWidget(scrollArea);
contents = new QWidget(this);
scrollArea->setWidget(contents);
contents->resize(300,400);
scrollArea->setWidgetResizable(true);
contentsLayout = new QVBoxLayout(contents);
for (int i = 0; i < 10; ++i)
{
QPushButton * button = new QPushButton(QString("button %1").arg(i), this);
connect(button, SIGNAL(clicked()), this, SLOT(addButton()));
contentsLayout->addWidget(button);
}
}
void TestWidget::addButton()
{
QPushButton * button = new QPushButton("button", this);
connect(button, SIGNAL(clicked()), this, SLOT(addButton()));
contentsLayout->addWidget(button);
QScrollBar * scrollBar = scrollArea->verticalScrollBar();
scrollBar->setValue(scrollBar->maximum());
}
int main(int argc, char * argv[])
{
QApplication app(argc, argv);
TestWidget widget;
widget.show();
app.setQuitOnLastWindowClosed(true);
return app.exec();
}
#include "main.moc"
The problem occurs both with Qt 4.8 and 5.2 (needs minor modifications of the example).
Had the same problem, here is what does the manual say:
"In such cases, setting the layout's size constraint property to one which provides
constraints on the minimum and/or maximum size of the layout (e.g.,
QLayout::SetMinAndMaxSize) will cause the size of the scroll area to be updated
whenever the contents of the layout changes."
In my case QScrollArea's contents size was changed by setMinimumSize method of a QScrollArea's widget, so i simply added ui->scroll_area->widget()->layout()->setSizeConstraint(QLayout::SetMinAndMaxSize); and it started to work fine.
Important: after resizing the QScrollArea's widget, you should change QScrollBar's value inside of a custom event handler. Simple example:
bool MyCustomClassWithScrollArea::event(QEvent * event)
{
switch (event->type())
{
case MyCustomScrollToEndEvent::ScrollToEndEventType:
{
ui->scroll_area->verticalScrollBar()->setValue( ui->scroll_area->verticalScrollBar()->maximum() ); //scrolls to the end
return true;
}
}
return QWidget::event(event);
}
I'm working on a Qt application to control an industrial camera, and in particular I need to trigger the camera at a particular time (when various illumination settings have been put in place, for example), and wait until a frame is returned. In the simplest case, the following code does the job nicely:
void AcquireFrame()
{
// Runs in the main GUI thread:
camera -> m_mutex.lock();
camera -> frameHasArrived = false;
camera -> m_mutex.unlock();
camera -> triggerImageAcquisition();
forever
{
camera -> m_mutex.lock()
bool isReady = camera -> frameHasArrived;
camera -> m_mutex.unlock()
if (isReady)
{
return;
}
else
{
Sleep(10);
}
}
}
void callback(camera *)
{
// Called by the camera driver from a separate OS thread - not a Qt thread -
// when a frame is ready:
camera -> m_mutex.lock();
camera -> frameHasArrived = true;
camera -> m_mutex.unlock();
}
...and most of the time this works perfectly well. However, this being the real world, occasionally the camera will fail to receive the trigger or the computer will fail to receive the frame cleanly, and the above code will then go into an infinite loop.
The obvious thing to do is to put in a timeout, so if the frame is not received within a certain time then the image acquisition can be attempted again. The revised code looks like:
void AcquireFrame()
{
camera -> m_mutex.lock();
camera -> frameHasArrived = false;
camera -> m_mutex.unlock();
camera -> triggerImageAcquisition();
QTime timeout;
timeout.start();
forever
{
timeout.restart();
fetch: camera -> m_mutex.lock()
bool isReady = camera -> frameHasArrived;
camera -> m_mutex.unlock()
if (isReady)
{
return;
}
else if (timeout.elapsed() > CAM_TIMEOUT)
{
// Assume the first trigger failed, so try again:
camera -> triggerImageAcquisition();
continue;
}
else
{
Sleep(10);
goto fetch;
}
}
}
Now, the problem is that with this latter version the failure rate (the proportion of 'unsuccessful triggers') is much, much higher - at least an order of magnitude. Moreover, this code too will eventually find itself in an infinite loop where, however many times it tries to re-trigger the camera, it never sees a frame come back. Under these latter circumstances, killing the application and checking the camera reveals that the camera is in perfect working order and patiently waiting for its next trigger, so it doesn't appear to be a camera problem. I'm coming to the conclusion that in fact it's some sort of a system resource issue or a thread conflict, so that Qt's event loop is not allowing the camera callback to be called at the proper time.
Is this likely, and is there in fact a better way of doing this?
Update on 6th June:
For what it's worth, I've seen no more problems since I adopted the method below (having given the camera object an extra member, namely a QWaitCondition called 'm_condition'):
void AcquireFrame()
{
bool frameReceived;
forever
{
camera -> triggerImageAcquisition();
camera -> m_mutex.lock();
frameReceived = camera -> m_condition.wait(&camera->m_mutex, CAM_TIMEOUT);
if (frameReceived)
{
// We received a frame from the camera, so can return:
camera -> m_mutex.unlock();
return;
}
// If we got to here, then the wait condition must have timed out. We need to
// unlock the mutex, go back to the beginning of the 'forever' loop and try
// again:
camera -> m_mutex.unlock();
}
}
void callback (camera *)
{
// Called by the camera driver from a separate OS thread -
// not a QThread - when a frame is ready:
camera -> m_condition.wakeOne();
}
This still has the effect of pausing the main thread until we have either received a frame or experienced a timeout, but now we have eliminated the Sleep() and the Qt event loop remains in full control throughout. It's still not clear to me why the old method caused so many problems - I still suspect some sort of system resource limitation - but this new approach seems to be more lightweight and certainly works better.
Running the AcquireFrame that blocks on mutexes in the GUI thread makes not much sense to me unless you wanted to trade off GUI reponsiveness for latency, but I doubt that you care about the latency here as the camera snaps single frames and you insist on processing them in the busy GUI thread in the first place.
Secondly, there is nothing that Qt would do to prevent the callback from getting called from the other thread, other than the other thread having lower priority and being preempted by higher priority threads completely monopolizing the CPU.
I would simply post an event to a QObject in the GUI thread (or any other QThread!) from the callback function. You can post events from any thread, it doesn't matter -- what matters is the receiver. QCoreApplication::postEvent is a static function, after all, and it doesn't check the current thread at all.
In a complex application you probably want to have the logic in a dedicated controller QObject, and not spread across QWidget-derived classes. So you'd simply post the event to the controller instance.
Do note that posting an event to an idle GUI thread will work exactly the same as using a mutex -- Qt's event loop uses a mutex and sleeps on that mutex and on messages from the OS. The beautiful thing is that Qt already does all the waiting for you, but the wait is interruptible. The posted event should have a high priority, so that it'll end up the first one in the queue and preempt all the other events. When you're ready to acquire the frame, but before you trigger it, you can probably call QCoreApplication::flush(). That's about it.
There should be no problem in having a separate image processor QObject living in a dedicated QThread to leverage multicore machines. You can then process the image into a QImage, and forward that one to the GUI thread using another event, or simply via a signal-slot connection. You can probably also notify the GUI thread when you've acquired the frame but are only beginning to process it. That way it'd be more obvious to the user that something is happening. If image processing takes long, you can even send periodic updates that'd be mapped to a progress bar.
The benchmark results (using release builds) are interesting but in line with the fact that Qt's event queues are internally protected by a mutex, and the event loop waits on that mutex. Oh, the results seem to be portable among mac and windows xp platforms.
Using a naked wait condition is not the fastest way, but using a naked posted event is even slower. The fastest way is to use a queued signal-slot connection. In that case, the cost of posting an event to the same thread (that's what the FrameProcessorEvents::tick() does) seems to be negligible.
Mac
warming caches...
benchmarking...
wait condition latency: avg=45us, max=152us, min=8us, n=1001
queued signal connection latency: avg=25us, max=82us, min=10us, n=1000
queued event latency: avg=71us, max=205us, min=14us, n=1000
Windows XP under VMWare Fusion
Note that results over 1ms are likely due to VMWare being not scheduled at the moment.
warming caches...
benchmarking...
wait condition latency: avg=93us, max=783us, min=8us, n=1000
queued signal connection latency: avg=46us, max=1799us, min=0us, n=1000
queued event latency: avg=117us, max=989us, min=18us, n=1001
Below is the benchmarking code.
#include <cstdio>
#include <limits>
#include <QtCore>
QTextStream out(stdout);
class TimedBase : public QObject
{
public:
TimedBase(QObject * parent = 0) : QObject(parent) { reset(); }
friend QTextStream & operator<<(QTextStream & str, const TimedBase & tb) {
return str << "avg=" << tb.avg() << "us, max=" << tb.usMax << "us, min="
<< tb.usMin << "us, n=" << tb.n;
}
void reset() { usMax = 0; n = 0; usMin = std::numeric_limits<quint32>::max(); usSum = 0; }
protected:
quint64 n, usMax, usMin, usSum;
quint64 avg() const { return (n) ? usSum/n : 0; }
void tock() {
const quint64 t = elapsed.nsecsElapsed() / 1000;
usSum += t;
if (t > usMax) usMax = t;
if (t < usMin) usMin = t;
n ++;
}
QElapsedTimer elapsed;
};
class FrameProcessorEvents : public TimedBase
{
Q_OBJECT
public:
FrameProcessorEvents(QObject * parent = 0) : TimedBase(parent) {}
public slots: // can be invoked either from object thread or from the caller thread
void tick() {
elapsed.start();
QCoreApplication::postEvent(this, new QEvent(QEvent::User), 1000);
}
protected:
void customEvent(QEvent * ev) { if (ev->type() == QEvent::User) tock(); }
};
class FrameProcessorWait : public TimedBase
{
Q_OBJECT
public:
FrameProcessorWait(QObject * parent = 0) : TimedBase(parent) {}
void start() {
QTimer::singleShot(0, this, SLOT(spinner()));
}
public: // not a slot since it must be always invoked in the caller thread
void tick() { elapsed.start(); wc.wakeAll(); }
protected:
QMutex mutex;
QWaitCondition wc;
protected slots:
void spinner() {
forever {
QMutexLocker lock(&mutex);
if (wc.wait(&mutex, 1000)) {
tock();
} else {
return;
}
}
}
};
FrameProcessorEvents * fpe;
FrameProcessorWait * fpw;
static const int avgCount = 1000;
static const int period = 5;
class FrameSender : public QObject
{
Q_OBJECT
public:
FrameSender(QObject * parent = 0) : QObject(parent), n(0), N(1) {
QTimer::singleShot(0, this, SLOT(start()));
}
protected slots:
void start() {
out << (N ? "warming caches..." : "benchmarking...") << endl;
// fire off a bunch of wait ticks
n = avgCount;
timer.disconnect();
connect(&timer, SIGNAL(timeout()), SLOT(waitTick()));
fpw->reset();
fpw->start();
timer.start(period);
}
void waitTick() {
fpw->tick();
if (!n--) {
if (!N) { out << "wait condition latency: " << *fpw << endl; }
// fire off a bunch of signal+event ticks
n = avgCount;
fpe->reset();
timer.disconnect();
connect(&timer, SIGNAL(timeout()), fpe, SLOT(tick()));
connect(&timer, SIGNAL(timeout()), SLOT(signalTick()));
}
}
void signalTick() {
if (!n--) {
if (!N) { out << "queued signal connection latency: " << *fpe << endl; }
// fire off a bunch of event-only ticks
n = avgCount;
fpe->reset();
timer.disconnect();
connect(&timer, SIGNAL(timeout()), SLOT(eventTick()));
}
}
void eventTick() {
fpe->tick();
if (!n--) {
if (!N) { out << "queued event latency: " << *fpe << endl; }
if (!N--) {
qApp->exit();
} else {
start();
}
}
}
protected:
QTimer timer;
int n, N;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread eThread;
QThread wThread;
eThread.start(QThread::TimeCriticalPriority);
wThread.start(QThread::TimeCriticalPriority);
fpw = new FrameProcessorWait();
fpe = new FrameProcessorEvents();
fpw->moveToThread(&eThread);
fpe->moveToThread(&wThread);
FrameSender s;
a.exec();
eThread.exit();
wThread.exit();
eThread.wait();
wThread.wait();
return 0;
}
#include "main.moc"
How much work is it to detect the trigger state and fire the camera?
If that's relatively cheap - I would have a separate thread just blocking on a trigger event and firing the camera. Then have the main thread informed by a Qt signal sent from the callback function.