Merge branch 'refactor/config-file' into 'dev'

refactor: added config file and moved constants

See merge request ics/sp/2025/n25b/pepperplus-ri!9
This commit was merged in pull request #9.
This commit is contained in:
Twirre
2025-11-14 14:15:06 +00:00
9 changed files with 96 additions and 21 deletions

View File

View File

@@ -0,0 +1,62 @@
from __future__ import unicode_literals
class AgentSettings(object):
"""Agent port configuration."""
def __init__(
self,
actuating_receiver_port=5557,
main_receiver_port=5555,
video_sender_port=5556,
audio_sender_port=5558,
):
self.actuating_receiver_port = actuating_receiver_port
self.main_receiver_port = main_receiver_port
self.video_sender_port = video_sender_port
self.audio_sender_port = audio_sender_port
class VideoConfig(object):
"""Video configuration constants."""
def __init__(
self,
camera_index=0,
resolution=2,
color_space=11,
fps=15,
stream_name="Pepper Video",
image_buffer=6,
):
self.camera_index = camera_index
self.resolution = resolution
self.color_space = color_space
self.fps = fps
self.stream_name = stream_name
self.image_buffer = image_buffer
class AudioConfig(object):
"""Audio configuration constants."""
def __init__(self, sample_rate=16000, chunk_size=512, channels=1):
self.sample_rate = sample_rate
self.chunk_size = chunk_size
self.channels = channels
class MainConfig(object):
"""Main configuration"""
def __init__(self, poll_timeout_ms=100, max_handler_time_ms=50):
self.poll_timeout_ms = poll_timeout_ms
self.max_handler_time_ms = max_handler_time_ms
class Settings(object):
"""Global settings container."""
def __init__(self, agent_settings=None, video_config=None, audio_config=None, main_config=None):
self.agent_settings = agent_settings or AgentSettings()
self.video_config = video_config or VideoConfig()
self.audio_config = audio_config or AudioConfig()
self.main_config = main_config or MainConfig()
settings = Settings()

View File

@@ -6,9 +6,11 @@ import zmq
from robot_interface.endpoints.receiver_base import ReceiverBase from robot_interface.endpoints.receiver_base import ReceiverBase
from robot_interface.state import state from robot_interface.state import state
from robot_interface.core.config import settings
class ActuationReceiver(ReceiverBase): class ActuationReceiver(ReceiverBase):
def __init__(self, zmq_context, port=5557): def __init__(self, zmq_context, port=settings.agent_settings.actuating_receiver_port):
""" """
The actuation receiver endpoint, responsible for handling speech and gesture requests. The actuation receiver endpoint, responsible for handling speech and gesture requests.

View File

@@ -8,13 +8,13 @@ import zmq
from robot_interface.endpoints.socket_base import SocketBase from robot_interface.endpoints.socket_base import SocketBase
from robot_interface.state import state from robot_interface.state import state
from robot_interface.utils.microphone import choose_mic from robot_interface.utils.microphone import choose_mic
from robot_interface.core.config import settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AudioSender(SocketBase): class AudioSender(SocketBase):
def __init__(self, zmq_context, port=5558): def __init__(self, zmq_context, port=settings.agent_settings.audio_sender_port):
super(AudioSender, self).__init__(str("audio")) # Convert future's unicode_literal to str super(AudioSender, self).__init__(str("audio")) # Convert future's unicode_literal to str
self.create_socket(zmq_context, zmq.PUB, port) self.create_socket(zmq_context, zmq.PUB, port)
self.thread = None self.thread = None
@@ -49,13 +49,14 @@ class AudioSender(SocketBase):
self.thread = None self.thread = None
def _stream(self): def _stream(self):
chunk = 512 # 320 at 16000 Hz is 20ms, 512 is required for Silero-VAD audio_settings = settings.audio_config
chunk = audio_settings.chunk_size # 320 at 16000 Hz is 20ms, 512 is required for Silero-VAD
# Docs say this only raises an error if neither `input` nor `output` is True # Docs say this only raises an error if neither `input` nor `output` is True
stream = self.audio.open( stream = self.audio.open(
format=pyaudio.paFloat32, format=pyaudio.paFloat32,
channels=1, channels=audio_settings.channels,
rate=16000, rate=audio_settings.sample_rate,
input=True, input=True,
input_device_index=self.microphone["index"], input_device_index=self.microphone["index"],
frames_per_buffer=chunk, frames_per_buffer=chunk,

View File

@@ -3,9 +3,10 @@ import zmq
from robot_interface.endpoints.receiver_base import ReceiverBase from robot_interface.endpoints.receiver_base import ReceiverBase
from robot_interface.state import state from robot_interface.state import state
from robot_interface.core.config import settings
class MainReceiver(ReceiverBase): class MainReceiver(ReceiverBase):
def __init__(self, zmq_context, port=5555): def __init__(self, zmq_context, port=settings.agent_settings.main_receiver_port):
""" """
The main receiver endpoint, responsible for handling ping and negotiation requests. The main receiver endpoint, responsible for handling ping and negotiation requests.

View File

@@ -4,10 +4,10 @@ import logging
from robot_interface.endpoints.socket_base import SocketBase from robot_interface.endpoints.socket_base import SocketBase
from robot_interface.state import state from robot_interface.state import state
from robot_interface.core.config import settings
class VideoSender(SocketBase): class VideoSender(SocketBase):
def __init__(self, zmq_context, port=5556): def __init__(self, zmq_context, port=settings.agent_settings.video_sender_port):
super(VideoSender, self).__init__("video") super(VideoSender, self).__init__("video")
self.create_socket(zmq_context, zmq.PUB, port, [(zmq.CONFLATE,1)]) self.create_socket(zmq_context, zmq.PUB, port, [(zmq.CONFLATE,1)])
@@ -20,12 +20,13 @@ class VideoSender(SocketBase):
return return
video = state.qi_session.service("ALVideoDevice") video = state.qi_session.service("ALVideoDevice")
video_settings = settings.video_config
camera_index = 0 camera_index = video_settings.camera_index
kQVGA = 2 kQVGA = video_settings.resolution
kRGB = 11 kRGB = video_settings.color_space
FPS = 15 FPS = video_settings.fps
vid_stream_name = video.subscribeCamera("Pepper Video", camera_index, kQVGA, kRGB, FPS) video_name = video_settings.stream_name
vid_stream_name = video.subscribeCamera(video_name, camera_index, kQVGA, kRGB, FPS)
thread = threading.Thread(target=self.video_rcv_loop, args=(video, vid_stream_name)) thread = threading.Thread(target=self.video_rcv_loop, args=(video, vid_stream_name))
thread.start() thread.start()
@@ -43,6 +44,6 @@ class VideoSender(SocketBase):
try: try:
img = vid_service.getImageRemote(vid_stream_name) img = vid_service.getImageRemote(vid_stream_name)
#Possibly limit images sent if queuing issues arise #Possibly limit images sent if queuing issues arise
self.socket.send(img[6]) self.socket.send(img[settings.video_config.image_buffer])
except: except:
logging.warn("Failed to retrieve video image from robot.") logging.warn("Failed to retrieve video image from robot.")

View File

@@ -10,6 +10,7 @@ from robot_interface.endpoints.actuation_receiver import ActuationReceiver
from robot_interface.endpoints.main_receiver import MainReceiver from robot_interface.endpoints.main_receiver import MainReceiver
from robot_interface.endpoints.video_sender import VideoSender from robot_interface.endpoints.video_sender import VideoSender
from robot_interface.state import state from robot_interface.state import state
from robot_interface.core.config import settings
from robot_interface.utils.timeblock import TimeBlock from robot_interface.utils.timeblock import TimeBlock
@@ -45,7 +46,7 @@ def main_loop(context):
while True: while True:
if state.exit_event.is_set(): break if state.exit_event.is_set(): break
socks = dict(poller.poll(100)) socks = dict(poller.poll(settings.main_config.poll_timeout_ms))
for receiver in receivers: for receiver in receivers:
if receiver.socket not in socks: continue if receiver.socket not in socks: continue
@@ -59,7 +60,7 @@ def main_loop(context):
logging.warn("Endpoint \"%s\" took too long (%.2f ms) on the main thread.", logging.warn("Endpoint \"%s\" took too long (%.2f ms) on the main thread.",
message["endpoint"], time_ms) message["endpoint"], time_ms)
with TimeBlock(overtime_callback, 50): with TimeBlock(overtime_callback, settings.main_config.max_handler_time_ms):
response = receiver.handle_message(message) response = receiver.handle_message(message)
if receiver.socket.getsockopt(zmq.TYPE) == zmq.REP: if receiver.socket.getsockopt(zmq.TYPE) == zmq.REP:

View File

@@ -86,7 +86,8 @@ def choose_mic_arguments(audio):
if arg == "--microphone" and len(sys.argv) > i+1: if arg == "--microphone" and len(sys.argv) > i+1:
microphone_name = sys.argv[i+1].strip() microphone_name = sys.argv[i+1].strip()
if arg.startswith("--microphone="): if arg.startswith("--microphone="):
microphone_name = arg[13:].strip() pre_fix_len = len("--microphone=")
microphone_name = arg[pre_fix_len:].strip()
if not microphone_name: return None if not microphone_name: return None

View File

@@ -41,6 +41,9 @@ class MicrophoneUtils(object):
""" """
First mock an input that's not an integer, then a valid integer. There should be no errors. First mock an input that's not an integer, then a valid integer. There should be no errors.
""" """
microphones = get_microphones(pyaudio_instance)
target_microphone = next(microphones)
mock_input = mocker.patch("__builtin__.raw_input", side_effect=["not an integer", "0"]) mock_input = mocker.patch("__builtin__.raw_input", side_effect=["not an integer", "0"])
fake_out = StringIO() fake_out = StringIO()
mocker.patch.object(sys, "stdout", fake_out) mocker.patch.object(sys, "stdout", fake_out)
@@ -48,7 +51,7 @@ class MicrophoneUtils(object):
result = choose_mic_interactive(pyaudio_instance) result = choose_mic_interactive(pyaudio_instance)
assert "index" in result assert "index" in result
assert isinstance(result["index"], (int, long)) assert isinstance(result["index"], (int, long))
assert result["index"] == 0 assert result["index"] == target_microphone["index"]
assert mock_input.called assert mock_input.called
@@ -58,6 +61,9 @@ class MicrophoneUtils(object):
""" """
Make sure that the interactive method does not allow negative integers as input. Make sure that the interactive method does not allow negative integers as input.
""" """
microphones = get_microphones(pyaudio_instance)
target_microphone = next(microphones)
mock_input = mocker.patch("__builtin__.raw_input", side_effect=["-1", "0"]) mock_input = mocker.patch("__builtin__.raw_input", side_effect=["-1", "0"])
fake_out = StringIO() fake_out = StringIO()
mocker.patch.object(sys, "stdout", fake_out) mocker.patch.object(sys, "stdout", fake_out)
@@ -65,7 +71,7 @@ class MicrophoneUtils(object):
result = choose_mic_interactive(pyaudio_instance) result = choose_mic_interactive(pyaudio_instance)
assert "index" in result assert "index" in result
assert isinstance(result["index"], (int, long)) assert isinstance(result["index"], (int, long))
assert result["index"] == 0 assert result["index"] == target_microphone["index"]
assert mock_input.called assert mock_input.called