Configuração do PC2 (Unitree)
Para este tutorial é recomendado o uso desta ferramenta.
Copiar certificados
Ainda no HOST, use SSH para criar o diretório de configuração no PC2 e copie os arquivos:
ssh unitree@192.168.123.164 'mkdir -p ~/.config/xr_teleoperate'
scp ~/Desktop/Teleoperacao/xr_teleoperate/teleop/televuer/cert.pem \
~/Desktop/Teleoperacao/xr_teleoperate/teleop/televuer/key.pem \
unitree@192.168.123.164:~/.config/xr_teleoperate/
Verifique se os certificados foram copiados:
ssh unitree@192.168.123.164 'ls -l ~/.config/xr_teleoperate/'
Saída esperada:
cert.pem
key.pem
Instalar pacotes no PC2
Estas notas resumem a instalação realizada nesta máquina para que possa ser reproduzida em outro sistema Ubuntu estilo Unitree/Jetson.
1. Instalar Miniconda
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.sh -O /tmp/miniconda.sh
bash /tmp/miniconda.sh -b -u -p /home/unitree/miniconda3
rm /tmp/miniconda.sh
Se o Conda exigir aceitação dos Termos de Serviço do canal:
/home/unitree/miniconda3/bin/conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
/home/unitree/miniconda3/bin/conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r
Inicialize o Conda para bash:
/home/unitree/miniconda3/bin/conda init bash
source ~/.bashrc
2. Criar ambiente teleimager
/home/unitree/miniconda3/bin/conda create -n teleimager python=3.10 -y
conda activate teleimager
3. Instalar pacotes do sistema
sudo apt update
sudo apt install -y libusb-1.0-0-dev libturbojpeg-dev
4. Clonar e instalar Tele Imager
cd unitree
git clone https://github.com/silencht/teleimager
cd teleimager
pip install -e ".[server]"
4a. Certificado para o Tele Imager
Pro precaução, abra outro terminal no HOST e copie os certificados para o recém clonado repositório do Tele Imager
scp ~/Desktop/Developer/unitree-g1/packages/xr_teleoperate/teleop/televuer/cert.pem \
~/Desktop/Developer/unitree-g1/packages/xr_teleoperate/teleop/televuer/key.pem \
unitree@192.168.123.164:~/unitree/teleimager/
5. Correção de dependência logging_mp
A versão atual do pacote logging_mp expõe getLogger, mas o Tele Imager 1.5.0 chama get_logger. Isso fazia tanto o teleimager-server quanto o teleimager-client falharem na inicialização.
Edite o pyproject.toml local, alterando:
"logging_mp",
para:
"logging_mp==0.1.6",
Em seguida, reinstale:
cd /home/unitree/teleimager
pip install -e ".[server]"
E foi verificado, que para solucionar 100% dos erros é necessário trocar as seguintes linhas dos seguintes arquivos:
src/teleimager/image_server.py
- linha 15: logging_mp.basicConfig → logging_mp.basic_config
- linha 16: logging_mp.getLogger → logging_mp.get_logger
src/teleimager/image_client.py
- linha 33: logging_mp.getLogger → logging_mp.get_logger
6. Modificações no código TeleImager
As seguintes alterações foram feitas localmente no teleimager para suportar a câmera RealSense D435i em modo OpenCV.
6a. setup_uvc.sh — Tolerante a driver UVC ocupado
O script original falhava quando o driver uvcvideo estava em uso. Agora ele avisa e continua, apague o step 4 e cooloque esse:
if sudo $MODPROBE_PATH -r uvcvideo; then
sudo $MODPROBE_PATH uvcvideo debug=0
else
echo "Warning: uvcvideo is currently in use; skipping driver reload."
echo "Stop camera/video services or reboot if you need to reload it."
fi
echo "UVC setup completed successfully."
Execute:
cd /home/unitree/teleimager
bash setup_uvc.sh
6b. src/teleimager/image_server.py — Múltiplas correções, troque os código atuais pelo os que serão passados:
UVC reload warning:
def reload_uvc_driver():
try:
subprocess.run("sudo modprobe -r uvcvideo", shell=True, check=True)
time.sleep(1)
subprocess.run("sudo modprobe uvcvideo debug=0", shell=True, check=True)
time.sleep(1)
logger_mp.info("UVC driver reloaded successfully.")
except subprocess.CalledProcessError as e:
logger_mp.warning(f"UVC driver is currently in use; skipping reload: {e}")
RealSense import error inclui a exceção real:
def get_realsense_module(self) -> object:
try:
import pyrealsense2 as rs
return rs
except (ImportError, OSError) as e:
msg = f"{msg}\nOriginal import error: {e}"
raise RuntimeError(msg)
_is_like_rgb — usa índice numérico V4L2 com até 3 tentativas:
def _is_like_rgb(self, video_path):
source = int(video_path.replace("/dev/video", "")) if video_path.startswith("/dev/video") else video_path
for _ in range(3):
cap = cv2.VideoCapture(source, cv2.CAP_V4L2)
if cap.isOpened():
ret, frame = cap.read()
cap.release()
if ret and frame is not None and frame.ndim == 3 and frame.shape[2] == 3:
return True
else:
cap.release()
time.sleep(0.5)
return False
OpenCVCamera.__init__ — usa índice numérico V4L2:
def __init__(self, cam_topic, video_path, img_shape, fps, ...):
super().__init__(...)
self._video_path = video_path
source = int(self._video_path.replace("/dev/video", "")) if self._video_path.startswith("/dev/video") else self._video_path
self.cap = cv2.VideoCapture(source, cv2.CAP_V4L2)
CameraFinder.__init__ — parâmetro probe_rgb:
def __init__(self, realsense_enable=False, verbose=False, probe_rgb=True):
# ...
self.uvc_rgb_video_paths = self._list_uvc_rgb_video_paths() if probe_rgb else []
ImageServer.__init__ — só faz probe RGB quando necessário:
probe_rgb = any(
(cam_cfg.get("enable_zmq", False) or cam_cfg.get("enable_webrtc", False))
and cam_cfg.get("type", "uvc").lower() in ("opencv", "uvc")
and not cam_cfg.get("physical_path")
for cam_cfg in cam_config.values()
)
self._cam_finder = CameraFinder(realsense_enable, camera_finder_verbose, probe_rgb=probe_rgb)
get_vpath_by_ppath — resolve pelo menor índice da interface, sem probe OpenCV:
def get_vpath_by_ppath(self, physical_path):
base = "/sys/class/video4linux/"
matches = []
for v in os.listdir(base):
sys_path = os.path.realpath(os.path.join(base, v, "device"))
if sys_path == physical_path:
index_path = os.path.join(base, v, "index")
try:
index = int(open(index_path).read().strip())
except Exception:
index = 999
matches.append((index, f"/dev/{v}"))
if not matches:
return None
matches.sort()
return matches[0][1]
6c. src/teleimager/image_client.py — Suporte headless
parser.add_argument('--no-display', action='store_true',
help='Receive frames without opening OpenCV display windows')
args = parser.parse_args()
display_enabled = not args.no_display and bool(os.environ.get("DISPLAY"))
if not display_enabled:
logger_mp.info("Display disabled; receiving frames without OpenCV windows.")
E cada cv2.imshow(...) envolvido em if display_enabled:.
7. Configuração da câmera (cam_config_server.yaml)
head_camera:
enable_zmq: true
zmq_port: 55555
enable_webrtc: true
webrtc_port: 60001
webrtc_codec: h264
type: opencv
image_shape: [480, 640]
binocular: false
fps: 30
video_id: null
serial_number: null
physical_path: /sys/devices/platform/3610000.xhci/usb2/2-3/2-3:1.3
left_wrist_camera:
enable_zmq: false
enable_webrtc: false
right_wrist_camera:
enable_zmq: false
enable_webrtc: false
O
physical_pathusado é o da interface RGB da RealSense (sub-interface1.3, V4L2 index 0). Isso evita a ambiguidade de serial number (onde/dev/video2e/dev/video4compartilham o mesmo serial252443060502).
8. Script de inicialização (run_realsense_server.sh)
Crie o arquivo /home/unitree/teleimager/run_realsense_server.sh com o seguinte conteúdo:
#!/bin/bash
set -euo pipefail
SERVER_BIN="${TELEIMAGER_SERVER:-/home/unitree/miniconda3/envs/teleimager/bin/teleimager-server}"
VIDEO_HUB="/unitree/module/video_hub_pc4/videohub_pc4"
STOPPED_MASTER=0
cleanup() {
if [ "$STOPPED_MASTER" -eq 1 ]; then
sudo systemctl start master_service || true
fi
}
existing_server_pids=$(pgrep -f "teleimager-server" || true)
if [ -n "$existing_server_pids" ]; then
echo "TeleImager server is already running: $existing_server_pids"
echo "Stop it first with Ctrl+C in its terminal, or run: pkill -TERM -f teleimager-server"
exit 1
fi
trap cleanup EXIT
sudo systemctl stop master_service || true
STOPPED_MASTER=1
sudo pkill -TERM -f "$VIDEO_HUB" || true
for _ in 1 2 3 4 5; do
if ! pgrep -f "$VIDEO_HUB" > /dev/null; then
break
fi
sleep 1
done
if pgrep -f "$VIDEO_HUB" > /dev/null; then
sudo pkill -KILL -f "$VIDEO_HUB" || true
fi
"$SERVER_BIN"
Torne executável:
chmod +x /home/unitree/teleimager/run_realsense_server.sh
9. Verificação
Verifique se os comandos estão disponíveis:
teleimager-server --help
teleimager-client --help
Execute a descoberta de câmera:
cd /home/unitree/teleimager
python -m teleimager.image_server --cf
10. Uso típico no PC2
Inicie o servidor:
cd /home/unitree/teleimager
./run_realsense_server.sh
Para testar o stream localmente (PC2 com display):
teleimager-client --host 127.0.0.1
Ou em modo headless (PC2 sem display):
teleimager-client --host 127.0.0.1 --no-display
Portas usadas:
| Porta | Função |
|---|---|
| 60000 | Config responder |
| 55555 | ZMQ video stream |
| 60001 | WebRTC |
Câmera RealSense D435i
A câmera utilizada é uma Intel RealSense D435i conectada na porta USB 9 do G1.
Por que OpenCV em vez do SDK RealSense?
O modo nativo RealSense (pyrealsense2) não é compatível com o GLIBC do Ubuntu do G1 (aarch64). As wheels disponíveis para pyrealsense2 exigem GLIBC_2.32, mas o sistema possui uma versão mais antiga. Por isso, a câmera é configurada em modo OpenCV, tratando a RealSense como uma câmera UVC padrão.
Identificar o physical path
Conecte a câmera na porta USB 9 e execute:
rs-enumerate-devices
Saída esperada:
Device info:
Name : Intel RealSense D435I
Serial Number : 406122071162
Firmware Version : 05.15.01.55
Physical Port : /sys/devices/platform/3610000.xhci/usb2/2-3/2-3:1.0/video4linux/video0
O Physical Port completo aponta para a interface 1.0 (meta-dados). A interface RGB que queremos é a 1.3:
/sys/devices/platform/3610000.xhci/usb2/2-3/2-3:1.3
Para confirmar os dispositivos V4L2 associados:
ls -l /sys/class/video4linux/
E verificar qual índice V4L2 cada interface expõe:
for v in /sys/class/video4linux/video*; do
echo "$(basename $v): interface=$(basename $(readlink $v/device)), index=$(cat $v/index 2>/dev/null), name=$(cat $v/name 2>/dev/null)"
done
Configurar cam_config_server.yaml
No PC2, edite /home/unitree/teleimager/cam_config_server.yaml:
head_camera:
enable_zmq: true
zmq_port: 55555
enable_webrtc: true
webrtc_port: 60001
webrtc_codec: h264
type: opencv
image_shape: [480, 640]
binocular: false
fps: 30
video_id: null
serial_number: null
physical_path: /sys/devices/platform/3610000.xhci/usb2/2-3/2-3:1.3
O campo
physical_pathé a chave para o funcionamento correto. Oteleimagermodificado (veja seção 6b) usaget_vpath_by_ppath()para resolver ophysical_pathao menor índice V4L2 disponível sob aquela interface USB, sem precisar fazer probe OpenCV.
Iniciar o servidor
cd /home/unitree/teleimager
./run_realsense_server.sh
Testar o stream
Do host:
conda activate tv
cd ~/unitreeG1/packages/xr_teleoperate/teleop
python -m teleimager.image_client --host 192.168.123.164 --no-display
Ou do próprio PC2 (com display):
teleimager-client --host 127.0.0.1
Portas
| Porta | Função |
|---|---|
| 60000 | Config responder |
| 55555 | ZMQ video stream |
| 60001 | WebRTC |