Skip to content

Commit 81515ec

Browse files
committed
WIP - compatibility with net station
1 parent f22ce84 commit 81515ec

11 files changed

Lines changed: 274 additions & 35 deletions

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Usage
44

55
This program should work with any amplifier that works with the AmpServer produced by EGI (http://www.egi.com/).
6+
All communication with the amplifier happens through the Amp Server Pro SDK, [documented here](https://www.egi.com/images/stories/manuals/amp-server-pro-sdk-3-0-network-apis-user-guide-rev-01.pdf).
67

78
* Make sure that your AmpServer is running and can correctly record from its connected amplifier(s). To connect to the Amp Server you need to purchase the Amp Server Pro SDK, see: [ftp://ftp.egi.com/pub/documentation/placards/AS_guide_8409503-50_20100421.pdf](ftp://ftp.egi.com/pub/documentation/placards/AS_guide_8409503-50_20100421.pdf), otherwise the LSL application will not work.
89

@@ -44,3 +45,27 @@ To use the mock server for development:
4445
> # or update ampserver_config.cfg to use 127.0.0.1 and run GUI
4546
4647
The mock server generates synthetic sine waves (10-50 Hz) with noise for the EEG data, so you can also verify the LSL stream in downstream applications.
48+
49+
# Known Issues
50+
51+
## Net Station Acquisition Compatibility
52+
53+
When using this application alongside Net Station Acquisition:
54+
55+
- **Start Net Station first**: If you plan to use Net Station Acquisition, start it and initialize the amplifier (click "On") BEFORE connecting this app. Our app will detect the running amplifier and automatically use its sample rate.
56+
57+
- **Do not start Net Station after**: If this app is already streaming and Net Station subsequently initializes the amplifier at a different sample rate, our app cannot detect this change. AmpServer only sends notifications to one subscriber, and Net Station consumes them when it's running.
58+
59+
- **Recommended workflow**:
60+
1. Start Net Station Acquisition
61+
2. Initialize the amplifier at your desired sample rate (click "On")
62+
3. Start EGIAmpServer and click "Link" - it will detect the running amp and match its sample rate
63+
4. Both applications will now receive data at the correct rate
64+
65+
## Dropped Packets After Device Shutdown
66+
67+
After Net Station shuts down the amplifier (via "Shutdown" command), immediately starting this app may result in excessive dropped packets and eventual stream loss. This appears to be related to stale data in the connection. **Workaround**: Wait a moment and restart the app, or power cycle the amplifier.
68+
69+
## Sample Rate Auto-Detection
70+
71+
The app automatically detects the sample rate when connecting to an already-running amplifier by measuring packet timing. This detection snaps to standard rates (250, 500, or 1000 Hz). If the amplifier is idle when connecting, the app uses the sample rate configured in the UI/config file.

src/cli/main.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ void printUsage(const char* programName) {
3131
<< " --data-port <port> Data port (default: 9879)\n"
3232
<< " --amp-id <id> Amplifier ID (default: 0)\n"
3333
<< " --sample-rate <hz> Sample rate (default: 1000)\n"
34-
<< " --listen-only Don't initialize amp, just listen (for multi-client)\n"
3534
<< " --help Show this help message\n";
3635
}
3736

@@ -58,8 +57,6 @@ int main(int argc, char* argv[]) {
5857
config.amplifierId = std::stoi(argv[++i]);
5958
} else if (arg == "--sample-rate" && i + 1 < argc) {
6059
config.sampleRate = std::stoi(argv[++i]);
61-
} else if (arg == "--listen-only") {
62-
config.listenOnly = true;
6360
} else {
6461
std::cerr << "Unknown option: " << arg << std::endl;
6562
printUsage(argv[0]);
@@ -108,7 +105,6 @@ int main(int argc, char* argv[]) {
108105
<< " Data Port: " << config.dataPort << "\n"
109106
<< " Amplifier ID: " << config.amplifierId << "\n"
110107
<< " Sample Rate: " << config.sampleRate << " Hz\n"
111-
<< " Listen Only: " << (config.listenOnly ? "yes" : "no") << "\n"
112108
<< "Press Ctrl+C to stop.\n\n";
113109

114110
// Connect and stream

src/core/include/egiamp/AmpServerConfig.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ struct AmpServerConfig {
1515
int amplifierId = 0;
1616
int sampleRate = 1000;
1717

18-
// If true, just listen to an already-running amp without initializing it.
19-
// This allows multiple clients to connect without disrupting each other.
20-
bool listenOnly = false;
21-
2218
static AmpServerConfig loadFromFile(const std::string& filename);
2319
void saveToFile(const std::string& filename) const;
2420
};

src/core/include/egiamp/AmpServerConnection.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class AmpServerConnection {
2626
void disconnect();
2727
bool isConnected() const;
2828

29+
// Reconnect just the data stream (used to reset stream position after detection)
30+
bool reconnectDataStream();
31+
2932
std::string sendCommand(const std::string& command, int ampId,
3033
int channel = 0, const std::string& value = "0");
3134
void sendDatastreamCommand(const std::string& command, int ampId,
@@ -50,6 +53,8 @@ class AmpServerConnection {
5053
socket_stream notificationStream_;
5154
socket_stream dataStream_;
5255
std::atomic<bool> connected_{false};
56+
std::string serverAddress_;
57+
uint16_t dataPort_{0};
5358
};
5459

5560
} // namespace egiamp

src/core/include/egiamp/EGIAmpClient.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ class EGIAmpClient {
4646
bool startStreaming();
4747
void stopStreaming();
4848

49+
// Query amp state - returns true if amp is already running, updates detectedSampleRate
50+
bool queryAmpState();
51+
int detectedSampleRate() const { return detectedSampleRate_; }
52+
bool ampWasRunning() const { return ampWasRunning_; }
53+
4954
const AmplifierDetails& amplifierDetails() const { return details_; }
5055

5156
void setStatusCallback(StatusCallback cb) { statusCallback_ = std::move(cb); }
@@ -60,6 +65,8 @@ class EGIAmpClient {
6065
void emitChannelCount(int count);
6166

6267
bool queryAmplifierDetails();
68+
bool isAmplifierStreaming();
69+
int detectSampleRate(); // Returns detected sample rate, or 0 if detection failed
6370
bool initAmplifier();
6471
void haltAmplifier();
6572

@@ -73,6 +80,10 @@ class EGIAmpClient {
7380
AmplifierDetails details_;
7481

7582
std::atomic<bool> stopFlag_{false};
83+
std::atomic<bool> sampleRateChangeDetected_{false}; // Set when ntn_AmpStarted received
84+
bool weInitializedAmp_{false}; // Track if we initialized the amp (vs. it was already running)
85+
bool ampWasRunning_{false}; // Was the amp running when we queried state?
86+
int detectedSampleRate_{0}; // Sample rate detected from running amp
7687
std::unique_ptr<std::thread> readerThread_;
7788
std::unique_ptr<std::thread> notificationThread_;
7889

src/core/src/AmpServerConfig.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ AmpServerConfig AmpServerConfig::loadFromFile(const std::string& filename) {
4040
if (auto node = settings.child("samplingrate")) {
4141
config.sampleRate = node.text().as_int(config.sampleRate);
4242
}
43-
if (auto node = settings.child("listenonly")) {
44-
config.listenOnly = node.text().as_bool(config.listenOnly);
45-
}
4643
}
4744

4845
return config;
@@ -62,7 +59,6 @@ void AmpServerConfig::saveToFile(const std::string& filename) const {
6259
auto settings = doc.append_child("settings");
6360
settings.append_child("amplifierid").text().set(amplifierId);
6461
settings.append_child("samplingrate").text().set(sampleRate);
65-
settings.append_child("listenonly").text().set(listenOnly);
6662

6763
if (!doc.save_file(filename.c_str())) {
6864
throw ConfigError("Could not write to config file: " + filename);

src/core/src/AmpServerConnection.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ AmpServerConnection::~AmpServerConnection() {
1111
bool AmpServerConnection::connect(const std::string& address, uint16_t cmdPort,
1212
uint16_t notifyPort, uint16_t dataPort) {
1313
try {
14+
// Store for potential reconnection
15+
serverAddress_ = address;
16+
dataPort_ = dataPort;
17+
1418
// Connect command stream
1519
commandStream_.clear();
1620
commandStream_.exceptions(std::ios::eofbit | std::ios::failbit | std::ios::badbit);
@@ -36,6 +40,18 @@ bool AmpServerConnection::connect(const std::string& address, uint16_t cmdPort,
3640
}
3741
}
3842

43+
bool AmpServerConnection::reconnectDataStream() {
44+
try {
45+
dataStream_.close();
46+
dataStream_.clear();
47+
dataStream_.expires_after(std::chrono::seconds(5));
48+
dataStream_.connect(serverAddress_, std::to_string(dataPort_));
49+
return true;
50+
} catch (...) {
51+
return false;
52+
}
53+
}
54+
3955
void AmpServerConnection::disconnect() {
4056
connected_ = false;
4157
commandStream_.close();

0 commit comments

Comments
 (0)