Skip to content

Commit 9fb2f3c

Browse files
committed
CSV Logging option added
1 parent da062fc commit 9fb2f3c

File tree

3 files changed

+178
-13
lines changed

3 files changed

+178
-13
lines changed

app.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import queue
99
import threading
1010
import time
11+
from datetime import datetime
1112

1213
console_queue = queue.Queue()
1314
app = Flask(__name__)
@@ -138,22 +139,36 @@ def disconnect_device():
138139
@app.route('/start_recording', methods=['POST'])
139140
def start_recording():
140141
global connection_manager
141-
if connection_manager and connection_manager.stream_active:
142-
try:
143-
connection_manager.start_csv_recording()
142+
if not connection_manager:
143+
return jsonify({'status': 'error', 'message': 'No active connection'}), 400
144+
145+
data = request.get_json()
146+
filename = data.get('filename')
147+
148+
# If filename is empty or None, let connection_manager use default
149+
if filename == "":
150+
filename = None
151+
152+
try:
153+
if connection_manager.start_csv_recording(filename):
154+
post_console_message(f"Recording started: {filename or 'default filename'}")
144155
return jsonify({'status': 'recording_started'})
145-
except Exception as e:
146-
return jsonify({'status': 'error', 'message': str(e)}), 500
147-
return jsonify({'status': 'error', 'message': 'No active stream'}), 400
156+
return jsonify({'status': 'error', 'message': 'Failed to start recording'}), 500
157+
except Exception as e:
158+
logging.error(f"Recording error: {str(e)}")
159+
return jsonify({'status': 'error', 'message': str(e)}), 500
148160

149161
@app.route('/stop_recording', methods=['POST'])
150162
def stop_recording():
151163
global connection_manager
152164
if connection_manager:
153165
try:
154-
connection_manager.stop_csv_recording()
155-
return jsonify({'status': 'recording_stopped'})
166+
if connection_manager.stop_csv_recording():
167+
post_console_message("Recording stopped")
168+
return jsonify({'status': 'recording_stopped'})
169+
return jsonify({'status': 'error', 'message': 'Failed to stop recording'}), 500
156170
except Exception as e:
171+
logging.error(f"Stop recording error: {str(e)}")
157172
return jsonify({'status': 'error', 'message': str(e)}), 500
158173
return jsonify({'status': 'error', 'message': 'No active connection'}), 400
159174

connection.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,17 @@ def setup_lsl(self, num_channels, sampling_rate):
5555
self.stream_active = True
5656
self.num_channels = num_channels
5757

58-
def start_csv_recording(self):
58+
def start_csv_recording(self, filename=None):
5959
if self.recording_active:
6060
return False
6161

6262
try:
63-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
64-
filename = f"ChordsPy_{timestamp}.csv"
63+
if not filename:
64+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
65+
filename = f"ChordsPy_{timestamp}.csv"
66+
elif not filename.endswith('.csv'):
67+
filename += '.csv'
68+
6569
self.csv_file = open(filename, 'w', newline='')
6670
headers = ['Counter'] + [f'Channel{i+1}' for i in range(self.num_channels)]
6771
self.csv_writer = csv.writer(self.csv_file)

templates/index.html

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656

5757
<div class="hidden md:block self-stretch w-px bg-gray-300 dark:bg-gray-700 mx-4"></div>
5858

59-
<input type="text" id="filename" value="ChordsPy"
59+
<input type="text" id="filename"
6060
class="flex-1 h-12 px-4 border-r-0 rounded-l-lg focus:outline-none focus:ring-0 bg-gray-100 border border-gray-110 dark:bg-gray-800 dark:border-gray-700 dark:text-white"
6161
placeholder="Enter recording name">
6262
<span class="h-12 px-4 bg-gray-200 text-gray-500 rounded-r-lg dark:bg-gray-700 dark:text-gray-400 inline-flex items-center justify-center text-base">
@@ -66,6 +66,8 @@
6666
<button id="record-btn" class="h-12 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors font-semibold px-6">
6767
Start Recording
6868
</button>
69+
<div id="recording-status" class="flex items-center hidden">
70+
</div>
6971
</div>
7072
<div id="connection-status" class="mt-2 text-sm text-gray-600 dark:text-gray-400 hidden">
7173
<span id="status-icon" class="mr-2"></span>
@@ -182,6 +184,7 @@ <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-1">${app.ti
182184
const disconnectBtn = document.getElementById('disconnect-btn');
183185
const recordBtn = document.getElementById('record-btn');
184186
const filenameInput = document.getElementById('filename');
187+
const recordingStatus = document.getElementById('recording-status');
185188
const statusDiv = document.getElementById('connection-status');
186189
const statusText = document.getElementById('status-text');
187190
const statusIcon = document.getElementById('status-icon');
@@ -193,9 +196,48 @@ <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-1">${app.ti
193196
let selectedProtocol = null;
194197
let selectedBleDevice = null;
195198
let isConnected = false;
199+
let isRecording = false;
196200
let eventSource = null;
197201
let isScanning = false;
198202

203+
// Function to generate timestamp for filename
204+
function getTimestamp() {
205+
const now = new Date();
206+
const year = now.getFullYear();
207+
const month = String(now.getMonth() + 1).padStart(2, '0');
208+
const day = String(now.getDate()).padStart(2, '0');
209+
const hours = String(now.getHours()).padStart(2, '0');
210+
const minutes = String(now.getMinutes()).padStart(2, '0');
211+
const seconds = String(now.getSeconds()).padStart(2, '0');
212+
return `${year}${month}${day}_${hours}${minutes}${seconds}`;
213+
}
214+
215+
// Initialize filename with default value
216+
function initializeFilename() {
217+
const defaultName = `ChordsPy_${getTimestamp()}`;
218+
filenameInput.value = defaultName;
219+
filenameInput.placeholder = defaultName;
220+
}
221+
222+
// Sanitize filename input - replace spaces and dots with underscores
223+
function sanitizeFilename(filename) {
224+
// Remove leading/trailing whitespace
225+
filename = filename.trim();
226+
// Replace spaces and dots with underscores
227+
filename = filename.replace(/[\s.]+/g, '_');
228+
return filename;
229+
}
230+
231+
// Handle filename input changes
232+
filenameInput.addEventListener('input', function() {
233+
// Only sanitize for display (don't modify the actual value)
234+
const cursorPos = this.selectionStart;
235+
const displayValue = sanitizeFilename(this.value);
236+
this.value = displayValue;
237+
// Restore cursor position after display update
238+
this.setSelectionRange(cursorPos, cursorPos);
239+
});
240+
199241
// Handle protocol selection
200242
connectionBtns.forEach(btn => {
201243
btn.addEventListener('click', () => {
@@ -416,6 +458,8 @@ <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-1">${app.ti
416458

417459
showStatus(`Connected via ${selectedProtocol.toUpperCase()}`, 'fa-check-circle', 'text-green-500');
418460

461+
// Start console updates
462+
startConsoleUpdates();
419463
}
420464

421465
// Handle disconnect button
@@ -444,18 +488,108 @@ <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-1">${app.ti
444488
statusDiv.classList.add('hidden');
445489
selectedProtocol = null;
446490
selectedBleDevice = null;
491+
492+
// Stop recording if active
493+
if (isRecording) {
494+
toggleRecording();
495+
}
496+
497+
// Stop console updates
498+
if (eventSource) {
499+
eventSource.close();
500+
eventSource = null;
501+
}
447502
}
448503
} catch (error) {
449504
console.error('Disconnection error:', error);
450-
showStatus('Disconnection failed', 'fa-times-circle', 'text-red-500');
451505
// Return to disconnect state if disconnection failed
452506
connectingBtn.classList.add('hidden');
453507
disconnectBtn.classList.remove('hidden');
454508
}
455509
});
456510

511+
// Start console updates via SSE
512+
function startConsoleUpdates() {
513+
if (eventSource) {
514+
eventSource.close();
515+
}
516+
517+
eventSource = new EventSource('/console_updates');
518+
519+
eventSource.onmessage = function(e) {
520+
console.log('Console update:', e.data);
521+
};
522+
523+
eventSource.onerror = function() {
524+
console.error('EventSource failed');
525+
if (eventSource) {
526+
eventSource.close();
527+
eventSource = null;
528+
}
529+
};
530+
}
531+
532+
// Handle record button
533+
recordBtn.addEventListener('click', toggleRecording);
534+
535+
// In the toggleRecording function
536+
function toggleRecording() {
537+
if (!isConnected) {
538+
showAlert('Please Start a stream before recording.');
539+
return;
540+
}
541+
542+
// Get the filename (already sanitized in the display)
543+
let filename = filenameInput.value.trim();
544+
545+
// If empty, use default (pass null to let server generate default)
546+
if (filename === '') {
547+
filename = null;
548+
}
549+
550+
if (isRecording) {
551+
// Stop recording
552+
fetch('/stop_recording', { method: 'POST' })
553+
.then(response => response.json())
554+
.then(data => {
555+
if (data.status === 'recording_stopped') {
556+
isRecording = false;
557+
recordBtn.innerHTML = 'Start Recording';
558+
recordBtn.classList.remove('bg-gray-500');
559+
recordBtn.classList.add('bg-red-500', 'hover:bg-red-600');
560+
recordingStatus.classList.add('hidden');
561+
}
562+
})
563+
.catch(error => {
564+
console.error('Error stopping recording:', error);
565+
});
566+
} else {
567+
// Start recording - send the filename (or null for default)
568+
fetch('/start_recording', {
569+
method: 'POST',
570+
headers: {'Content-Type': 'application/json', },
571+
body: JSON.stringify({ filename: filename })
572+
})
573+
.then(response => response.json())
574+
.then(data => {
575+
if (data.status === 'recording_started') {
576+
isRecording = true;
577+
recordBtn.innerHTML = 'Stop Recording';
578+
recordBtn.classList.remove('bg-red-500', 'hover:bg-red-600');
579+
recordBtn.classList.add('bg-gray-500');
580+
recordingStatus.classList.remove('hidden');
581+
}
582+
})
583+
.catch(error => {
584+
console.error('Error starting recording:', error);
585+
showAlert('Failed to start recording: ' + error.message);
586+
});
587+
}
588+
}
589+
457590
// Initial setup
458591
connectBtn.disabled = true; // Disable connect button until protocol is selected
592+
initializeFilename(); // Set default filename with timestamp
459593

460594
function showStatus(text, icon, colorClass) {
461595
statusText.textContent = text;
@@ -478,6 +612,7 @@ <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-1">${app.ti
478612
connectBtn.classList.add('hidden');
479613
connectingBtn.classList.add('hidden');
480614
disconnectBtn.classList.remove('hidden');
615+
startConsoleUpdates();
481616
}
482617
} else {
483618
// If not connected, update the frontend
@@ -486,6 +621,17 @@ <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-1">${app.ti
486621
disconnectBtn.classList.add('hidden');
487622
connectingBtn.classList.add('hidden');
488623
connectBtn.classList.remove('hidden');
624+
625+
// Stop recording if active
626+
if (isRecording) {
627+
toggleRecording();
628+
}
629+
630+
// Stop console updates
631+
if (eventSource) {
632+
eventSource.close();
633+
eventSource = null;
634+
}
489635
}
490636
}
491637
})

0 commit comments

Comments
 (0)