Skip to content

Commit 47fe691

Browse files
committed
Implemented record control via network events
1 parent dff7817 commit 47fe691

8 files changed

Lines changed: 325 additions & 7 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
A zmq client to test remote control of open-ephys GUI
3+
"""
4+
5+
import zmq
6+
import os
7+
import time
8+
9+
10+
def run_client():
11+
12+
# Basic start/stop commands
13+
start_cmd = 'StartRecord'
14+
stop_cmd = 'StopRecord'
15+
16+
# Example settings
17+
rec_dir = os.path.join(os.getcwd(), 'Output_RecordControl')
18+
print "Saving data to:", rec_dir
19+
20+
# Some commands
21+
commands = [start_cmd + ' RecDir=%s' % rec_dir,
22+
start_cmd + ' PrependText=Session01 AppendText=Condition01',
23+
start_cmd + ' PrependText=Session01 AppendText=Condition02',
24+
start_cmd + ' PrependText=Session02 AppendText=Condition01',
25+
start_cmd,
26+
start_cmd + ' CreateNewDir=1']
27+
28+
# Connect network handler
29+
ip = '127.0.0.1'
30+
port = 5556
31+
timeout = 1.
32+
33+
url = "tcp://%s:%d" % (ip, port)
34+
35+
with zmq.Context() as context:
36+
with context.socket(zmq.REQ) as socket:
37+
socket.RCVTIMEO = int(timeout * 1000) # timeout in milliseconds
38+
socket.connect(url)
39+
40+
# Finally, start data acquisition
41+
socket.send('StartAcquisition')
42+
answer = socket.recv()
43+
print answer
44+
time.sleep(5)
45+
46+
for start_cmd in commands:
47+
48+
for cmd in [start_cmd, stop_cmd]:
49+
socket.send(cmd)
50+
answer = socket.recv()
51+
print answer
52+
53+
if 'StartRecord' in cmd:
54+
# Record data for 5 seconds
55+
time.sleep(5)
56+
else:
57+
# Stop for 1 second
58+
time.sleep(1)
59+
60+
# Finally, stop data acquisition; it might be a good idea to
61+
# wait a little bit until all data have been written to hard drive
62+
time.sleep(0.5)
63+
socket.send('StopAcquisition')
64+
answer = socket.recv()
65+
print answer
66+
67+
68+
if __name__ == '__main__':
69+
run_client()

Source/CoreServices.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ void setRecordingStatus(bool enable)
5252
getControlPanel()->setRecordState(enable);
5353
}
5454

55+
bool getAcquisitionStatus()
56+
{
57+
return getControlPanel()->getAcquisitionState();
58+
}
59+
60+
void setAcquisitionStatus(bool enable)
61+
{
62+
getControlPanel()->setAcquisitionState(enable);
63+
}
64+
5565
void sendStatusMessage(const String& text)
5666
{
5767
getBroadcaster()->sendActionMessage(text);
@@ -77,6 +87,26 @@ int64 getSoftwareTimestamp()
7787
return getMessageCenter()->getTimestamp(true);
7888
}
7989

90+
void setRecordingDirectory(String dir)
91+
{
92+
getControlPanel()->setRecordingDirectory(dir);
93+
}
94+
95+
void createNewRecordingDir()
96+
{
97+
getControlPanel()->labelTextChanged(NULL);
98+
}
99+
100+
void setPrependTextToRecordingDir(String text)
101+
{
102+
getControlPanel()->setPrependText(text);
103+
}
104+
105+
void setAppendTextToRecordingDir(String text)
106+
{
107+
getControlPanel()->setAppendText(text);
108+
}
109+
80110
namespace RecordNode
81111
{
82112
void createNewrecordingDir()
@@ -105,4 +135,4 @@ int addSpikeElectrode(SpikeRecordInfo* elec)
105135
}
106136
};
107137

108-
};
138+
};

Source/CoreServices.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ bool getRecordingStatus();
4242
/** Activated or deactivates recording */
4343
void setRecordingStatus(bool enable);
4444

45+
/** Returns true if the GUI is acquiring data */
46+
bool getAcquisitionStatus();
47+
48+
/** Activates or deactivates data acquisition */
49+
void setAcquisitionStatus(bool enable);
50+
4551
/** Sends a string to the message bar */
4652
void sendStatusMessage(const String& text);
4753

@@ -59,6 +65,18 @@ int64 getGlobalTimestamp();
5965
/** Gets the software timestamp based on a high resolution timer aligned to the start of each processing block */
6066
int64 getSoftwareTimestamp();
6167

68+
/** Set new recording directory */
69+
void setRecordingDirectory(String dir);
70+
71+
/** Create new recording directory */
72+
void createNewRecordingDir();
73+
74+
/** Manually set the text to be prepended to the recording directory */
75+
void setPrependTextToRecordingDir(String text);
76+
77+
/** Manually set the text to be appended to the recording directory */
78+
void setAppendTextToRecordingDir(String text);
79+
6280
namespace RecordNode
6381
{
6482
/** Forces creation of new directory on recording */

Source/Processors/NetworkEvents/NetworkEvents.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,31 @@ String NetworkEvents::handleSpecialMessages(StringTS msg)
405405
}
406406
407407
*/
408-
return String("NotHandled");
408+
409+
/** Start/stop data acquisition */
410+
String s = msg.getString();
411+
412+
const MessageManagerLock mmLock;
413+
if (s.compareIgnoreCase("StartAcquisition") == 0)
414+
{
415+
if (!CoreServices::getAcquisitionStatus())
416+
{
417+
CoreServices::setAcquisitionStatus(true);
418+
}
419+
return String("StartedAcquisition");
420+
}
421+
else if (s.compareIgnoreCase("StopAcquisition") == 0)
422+
{
423+
if (CoreServices::getAcquisitionStatus())
424+
{
425+
CoreServices::setAcquisitionStatus(false);
426+
}
427+
return String("StoppedAcquisition");
428+
}
429+
else
430+
{
431+
return String("NotHandled");
432+
}
409433
}
410434

411435
void NetworkEvents::process(AudioSampleBuffer& buffer,
@@ -565,4 +589,4 @@ void NetworkEvents::createZmqContext()
565589
if (zmqcontext == nullptr)
566590
zmqcontext = zmq_ctx_new(); //<-- this is only available in version 3+
567591
#endif
568-
}
592+
}

Source/Processors/RecordControl/RecordControl.cpp

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,124 @@ void RecordControl::handleEvent(int eventType, MidiMessage& event, int)
110110
{
111111
CoreServices::setRecordingStatus(!CoreServices::getRecordingStatus());
112112
}
113+
}
114+
else if (eventType == MESSAGE)
115+
{
116+
handleNetworkEvent(event);
117+
}
118+
119+
}
120+
121+
void RecordControl::handleNetworkEvent(MidiMessage& event)
122+
{
123+
/** Extract network message from midi event */
124+
const uint8* dataptr = event.getRawData();
125+
int bufferSize = event.getRawDataSize();
126+
int len = bufferSize - 6; // 6 for initial event prefix
127+
String msg = String((const char*)(dataptr + 6), len);
128+
129+
/** Command is first substring */
130+
StringArray inputs = StringArray::fromTokens(msg, " ");
131+
String cmd = String(inputs[0]);
113132

133+
const MessageManagerLock mmLock;
114134

135+
if (String("StartRecord").compareIgnoreCase(cmd) == 0)
136+
{
137+
if (!CoreServices::getRecordingStatus())
138+
{
139+
/** First set optional parameters (name/value pairs)*/
140+
if (msg.contains("="))
141+
{
142+
String s = msg.substring(cmd.length());
143+
StringPairArray dict = parseNetworkMessage(s);
144+
145+
StringArray keys = dict.getAllKeys();
146+
for (int i=0; i<keys.size(); i++)
147+
{
148+
String key = keys[i];
149+
String value = dict[key];
150+
151+
if (key.compareIgnoreCase("CreateNewDir") == 0)
152+
{
153+
if (value.compareIgnoreCase("1") == 0)
154+
{
155+
CoreServices::createNewRecordingDir();
156+
}
157+
}
158+
else if (key.compareIgnoreCase("RecDir") == 0)
159+
{
160+
CoreServices::setRecordingDirectory(value);
161+
}
162+
else if (key.compareIgnoreCase("PrependText") == 0)
163+
{
164+
CoreServices::setPrependTextToRecordingDir(value);
165+
}
166+
else if (key.compareIgnoreCase("AppendText") == 0)
167+
{
168+
CoreServices::setAppendTextToRecordingDir(value);
169+
}
170+
}
171+
}
172+
173+
/** Start recording */
174+
CoreServices::setRecordingStatus(true);
175+
}
115176
}
177+
else if (String("StopRecord").compareIgnoreCase(cmd) == 0)
178+
{
179+
if (CoreServices::getRecordingStatus())
180+
{
181+
CoreServices::setRecordingStatus(false);
182+
}
183+
}
184+
}
185+
186+
StringPairArray RecordControl::parseNetworkMessage(String msg)
187+
{
188+
StringArray splitted;
189+
splitted.addTokens(msg, "=", "");
190+
191+
StringPairArray dict = StringPairArray();
192+
String key = "";
193+
String value = "";
194+
for (int i=0; i<splitted.size()-1; i++)
195+
{
196+
String s1 = splitted[i];
197+
String s2 = splitted[i+1];
198+
199+
/** Get key */
200+
if (!key.isEmpty())
201+
{
202+
if (s1.contains(" "))
203+
{
204+
int i1 = s1.lastIndexOf(" ");
205+
key = s1.substring(i1+1);
206+
}
207+
else
208+
{
209+
key = s1;
210+
}
211+
}
212+
else
213+
{
214+
key = s1.trim();
215+
}
216+
217+
/** Get value */
218+
if (i < splitted.size() - 2)
219+
{
220+
int i1 = s2.lastIndexOf(" ");
221+
value = s2.substring(0, i1);
222+
}
223+
else
224+
{
225+
value = s2;
226+
}
227+
228+
dict.set(key, value);
229+
}
230+
231+
return dict;
232+
}
116233

117-
}

Source/Processors/RecordControl/RecordControl.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626

2727
#include "../../../JuceLibraryCode/JuceHeader.h"
2828
#include "../GenericProcessor/GenericProcessor.h"
29+
#include "../NetworkEvents/NetworkEvents.h"
2930
#include "RecordControlEditor.h"
30-
#include "../RecordNode/RecordNode.h"
3131

3232
/**
3333
@@ -47,6 +47,11 @@ class RecordControl : public GenericProcessor
4747
void setParameter(int, float);
4848
void updateTriggerChannel(int newChannel);
4949
void handleEvent(int eventType, MidiMessage& event, int);
50+
void handleNetworkEvent(MidiMessage& event);
51+
52+
//* Split network message into name/value pairs (name1=val1 name2=val2 etc) */
53+
StringPairArray parseNetworkMessage(String msg);
54+
5055
bool enable();
5156

5257
bool isUtility()
@@ -67,4 +72,4 @@ class RecordControl : public GenericProcessor
6772

6873
};
6974

70-
#endif
75+
#endif

0 commit comments

Comments
 (0)