Miniscope-DAQ-QT-Software
Miniscope-DAQ-QT-Software copied to clipboard
Software synchronize with other DAQ
Great job for your miniscope development. I have other customized behavior recording system that can be triggered by both keyboard shorcut
and python/websocket
. How to synchronize this behavior recording system with the Miniscope DAQ
software. I knew Miniscope DAQ
provides hardware triggers, how about software trigger (even small latency is tolerable).
I found the way out. I've implemented python/websocket in my own QT DAQ SOFTWARE (ArControl) that I can you python to start/stop a QT GUI recording button. Here I transform that code into your QT DAQ SOFTWARE (Miniscope).
1. Create tcpserver.h
and tcpserver.cpp
.
//tcpserver.h
#ifndef TCPSERVER_H
#define TCPSERVER_H
#include <QObject>
#include <QThread>
#include <QTcpSocket>
#include <QTcpServer>
#include <QDebug>
#include "controlpanel.h"
class TcpServer : public QTcpServer
{
Q_OBJECT
public:
explicit TcpServer(ControlPanel *,QObject *parent = 0);
bool tcpcmd_start_record(char *outmsg);
bool tcpcmd_stop_record(char *outmsg);
int tcpcmd_query_record(char *outmsg);
signals:
void tell_socket_port(QString port);
void recordButtonClick();
void stopButtonClick();
private:
ControlPanel *controlPanel;
public slots:
void on_socket_activate(bool activate);
protected:
void incomingConnection(qintptr socketDescriptor);
};
namespace TCPSERVER_PRIVATE{
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(qintptr ID, TcpServer *tcpserver, QObject *parent = 0);
void run();
signals:
void error(QTcpSocket::SocketError socketerror);
public slots:
void readyRead();
void disconnected();
private:
TcpServer *tcpServer;
QTcpSocket *socket;
qintptr socketDescriptor;
};
}
#endif // TCPSERVER_H
//tcpserver.cpp
#include <QQuickItem>
#include "tcpserver.h"
using namespace TCPSERVER_PRIVATE;
TcpServer::TcpServer(ControlPanel *ctl, QObject *parent):
QTcpServer(parent), controlPanel(ctl)
{
connect(this, SIGNAL(recordButtonClick()),
controlPanel->rootObject->findChild<QQuickItem*>("bRecord"),
SIGNAL(activated()));
connect(this, SIGNAL(stopButtonClick()),
controlPanel->rootObject->findChild<QQuickItem*>("bStop"),
SIGNAL(activated()));
connect(this, SIGNAL(tell_socket_port(QString)),
controlPanel,
SLOT(receiveMessage(QString)));
}
void TcpServer::on_socket_activate(bool activate)
{
if(activate){
quint16 port = 20172;
if(this->listen(QHostAddress::Any, port) || this->listen(QHostAddress::Any))
{
qDebug() << "Server started!" << this->serverPort();
emit tell_socket_port(QString("Connect socket to PORT [%1].").arg(this->serverPort()));
}
else
{
qDebug() << "Server could not start";
emit tell_socket_port(QString("Warning: connect socket failed"));
}
}
else{
this->close();
}
}
// This function is called by QTcpServer when a new connection is available.
void TcpServer::incomingConnection(qintptr socketDescriptor)
{
// We have a new connection
qDebug() << socketDescriptor << " Connecting...";
// Every new connection will be run in a newly created thread
MyThread *thread = new MyThread(socketDescriptor, this);
// connect signal/slot
// once a thread is not needed, it will be beleted later
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
bool TcpServer::tcpcmd_start_record(char *outmsg)
{
bool currentstuts = controlPanel->rootObject->property("recording").toBool();
if(currentstuts){
strcpy(outmsg, "Error! Already started before.");
return false;
}
bool btn_enabled = true;
if(!btn_enabled){
strcpy(outmsg, "Error! Device not connected.");
return false;
}
emit recordButtonClick();
strcpy(outmsg, "OK! Task should be starting.");
return true;
}
bool TcpServer::tcpcmd_stop_record(char *outmsg)
{
bool currentstuts = controlPanel->rootObject->property("recording").toBool();
if(!currentstuts){
strcpy(outmsg, "Error! Already stopped before.");
return false;
}
bool btn_enabled = true;
if(!btn_enabled){
strcpy(outmsg, "Error! Device not connected.");
return false;
}
emit stopButtonClick();
strcpy(outmsg, "OK! Task should be stopping.");
return true;
}
int TcpServer::tcpcmd_query_record(char *outmsg)
{
bool btn_enabled = true;
if(!btn_enabled){
strcpy(outmsg, "Device not connected.");
return 0;
}
bool currentstuts = controlPanel->rootObject->property("recording").toBool();
if(currentstuts){
strcpy(outmsg, "Running.");
return 2;
}
else{
strcpy(outmsg, "Stopped.");
return 1;
}
}
MyThread::MyThread(qintptr ID, TcpServer *tcpServer, QObject *parent) :
QThread(parent)
{
this->socketDescriptor = ID;
this->tcpServer = tcpServer;
}
void MyThread::run()
{
// thread starts here
qDebug() << " Thread started";
socket = new QTcpSocket();
// set the ID
if(!socket->setSocketDescriptor(this->socketDescriptor))
{
// something's wrong, we just emit a signal
emit error(socket->error());
return;
}
// connect socket and signal
// note - Qt::DirectConnection is used because it's multithreaded
// This makes the slot to be invoked immediately, when the signal is emitted.
connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()), Qt::DirectConnection);
connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
// We'll have multiple clients, we want to know which is which
qDebug() << socketDescriptor << " Client connected";
// make this thread a loop,
// thread will stay alive so that signal/slot to function properly
// not dropped out in the middle when thread dies
exec();
}
void MyThread::disconnected()
{
qDebug() << socketDescriptor << " Disconnected";
socket->deleteLater();
exit(0);
}
void MyThread::readyRead()
{
const int MaxLength = 1024;
char buffer[MaxLength+1];
qint64 byteCount = socket->read(buffer, MaxLength);
buffer[byteCount] = 0;
qDebug() << socket->bytesAvailable() << buffer;
char response[MaxLength+1];
if(strcmp(buffer, "start_record")==0)
{
tcpServer->tcpcmd_start_record(response);
}
else if(strcmp(buffer, "stop_record")==0)
{
tcpServer->tcpcmd_stop_record(response);
}
else if(strcmp(buffer, "query_record")==0)
{
tcpServer->tcpcmd_query_record(response);
}
else
{
strcpy(response, "Error! Not a valid command.");
}
socket->write(response);
socket->flush();
socket->waitForBytesWritten(100);
}
2. Modifiy the controlpanel.h
//controlpanel.h
class ControlPanel : public QObject
{
Q_OBJECT
public:
...
QObject *rootObject; //change it from private to public
...
3. Modify the backend.cpp
//backend.cpp
#include "tcpserver.h"
void backEnd::constructUserConfigGUI()
{
controlPanel = new ControlPanel(this, m_userConfig);
QObject::connect(this, SIGNAL (sendMessage(QString) ), controlPanel, SLOT( receiveMessage(QString)));
TcpServer *tcpServer = new TcpServer(controlPanel, this);
tcpServer->on_socket_activate(true);
}
4(Optional). Modify the Miniscope-DAQ-QT-Software.pro
. Add network
keyword.
QT += qml quick widgets network
5. Compile and run.
You can see a web socket tcp server is running on port [20172].
6. Use python3 to start / stop Control Pannel
.
I can also use python3 to synchronize other DAQ system.
# %%
import socket
import time
# %% create connection
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serve_ip = 'localhost'
serve_port = 20172 #default ArControl Recorder Socket PORT
tcp_socket.connect((serve_ip, serve_port))
def send_read(send_data):
send_data_byte = send_data.encode("utf-8")
tcp_socket.send(send_data_byte)
from_server_msg = tcp_socket.recv(1024)
print(from_server_msg.decode("utf-8"))
# %% Supported commands
cmds = ['query_record', 'start_record', 'stop_record']
for send_data in cmds:
send_read(send_data)
time.sleep(5)
Further, I can bind
global-hotkey
to trigger python>start_record
(F2) and python>stop_record
(F4). Both socket and hotkey is available now.
If you like this idea, I would open a pull request. However, I only test based on V1.02, not the latest version (I fail to setup the QT>python|numpy link).
OK this rocks, sorry nobody responded earlier. We'll be moving this functionality into a package that decouples the GUI from the underlying I/O functions, but in the future we will gladly gladly gladly accept PRs like this
Did you see my PR? How did you plan to do the synchronization? I have developed a software synchronizer that can trigger multiple hardwares&softwares, and across multiple computers. I hope it would be inspiring for your project.