chore: cleanup for merging/ switching to later branches
This commit is contained in:
83
README.md
83
README.md
@@ -2,88 +2,6 @@
|
|||||||
### Linux (or WSL)
|
### Linux (or WSL)
|
||||||
Start off by installing [Pyenv](https://github.com/pyenv/pyenv?tab=readme-ov-file#installation) and walk through the steps outlined there (be sure to also add it to PATH). Also install the [Python build requirements](https://github.com/pyenv/pyenv/wiki#suggested-build-environment). Afterwards, install Python 2.7 and activate it for your current shell:
|
Start off by installing [Pyenv](https://github.com/pyenv/pyenv?tab=readme-ov-file#installation) and walk through the steps outlined there (be sure to also add it to PATH). Also install the [Python build requirements](https://github.com/pyenv/pyenv/wiki#suggested-build-environment). Afterwards, install Python 2.7 and activate it for your current shell:
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
```bash
|
|
||||||
pyenv install 2.7
|
|
||||||
pyenv shell 2.7
|
|
||||||
```
|
|
||||||
|
|
||||||
You can check that this worked by typing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -V
|
|
||||||
```
|
|
||||||
|
|
||||||
Which should return `Python 2.7.18`.
|
|
||||||
|
|
||||||
Next, `cd` into this repository and create (and activate) a virtual environment:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd <path to project>/
|
|
||||||
python -m pip install virtualenv
|
|
||||||
python -m virtualenv .venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
```
|
|
||||||
|
|
||||||
To be able to install the PyAudio Python package, you'll need to have the `portaudio` system package installed. On Debian or Ubuntu:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo apt install portaudio19-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Then you can install the required packages with
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we need to install the NaoQi SDK into our virtual environment, which we need to do manually. Begin by downloading the SDK:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wget https://community-static.aldebaran.com/resources/2.5.10/Python%20SDK/pynaoqi-python2.7-2.5.7.1-linux64.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, move into the `site-packages` directory and extract the file you just downloaded:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd .venv/lib/python2.7/site-packages/
|
|
||||||
tar xvfz <path to SDK>/pynaoqi-python2.7-2.5.7.1-linux64.tar.gz
|
|
||||||
rm <path to SDK>/pynaoqi-python2.7-2.5.7.1-linux64.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
Lastly, we need to inform our virtual environment where to find our newly installed package:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
echo <path to project>/.venv/lib/python2.7/site-packages/pynaoqi-python2.7-2.5.7.1-linux64/lib/python2.7/site-packages/ > pynaoqi-python2.7.pth
|
|
||||||
```
|
|
||||||
|
|
||||||
That's it! Verify that it works with
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -c "import qi; print(qi)"
|
|
||||||
```
|
|
||||||
|
|
||||||
You should now be able to run this project.
|
|
||||||
|
|
||||||
### MacOS
|
|
||||||
|
|
||||||
On ARM CPU's, pyenv doesn't want to install Python 2. You can download and install it from [the Python website](https://www.python.org/downloads/release/python-2718/).
|
|
||||||
|
|
||||||
Create a virtual environment as described in the Linux section.
|
|
||||||
|
|
||||||
Then build `portaudio` for x86_64 CPU's.
|
|
||||||
|
|
||||||
Then follow the remaining installation instructions in the Linux section.
|
|
||||||
|
|
||||||
## Running
|
|
||||||
Assuming you have the virtual environment activated (`source .venv/bin/activate` on Linux) and that you have a virtual robot running on localhost you should be able to run this project by typing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python main.py --qi-url tcp://localhost:<port>
|
|
||||||
```
|
|
||||||
|
|
||||||
where `<port>` is the port on which your robot is running.
|
|
||||||
=======
|
|
||||||
The robot interface is a high-level API for controlling the robot. It implements the API as designed: https://utrechtuniversity.youtrack.cloud/articles/N25B-A-14/RI-CB-Communication.
|
The robot interface is a high-level API for controlling the robot. It implements the API as designed: https://utrechtuniversity.youtrack.cloud/articles/N25B-A-14/RI-CB-Communication.
|
||||||
|
|
||||||
This is an implementation for the Pepper robot, using the Pepper SDK and Python 2.7 as required by the SDK.
|
This is an implementation for the Pepper robot, using the Pepper SDK and Python 2.7 as required by the SDK.
|
||||||
@@ -216,4 +134,3 @@ If your commit fails its either:
|
|||||||
branch name != <type>/description-of-branch ,
|
branch name != <type>/description-of-branch ,
|
||||||
commit name != <type>: description of the commit.
|
commit name != <type>: description of the commit.
|
||||||
<ref>: N25B-Num's
|
<ref>: N25B-Num's
|
||||||
>>>>>>> origin/dev
|
|
||||||
|
|||||||
149
main.py
149
main.py
@@ -1,149 +0,0 @@
|
|||||||
import sys
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import zmq
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import hashlib
|
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
|
|
||||||
from src.audio_streaming import AudioStreaming
|
|
||||||
from state import state
|
|
||||||
from urlparse import urlparse
|
|
||||||
|
|
||||||
|
|
||||||
def say(session, message):
|
|
||||||
tts = session.service("ALTextToSpeech")
|
|
||||||
tts.say(message)
|
|
||||||
|
|
||||||
def listen_for_messages(session):
|
|
||||||
context = zmq.Context()
|
|
||||||
socket = context.socket(zmq.SUB)
|
|
||||||
socket.connect("tcp://localhost:5556")
|
|
||||||
socket.setsockopt_string(zmq.SUBSCRIBE, u"") # u because Python 2 shenanigans
|
|
||||||
|
|
||||||
# Let the CB know we're connected.
|
|
||||||
pub = context.socket(zmq.PUB)
|
|
||||||
pub.bind("tcp://*:5555")
|
|
||||||
|
|
||||||
time.sleep(1)
|
|
||||||
print("Now attempting to send connection data to CB.")
|
|
||||||
connection_data = {
|
|
||||||
"event": "robot_connected",
|
|
||||||
"id": state.__getattribute__("id"),
|
|
||||||
"name": state.__getattribute__("name"),
|
|
||||||
"port": state.__getattribute__("port")
|
|
||||||
}
|
|
||||||
|
|
||||||
connection_json = json.dumps(connection_data)
|
|
||||||
pub.send_string(connection_json)
|
|
||||||
print("Send data: ", connection_json)
|
|
||||||
listening_loop(socket, pub)
|
|
||||||
|
|
||||||
def listening_loop(socket, pub):
|
|
||||||
|
|
||||||
print("Entered listening loop.")
|
|
||||||
poller = zmq.Poller()
|
|
||||||
poller.register(socket, zmq.POLLIN)
|
|
||||||
|
|
||||||
logging.info("Listening for messages")
|
|
||||||
|
|
||||||
while not state.exit_event.is_set():
|
|
||||||
if not poller.poll(200): continue # At most 200 ms delay after CTRL+C
|
|
||||||
# We now know there's a message waiting for us
|
|
||||||
message = socket.recv_string()
|
|
||||||
logging.debug("Received message: {}".format(message))
|
|
||||||
handle_message(message, pub)
|
|
||||||
|
|
||||||
def handle_message(msg, pub):
|
|
||||||
"""
|
|
||||||
Parse the message and act accordingly to conditional statements.
|
|
||||||
"""
|
|
||||||
msgs = msg.split(",") # Let's split the attributes
|
|
||||||
if msgs[0] == "ping":
|
|
||||||
# return ping
|
|
||||||
ping_data = {
|
|
||||||
"event": "ping",
|
|
||||||
"id": state.__getattribute__("id"),
|
|
||||||
}
|
|
||||||
ping_json = json.dumps(ping_data)
|
|
||||||
pub.send_string(ping_json)
|
|
||||||
logging.debug("Returned ping.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Other if statements such as `if msgs[0] == "say"`
|
|
||||||
return
|
|
||||||
|
|
||||||
def get_session():
|
|
||||||
if "--qi-url" not in sys.argv:
|
|
||||||
logging.info("No Qi URL argument given. Running in stand-alone mode.")
|
|
||||||
ip = resolve_local_ip()
|
|
||||||
port = -1
|
|
||||||
return None, ip, port
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("--qi-url", type=str, help="Qi URL argument")
|
|
||||||
parser.add_argument("--name", type=str, default="Robot without name", help="Optional robot name")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
name = args.name
|
|
||||||
parsed = urlparse(args.qi_url)
|
|
||||||
ip = parsed.hostname
|
|
||||||
port = parsed.port
|
|
||||||
|
|
||||||
# If URL uses localhost, get own ip instead
|
|
||||||
if ip in ("localhost", "127.0.0.1"):
|
|
||||||
ip = resolve_local_ip()
|
|
||||||
|
|
||||||
try:
|
|
||||||
import qi
|
|
||||||
except ImportError:
|
|
||||||
logging.info("Unable to import qi. Running in stand-alone mode.")
|
|
||||||
return None, ip, port, name
|
|
||||||
|
|
||||||
try:
|
|
||||||
app = qi.Application()
|
|
||||||
app.start()
|
|
||||||
return app.session, ip, port, name
|
|
||||||
except RuntimeError:
|
|
||||||
logging.info("Unable to connect to the robot. Running in stand-alone mode.")
|
|
||||||
return None, ip, port, name
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_local_ip():
|
|
||||||
"""Return the actual local IP, not 127.0.0.1."""
|
|
||||||
try:
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
s.connect(("8.8.8.8", 80)) # Use a public IP just to resolve interface
|
|
||||||
ip = s.getsockname()[0]
|
|
||||||
s.close()
|
|
||||||
return ip
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning("Could not resolve local IP: {}".format(e))
|
|
||||||
return "127.0.0.1"
|
|
||||||
|
|
||||||
def main():
|
|
||||||
session, ip, port, name = get_session()
|
|
||||||
|
|
||||||
# hash ip, port into id
|
|
||||||
id_source = "{}:{}".format(ip, port)
|
|
||||||
unique_id = hashlib.md5(id_source).hexdigest()
|
|
||||||
|
|
||||||
print("created unique id: ", unique_id)
|
|
||||||
|
|
||||||
state.id = unique_id
|
|
||||||
state.port = port
|
|
||||||
state.ip = ip
|
|
||||||
state.name = name
|
|
||||||
|
|
||||||
logging.info("Session ID: {} (from {})".format(unique_id, id_source))
|
|
||||||
listen_for_messages(session)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
state.initialize()
|
|
||||||
main()
|
|
||||||
finally:
|
|
||||||
state.deinitialize()
|
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
pyzmq<16
|
pyzmq<16
|
||||||
<<<<<<< HEAD
|
|
||||||
pyaudio<=0.2.11
|
|
||||||
=======
|
|
||||||
pyaudio<=0.2.11
|
pyaudio<=0.2.11
|
||||||
pytest<5
|
pytest<5
|
||||||
pytest-mock<3.0.0
|
pytest-mock<3.0.0
|
||||||
pytest-cov<3.0.0
|
pytest-cov<3.0.0
|
||||||
>>>>>>> origin/dev
|
|
||||||
|
|||||||
53
state.py
53
state.py
@@ -1,53 +0,0 @@
|
|||||||
import logging
|
|
||||||
import signal
|
|
||||||
import threading
|
|
||||||
|
|
||||||
|
|
||||||
class State(object):
|
|
||||||
"""
|
|
||||||
Do not create an instance of this class directly: use the instance `state` below. This state must be initiated once,
|
|
||||||
probably when your program starts.
|
|
||||||
|
|
||||||
This class is used to share state between threads. For example, when the program is quit, that all threads can
|
|
||||||
detect this via the `exit_event` property being set.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self.is_initialized = False
|
|
||||||
self.exit_event = None
|
|
||||||
|
|
||||||
def initialize(self):
|
|
||||||
if self.is_initialized:
|
|
||||||
logging.warn("Already initialized")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.exit_event = threading.Event()
|
|
||||||
def handle_exit(_, __):
|
|
||||||
logging.info("Exiting.")
|
|
||||||
self.exit_event.set()
|
|
||||||
signal.signal(signal.SIGINT, handle_exit)
|
|
||||||
signal.signal(signal.SIGTERM, handle_exit)
|
|
||||||
|
|
||||||
self.is_initialized = True
|
|
||||||
|
|
||||||
def deinitialize(self):
|
|
||||||
if not self.is_initialized: return
|
|
||||||
self.is_initialized = False
|
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
|
||||||
# Enforce that the state is initialized before accessing any property (aside from the basic ones)
|
|
||||||
if name in ("initialize", "deinitialize", "is_initialized", "__dict__", "__class__", "id", "name", "ip", "port"):
|
|
||||||
return object.__getattribute__(self, name)
|
|
||||||
|
|
||||||
if not object.__getattribute__(self, "is_initialized"):
|
|
||||||
# Special case for the exit_event: if the event is set, return it without an error
|
|
||||||
if name == "exit_event":
|
|
||||||
exit_event = object.__getattribute__(self, "exit_event")
|
|
||||||
if exit_event and exit_event.is_set(): return exit_event
|
|
||||||
|
|
||||||
raise RuntimeError("State must be initialized before accessing '%s'" % name)
|
|
||||||
|
|
||||||
return object.__getattribute__(self, name)
|
|
||||||
|
|
||||||
|
|
||||||
# Must call `.initialize` before use
|
|
||||||
state = State()
|
|
||||||
Reference in New Issue
Block a user