diff --git a/.gitignore b/.gitignore index 0a681f8..ffe6b33 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,5 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ +OS/01/conversations/user.json diff --git a/OS/01/conversations/user.json b/OS/01/conversations/user.json deleted file mode 100644 index 13088ff..0000000 --- a/OS/01/conversations/user.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "role": "user", - "type": "message", - "content": " Hello, how are you doing?\n" - }, - { - "role": "assistant", - "type": "message", - "content": "I'm an artificial intelligence, so I don't have feelings, but thank you for asking. How may I assist you today?" - } -] \ No newline at end of file diff --git a/OS/01/device.py b/OS/01/device.py index d51444a..6a1e24f 100644 --- a/OS/01/device.py +++ b/OS/01/device.py @@ -22,6 +22,10 @@ from interpreter import interpreter # Just for code execution. Maybe we should l from utils.kernel import put_kernel_messages_into_queue from stt import stt_wav +from utils.logs import setup_logging +from utils.logs import logger +setup_logging() + # Configuration for Audio Recording CHUNK = 1024 # Record in chunks of 1024 samples FORMAT = pyaudio.paInt16 # 16 bits per sample @@ -51,7 +55,7 @@ def record_audio(): """Record audio from the microphone and add it to the queue.""" stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK) - print("Recording started...") + logger.info("Recording started...") global RECORDING # Create a temporary WAV file to store the audio data @@ -69,7 +73,7 @@ def record_audio(): wav_file.close() stream.stop_stream() stream.close() - print("Recording stopped.") + logger.info("Recording stopped.") duration = wav_file.getnframes() / RATE if duration < 0.3: @@ -120,7 +124,7 @@ def on_release(key): if key == keyboard.Key.space: toggle_recording(False) elif key == keyboard.Key.esc: - print("Exiting...") + logger.info("Exiting...") os._exit(0) import asyncio @@ -137,7 +141,7 @@ async def websocket_communication(WS_URL): while True: try: async with websockets.connect(WS_URL) as websocket: - print("Press the spacebar to start/stop recording. Press ESC to exit.") + logger.info("Press the spacebar to start/stop recording. Press ESC to exit.") asyncio.create_task(message_sender(websocket)) initial_message = {"role": None, "type": None, "format": None, "content": None} @@ -146,16 +150,18 @@ async def websocket_communication(WS_URL): while True: message = await websocket.recv() - print("Got this message from the server:", type(message), message) + logger.debug(f"Got this message from the server: {type(message)} {message}") if type(message) == str: message = json.loads(message) if message.get("end"): - print(f"Complete message from the server: {message_so_far}") + logger.debug(f"Complete message from the server: {message_so_far}") + logger.info("\n") message_so_far = initial_message if "content" in message: + print(message['content'], end="", flush=True) if any(message_so_far[key] != message[key] for key in message_so_far if key != "content"): message_so_far = message else: @@ -183,7 +189,7 @@ async def websocket_communication(WS_URL): except: # traceback.print_exc() - print(f"Connecting to `{WS_URL}`...") + logger.info(f"Connecting to `{WS_URL}`...") await asyncio.sleep(2) diff --git a/OS/01/server.py b/OS/01/server.py index f24bdef..5986c48 100644 --- a/OS/01/server.py +++ b/OS/01/server.py @@ -22,6 +22,11 @@ from utils.kernel import put_kernel_messages_into_queue from i import configure_interpreter from interpreter import interpreter +from utils.logs import setup_logging +from utils.logs import logger +setup_logging() + + app = FastAPI() conversation_history_path = Path(__file__).parent / 'conversations' / 'user.json' @@ -64,10 +69,10 @@ if os.getenv('CODE_RUNNER') == "device": to_device.put({"role": "assistant", "type": "code", "format": "python", "end": True}) # Stream the response - print("Waiting for the device to respond...") + logger.info("Waiting for the device to respond...") while True: chunk = from_computer.get() - print("Server recieved from device:", chunk) + logger.info(f"Server received from device: {chunk}") if "end" in chunk: break yield chunk @@ -94,7 +99,7 @@ async def websocket_endpoint(websocket: WebSocket): await asyncio.gather(receive_task, send_task) except Exception as e: traceback.print_exc() - print(f"Connection lost. Error: {e}") + logger.info(f"Connection lost. Error: {e}") async def receive_messages(websocket: WebSocket): while True: @@ -109,7 +114,7 @@ async def receive_messages(websocket: WebSocket): async def send_messages(websocket: WebSocket): while True: message = await to_device.get() - print("Sending to the device:", type(message), message) + logger.debug(f"Sending to the device: {type(message)} {message}") await websocket.send_json(message) async def listener(): @@ -159,7 +164,7 @@ async def listener(): for chunk in interpreter.chat(messages, stream=True, display=False): - print("Got chunk:", chunk) + logger.debug("Got chunk:", chunk) # Send it to the user await to_device.put(chunk) @@ -195,7 +200,7 @@ async def listener(): with open(conversation_history_path, 'w') as file: json.dump(interpreter.messages, file, indent=4) - print("New user message recieved. Breaking.") + logger.info("New user message recieved. Breaking.") break # Also check if there's any new computer messages @@ -204,7 +209,7 @@ async def listener(): with open(conversation_history_path, 'w') as file: json.dump(interpreter.messages, file, indent=4) - print("New computer message recieved. Breaking.") + logger.info("New computer message recieved. Breaking.") break else: with open(conversation_history_path, 'w') as file: @@ -239,7 +244,7 @@ if __name__ == "__main__": if not server_url: raise ValueError("The environment variable SERVER_URL is not set. Please set it to proceed.") parsed_url = urllib.parse.urlparse(server_url) - print("Starting `server.py`...") + logger.info("Starting `server.py`...") config = Config(app, host=parsed_url.hostname, port=parsed_url.port, lifespan='on') server = Server(config) diff --git a/OS/01/start.sh b/OS/01/start.sh index 76cfcb3..8a4dcc6 100755 --- a/OS/01/start.sh +++ b/OS/01/start.sh @@ -25,6 +25,10 @@ export STT_RUNNER=device # If server, audio will be sent over websocket. # Will expose the server publically and display that URL. export SERVER_EXPOSE_PUBLICALLY=False +# Debug level +# export LOG_LEVEL=DEBUG +export LOG_LEVEL="INFO" + ### SETUP # if using local models, install the models / executables diff --git a/OS/01/stt.py b/OS/01/stt.py index 3929d06..aa14e24 100644 --- a/OS/01/stt.py +++ b/OS/01/stt.py @@ -11,6 +11,10 @@ import subprocess import openai from openai import OpenAI +from utils.logs import setup_logging +from utils.logs import logger +setup_logging() + client = OpenAI() def convert_mime_type_to_format(mime_type: str) -> str: @@ -81,10 +85,10 @@ def stt_wav(wav_file_path: str): response_format="text" ) except openai.BadRequestError as e: - print("openai.BadRequestError:", e) + logger.info(f"openai.BadRequestError: {e}") return None - print("Transcription result:", transcript) + logger.info(f"Transcription result: {transcript}") return transcript else: temp_dir = tempfile.gettempdir() diff --git a/OS/01/utils/kernel.py b/OS/01/utils/kernel.py index da7d55f..7f1bb3c 100644 --- a/OS/01/utils/kernel.py +++ b/OS/01/utils/kernel.py @@ -2,6 +2,10 @@ import asyncio import subprocess import platform +from utils.logs import setup_logging +from utils.logs import logger +setup_logging() + def get_kernel_messages(): """ Is this the way to do this? @@ -16,7 +20,7 @@ def get_kernel_messages(): with open('/var/log/dmesg', 'r') as file: return file.read() else: - print("Unsupported platform.") + logger.info("Unsupported platform.") def custom_filter(message): # Check for {TO_INTERPRETER{ message here }TO_INTERPRETER} pattern @@ -28,7 +32,7 @@ def custom_filter(message): elif 'USB' in message: return message # Check for network related keywords - elif any(keyword in message for keyword in ['network', 'IP', 'internet', 'LAN', 'WAN', 'router', 'switch']): + elif any(keyword in message for keyword in ['network', 'IP', 'internet', 'LAN', 'WAN', 'router', 'switch']) and "networkStatusForFlags" not in message: return message else: return None diff --git a/OS/01/utils/logs.py b/OS/01/utils/logs.py new file mode 100644 index 0000000..a73dea1 --- /dev/null +++ b/OS/01/utils/logs.py @@ -0,0 +1,22 @@ +import os +import logging + +logger: logging.Logger = logging.getLogger("01") +root_logger: logging.Logger = logging.getLogger() + + +def _basic_config() -> None: + logging.basicConfig( + format="%(message)s" + ) + + +def setup_logging() -> None: + env = os.environ.get("LOG_LEVEL", "").upper() + if env == "DEBUG": + _basic_config() + logger.setLevel(logging.DEBUG) + root_logger.setLevel(logging.DEBUG) + elif env == "INFO": + _basic_config() + logger.setLevel(logging.INFO) diff --git a/README.md b/README.md index 24057cf..5350605 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ sudo apt-get install portaudio19-dev libav-tools ```bash python -m pip install -r requirements.txt ``` +NB: Depending on your local Python version, you may run into [this issue↗](https://github.com/TaylorSMarks/playsound/issues/150) installing playsound. Workarounds are provided in the issue. If you want to run local speech-to-text from whisper, download the GGML Whisper model from [Huggingface](https://huggingface.co/ggerganov/whisper.cpp). Then in `OS/01/start.sh`, set `ALL_LOCAL=TRUE` and set `WHISPER_MODEL_PATH` to the path of the model. diff --git a/hardware/devices/jetson-nano/README.md b/hardware/devices/jetson-nano/README.md new file mode 100644 index 0000000..600bda4 --- /dev/null +++ b/hardware/devices/jetson-nano/README.md @@ -0,0 +1,22 @@ +# Development Setup for Jetson Nano + +1. Go through the tutorial here: https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit#intro + +2. At the end of that guide, you should have a Jetson running off a power supply or micro USB. + +3. Get network connectivity. The Jetson does not have a WiFi module so you will need to plug in ethernet. + If you have a laptop, you can share internet access over Ethernet. + + To do this with Mac, do the following: + + a. Plug a cable from the Jetson Ethernet port to your Mac (you can use a Ethernet -> USB converter for your Mac). + + b. Go to General->Sharing, then click the little `(i)` icon next to "Internet Sharing", and check all the options. + + ![](mac-share-internet.png) + + c. Go back to General->Sharing, and turn on "Internet Sharing". + + ![](mac-share-internet-v2.png) + + d. Now the Jetson should have connectivity! \ No newline at end of file diff --git a/hardware/devices/jetson-nano/mac-share-internet-v2.png b/hardware/devices/jetson-nano/mac-share-internet-v2.png new file mode 100644 index 0000000..74e1de4 Binary files /dev/null and b/hardware/devices/jetson-nano/mac-share-internet-v2.png differ diff --git a/hardware/devices/jetson-nano/mac-share-internet.png b/hardware/devices/jetson-nano/mac-share-internet.png new file mode 100644 index 0000000..51aaa5d Binary files /dev/null and b/hardware/devices/jetson-nano/mac-share-internet.png differ