web.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import asyncio
  2. import dataclasses
  3. import json
  4. import logging
  5. import os
  6. import ssl
  7. import subprocess
  8. import pyaudio
  9. import wave
  10. from aiohttp import web
  11. from aiohttp import ClientSession
  12. from openpilot.common.basedir import BASEDIR
  13. from openpilot.system.webrtc.webrtcd import StreamRequestBody
  14. from openpilot.common.params import Params
  15. logger = logging.getLogger("bodyteleop")
  16. logging.basicConfig(level=logging.INFO)
  17. TELEOPDIR = f"{BASEDIR}/tools/bodyteleop"
  18. WEBRTCD_HOST, WEBRTCD_PORT = "localhost", 5001
  19. ## UTILS
  20. async def play_sound(sound: str):
  21. SOUNDS = {
  22. "engage": "selfdrive/assets/sounds/engage.wav",
  23. "disengage": "selfdrive/assets/sounds/disengage.wav",
  24. "error": "selfdrive/assets/sounds/warning_immediate.wav",
  25. }
  26. assert sound in SOUNDS
  27. chunk = 5120
  28. with wave.open(os.path.join(BASEDIR, SOUNDS[sound]), "rb") as wf:
  29. def callback(in_data, frame_count, time_info, status):
  30. data = wf.readframes(frame_count)
  31. return data, pyaudio.paContinue
  32. p = pyaudio.PyAudio()
  33. stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
  34. channels=wf.getnchannels(),
  35. rate=wf.getframerate(),
  36. output=True,
  37. frames_per_buffer=chunk,
  38. stream_callback=callback)
  39. stream.start_stream()
  40. while stream.is_active():
  41. await asyncio.sleep(0)
  42. stream.stop_stream()
  43. stream.close()
  44. p.terminate()
  45. ## SSL
  46. def create_ssl_cert(cert_path: str, key_path: str):
  47. try:
  48. proc = subprocess.run(f'openssl req -x509 -newkey rsa:4096 -nodes -out {cert_path} -keyout {key_path} \
  49. -days 365 -subj "/C=US/ST=California/O=commaai/OU=comma body"',
  50. capture_output=True, shell=True)
  51. proc.check_returncode()
  52. except subprocess.CalledProcessError as ex:
  53. raise ValueError(f"Error creating SSL certificate:\n[stdout]\n{proc.stdout.decode()}\n[stderr]\n{proc.stderr.decode()}") from ex
  54. def create_ssl_context():
  55. cert_path = os.path.join(TELEOPDIR, "cert.pem")
  56. key_path = os.path.join(TELEOPDIR, "key.pem")
  57. if not os.path.exists(cert_path) or not os.path.exists(key_path):
  58. logger.info("Creating certificate...")
  59. create_ssl_cert(cert_path, key_path)
  60. else:
  61. logger.info("Certificate exists!")
  62. ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER)
  63. ssl_context.load_cert_chain(cert_path, key_path)
  64. return ssl_context
  65. ## ENDPOINTS
  66. async def index(request: 'web.Request'):
  67. with open(os.path.join(TELEOPDIR, "static", "index.html")) as f:
  68. content = f.read()
  69. return web.Response(content_type="text/html", text=content)
  70. async def ping(request: 'web.Request'):
  71. return web.Response(text="pong")
  72. async def sound(request: 'web.Request'):
  73. params = await request.json()
  74. sound_to_play = params["sound"]
  75. await play_sound(sound_to_play)
  76. return web.json_response({"status": "ok"})
  77. async def offer(request: 'web.Request'):
  78. params = await request.json()
  79. body = StreamRequestBody(params["sdp"], ["driver"], ["testJoystick"], ["carState"])
  80. body_json = json.dumps(dataclasses.asdict(body))
  81. logger.info("Sending offer to webrtcd...")
  82. webrtcd_url = f"http://{WEBRTCD_HOST}:{WEBRTCD_PORT}/stream"
  83. async with ClientSession() as session, session.post(webrtcd_url, data=body_json) as resp:
  84. assert resp.status == 200
  85. answer = await resp.json()
  86. return web.json_response(answer)
  87. def main():
  88. # Enable joystick debug mode
  89. Params().put_bool("JoystickDebugMode", True)
  90. # App needs to be HTTPS for microphone and audio autoplay to work on the browser
  91. ssl_context = create_ssl_context()
  92. app = web.Application()
  93. app.router.add_get("/", index)
  94. app.router.add_get("/ping", ping, allow_head=True)
  95. app.router.add_post("/offer", offer)
  96. app.router.add_post("/sound", sound)
  97. app.router.add_static('/static', os.path.join(TELEOPDIR, 'static'))
  98. web.run_app(app, access_log=None, host="0.0.0.0", port=5000, ssl_context=ssl_context)
  99. if __name__ == "__main__":
  100. main()