Conversation
alexstrangeparts
commented
Nov 25, 2025
- user sends tts message
- server queues tts message, sets “tts in progress flag” high
- robot plays message in thread
- based on a formula calculate how much time should pass, then send TTS_FINISHED message back to server
- server de-queues next message and rinse and repeat
controller.py
Outdated
| log.debug("on_handle_chat_message(data)") | ||
| if data['type'] == 'message': | ||
| word_count = len(data['message'].split()) | ||
| time_till_next_tts = (word_count * 1/2.5) + time.time() # How long in seconds a message should roughly take to read out at a rate of 150words/m |
There was a problem hiding this comment.
Is there not a way to actually know when the tts playback finishes?
There was a problem hiding this comment.
no unfortunately not that i could do
There was a problem hiding this comment.
Can't you just wait for the thread created at
Line 294 in 04ba02c
There was a problem hiding this comment.
but then wouldn't that block on the main thread? or is there a way to do a callback for when the thread returns?
There was a problem hiding this comment.
changed in ac806fd to use thread.is_alive()
controller.py
Outdated
| @@ -437,8 +441,10 @@ def gps_is_enabled(): | |||
| while not terminate.locked(): # Controller loop starts here | |||
There was a problem hiding this comment.
Is this the main control loop? If so, why isnt anythign else happening in it?
There was a problem hiding this comment.
everything else is based on the async websocket. The only thing that happens in this loop atm is the gps geofence. Nothing else needs to happen
There was a problem hiding this comment.
Okay. That makes sense. Can you write a comment to that effect?
ca0b6cb to
d9fc165
Compare
There was a problem hiding this comment.
Pull request overview
This PR implements a queuing mechanism for TTS (text-to-speech) messages on the robot. The changes allow the robot to notify the server when TTS playback completes, enabling proper message queuing.
Changes:
- Added
declareFinishedTTS()function to send completion notification to server - Modified message handling to only process 'message' type events for TTS
- Added tracking of TTS thread state and automatic completion notification in main loop
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| networking.py | Added declareFinishedTTS() function to send TTS_FINISHED event to server |
| controller.py | Modified chat message handling to track TTS thread and notify server on completion |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
networking.py
Outdated
| def declareFinishedTTS(): | ||
| log.info("Finishing TTS message") | ||
| webSocket.send(json.dumps( | ||
| {"e": "TTS_FINISHED", | ||
| "d": {} | ||
| })) |
There was a problem hiding this comment.
Missing closing parenthesis for json.dumps() call. This will cause a syntax error.
controller.py
Outdated
| global tts_thread | ||
| if chat_module == None: | ||
| thread.start_new_thread(handle_chat_message, args) | ||
| tts_thread = thread.start_new_thread(handle_chat_message, args) |
There was a problem hiding this comment.
thread.start_new_thread() returns a thread identifier (integer), not a thread object with an is_alive() method. This will cause an AttributeError at line 442 when trying to call tts_thread.is_alive(). Consider using the threading module's Thread class instead, which provides the is_alive() method.
controller.py
Outdated
| watchdog.watch() | ||
|
|
||
| if robot_config.getboolean('gps', 'enable_gps'): | ||
| if not tts_thread.is_alive(): |
There was a problem hiding this comment.
This code will attempt to check thread status even when tts_thread is None (its initial value). This will cause an AttributeError. Add a null check: if tts_thread is not None and not tts_thread.is_alive():
| if not tts_thread.is_alive(): | |
| if tts_thread is not None and not tts_thread.is_alive(): |
| if not test_mode: | ||
| log.critical('RemoTV Controller Started') | ||
| while not terminate.locked(): # Controller loop starts here | ||
| while not terminate.locked(): # Main control loop. This handles anythign that involves timing, everything else is asynchronous |
There was a problem hiding this comment.
Corrected spelling of 'anythign' to 'anything'.
| while not terminate.locked(): # Main control loop. This handles anythign that involves timing, everything else is asynchronous | |
| while not terminate.locked(): # Main control loop. This handles anything that involves timing, everything else is asynchronous |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
controller.py
Outdated
| tts_thread = thread.start_new_thread(handle_chat_message, args) | ||
| else: | ||
| thread.start_new_thread(chat_module.handle_chat, args) | ||
| tts_thread = thread.start_new_thread(chat_module.handle_chat, args) |
There was a problem hiding this comment.
The return value of thread.start_new_thread() is an integer thread ID, not a Thread object. This means calling .is_alive() on tts_thread at line 442 will raise an AttributeError. To fix this, you should use the threading.Thread class instead, similar to how it's done in watchdog.py and gps.py in this codebase.
controller.py
Outdated
| watchdog.watch() | ||
|
|
||
| if robot_config.getboolean('gps', 'enable_gps'): | ||
| if not tts_thread.is_alive(): |
There was a problem hiding this comment.
The global variable tts_thread is initialized to None, but the main loop at line 442 attempts to call .is_alive() on it without checking if it's None first. This will cause an AttributeError when the program starts (before any TTS message is received). You need to add a None check before calling .is_alive().
| if not tts_thread.is_alive(): | |
| if tts_thread is not None and not tts_thread.is_alive(): |
controller.py
Outdated
| if not tts_thread.is_alive(): | ||
| networking.declareFinishedTTS() |
There was a problem hiding this comment.
This logic will continuously send TTS_FINISHED messages to the server every second whenever there is no active TTS thread (including at startup before any TTS message has been received). According to the PR description, TTS_FINISHED should only be sent after a TTS message has completed. You should add a flag to track whether a TTS message is actually in progress and only send TTS_FINISHED when transitioning from active to inactive.
ac806fd to
fcdc551
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| if mute: | ||
| exit() | ||
| return False |
There was a problem hiding this comment.
Inconsistent handling of early termination: When mute is True, the function now returns False (line 111), but when a URL is filtered, it still calls exit() on line 139. This is inconsistent and exit() would terminate the entire process, not just the thread. For consistency and correctness, line 139 should also return False instead of calling exit().
| if data['type'] == 'message': | ||
| on_handle_chat_message(data) |
There was a problem hiding this comment.
The message type filtering logic has been changed from data['type'] != "robot" (exclude robot messages) to data['type'] == 'message' (only process message type). This is a significant behavioral change that could affect what messages get processed. The old logic would process any message type except "robot", while the new logic only processes messages with type exactly equal to "message". If there are other message types (e.g., "user", "system", etc.) that were previously processed, they will now be ignored. Verify this change is intentional and won't break existing functionality.
| if data['type'] == 'message': | |
| on_handle_chat_message(data) | |
| msg_type = data.get('type') | |
| if msg_type is None: | |
| log.error("Malformed message: missing 'type' field in MESSAGE_RECEIVED data: %s", data) | |
| elif msg_type != 'robot': | |
| on_handle_chat_message(data) |
| def declareFinishedTTS(): | ||
| log.info("Finishing TTS message") | ||
| webSocket.send(json.dumps( | ||
| { | ||
| "e": "TTS_FINISHED", | ||
| "d": {} | ||
| })) |
There was a problem hiding this comment.
Potential AttributeError: The function attempts to call webSocket.send() without checking if webSocket is None. If this function is called before the websocket is initialized, it will raise an AttributeError. Consider adding a check like if webSocket is None: return or if webSocket: before attempting to send.
| global tts_thread | ||
| if chat_module is None: | ||
| target = handle_chat_message | ||
| else: | ||
| target = chat_module.handle_chat | ||
|
|
||
| t = threading.Thread(target=target, args=args) | ||
| t.daemon = True # OK in Py2/Py3 | ||
| t.start() | ||
| tts_thread = t |
There was a problem hiding this comment.
Race condition: The tts_thread global variable can be accessed by multiple threads without synchronization. When a new chat message arrives while a TTS thread is still running, line 329 will overwrite the tts_thread reference, losing track of the previous thread. This breaks the queueing mechanism described in the PR, as only the most recently created thread will be tracked. Consider using a queue data structure (like collections.deque) to properly queue TTS messages, or add a lock to prevent concurrent access to tts_thread.
| t = threading.Thread(target=target, args=args) | ||
| t.daemon = True # OK in Py2/Py3 | ||
| t.start() | ||
| tts_thread = t |
There was a problem hiding this comment.
The implementation doesn't actually queue TTS messages as described in the PR description. The current code only tracks a single thread reference (tts_thread), which gets overwritten each time a new message arrives. This means if multiple TTS messages arrive rapidly, only the last one will be tracked for completion notification. To properly implement queueing, you should: 1) Check if tts_thread is alive before starting a new thread, 2) Either queue the message for later processing or implement a proper queue mechanism with a worker thread that processes messages sequentially.
| if tts_thread and not tts_thread.is_alive(): | ||
| networking.declareFinishedTTS() | ||
| tts_thread = None |
There was a problem hiding this comment.
Race condition in the main loop: The check for tts_thread.is_alive() on line 448 can fail if tts_thread is set to None by line 450 between the time it's checked and when is_alive() is called. Additionally, another thread could overwrite tts_thread on line 329 while this check is executing. This should be protected with proper synchronization, such as storing the thread reference in a local variable or using a lock.
| if tts_thread and not tts_thread.is_alive(): | |
| networking.declareFinishedTTS() | |
| tts_thread = None | |
| current_tts_thread = tts_thread | |
| if current_tts_thread and not current_tts_thread.is_alive(): | |
| networking.declareFinishedTTS() | |
| if tts_thread is current_tts_thread: | |
| tts_thread = None |