feat: Squash all min-features into full

This commit is contained in:
Rick Lan
2025-09-05 11:07:32 +08:00
parent 028b6e5664
commit 5e0f34905d
75 changed files with 4041 additions and 83 deletions
+5 -1
View File
@@ -12,6 +12,7 @@ from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import cloudlog
import os
UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
@@ -48,6 +49,9 @@ def register(show_spinner=False) -> str | None:
spinner = Spinner()
spinner.update("registering device")
if os.getenv("LITE"):
params.put("DongleId", UNREGISTERED_DONGLE_ID)
return dongle_id
# Create registration token, in the future, this key will make JWTs directly
with open(Paths.persist_root()+"/comma/id_rsa.pub") as f1, open(Paths.persist_root()+"/comma/id_rsa") as f2:
public_key = f1.read()
@@ -74,7 +78,7 @@ def register(show_spinner=False) -> str | None:
try:
register_token = jwt.encode({'register': True, 'exp': datetime.now(UTC).replace(tzinfo=None) + timedelta(hours=1)}, private_key, algorithm='RS256')
cloudlog.info("getting pilotauth")
resp = api_get("v2/pilotauth/", method='POST', timeout=15,
resp = api_get("v2/pilotauth/", method='POST', timeout=5,
imei=imei1, imei2=imei2, serial=serial, public_key=public_key, register_token=register_token)
if resp.status_code in (402, 403):
+1
View File
@@ -270,6 +270,7 @@ void camerad_thread() {
// *** per-cam init ***
std::vector<std::unique_ptr<CameraState>> cams;
for (const auto &config : ALL_CAMERA_CONFIGS) {
if (!config.enabled) continue;
auto cam = std::make_unique<CameraState>(&m, config);
cam->init(&v, device_id, ctx);
cams.emplace_back(std::move(cam));
+1 -1
View File
@@ -59,7 +59,7 @@ const CameraConfig DRIVER_CAMERA_CONFIG = {
.focal_len = 1.71,
.publish_name = "driverCameraState",
.init_camera_state = &cereal::Event::Builder::initDriverCameraState,
.enabled = !getenv("DISABLE_DRIVER"),
.enabled = (!getenv("DISABLE_DRIVER") && !getenv("LITE")),
.phy = CAM_ISP_IFE_IN_RES_PHY_2,
.vignetting_correction = false,
.output_type = ISP_BPS_PROCESSED,
+6 -2
View File
@@ -210,6 +210,8 @@ def hardware_thread(end_event, hw_queue) -> None:
fan_controller = None
dp_device_go_off_road = False
while not end_event.is_set():
sm.update(PANDA_STATES_TIMEOUT)
@@ -335,13 +337,15 @@ def hardware_thread(end_event, hw_queue) -> None:
startup_conditions["registered_device"] = PC or (params.get("DongleId") != UNREGISTERED_DONGLE_ID)
# TODO: this should move to TICI.initialize_hardware, but we currently can't import params there
if TICI and HARDWARE.get_device_type() == "tici":
if TICI and HARDWARE.get_device_type() == "tici" and not os.getenv("LITE"):
if not os.path.isfile("/persist/comma/living-in-the-moment"):
if not Path("/data/media").is_mount():
set_offroad_alert_if_changed("Offroad_StorageMissing", True)
# Handle offroad/onroad transition
should_start = all(onroad_conditions.values())
if count % 6 == 0:
dp_device_go_off_road = params.get_bool("dp_device_go_off_road")
should_start = not dp_device_go_off_road and all(onroad_conditions.values())
if started_ts is None:
should_start = should_start and all(startup_conditions.values())
+4
View File
@@ -28,6 +28,8 @@ class PowerMonitoring:
self.car_voltage_mV = 12e3 # Low-passed version of peripheralState voltage
self.car_voltage_instant_mV = 12e3 # Last value of peripheralState voltage
self.integration_lock = threading.Lock()
self.dp_device_auto_shutdown_in = int(self.params.get("dp_device_auto_shutdown_in") or -5) * 60
self.dp_device_auto_shutdown = self.dp_device_auto_shutdown_in >= 0
car_battery_capacity_uWh = self.params.get("CarBatteryCapacity") or 0
@@ -112,6 +114,8 @@ class PowerMonitoring:
now = time.monotonic()
should_shutdown = False
offroad_time = (now - offroad_timestamp)
if started_seen and self.dp_device_auto_shutdown and offroad_time > self.dp_device_auto_shutdown_in:
return True
low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and
offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S)
should_shutdown |= offroad_time > MAX_TIME_OFFROAD_S
+4 -2
View File
@@ -94,7 +94,7 @@ class Tici(HardwareBase):
@cached_property
def amplifier(self):
if self.get_device_type() == "mici":
if self.get_device_type() == "mici" or os.getenv("LITE"):
return None
return Amplifier()
@@ -190,7 +190,7 @@ class Tici(HardwareBase):
return str(self.get_modem().Get(MM_MODEM, 'EquipmentIdentifier', dbus_interface=DBUS_PROPS, timeout=TIMEOUT))
def get_network_info(self):
if self.get_device_type() == "mici":
if self.get_device_type() == "mici" or os.getenv("LITE"):
return None
try:
modem = self.get_modem()
@@ -282,6 +282,8 @@ class Tici(HardwareBase):
return None
def get_modem_temperatures(self):
if os.getenv("LITE"):
return []
timeout = 0.2 # Default timeout is too short
try:
modem = self.get_modem()
+9 -1
View File
@@ -22,6 +22,8 @@ extern "C" {
const int env_debug_encoder = (getenv("DEBUG_ENCODER") != NULL) ? atoi(getenv("DEBUG_ENCODER")) : 0;
const int env_dashy = (getenv("DASHY") != NULL) ? atoi(getenv("DASHY")) : 0;
FfmpegEncoder::FfmpegEncoder(const EncoderInfo &encoder_info, int in_width, int in_height)
: VideoEncoder(encoder_info, in_width, in_height) {
frame = av_frame_alloc();
@@ -57,7 +59,13 @@ void FfmpegEncoder::encoder_open() {
this->codec_ctx->height = frame->height;
this->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
this->codec_ctx->time_base = (AVRational){ 1, encoder_info.fps };
int err = avcodec_open2(this->codec_ctx, codec, NULL);
AVDictionary *opts = NULL;
if (env_dashy && codec_id == AV_CODEC_ID_H264) {
av_dict_set(&opts, "preset", "ultrafast", 0);
av_dict_set(&opts, "tune", "zerolatency", 0);
}
int err = avcodec_open2(this->codec_ctx, codec, &opts);
av_dict_free(&opts);
assert(err >= 0);
is_open = true;
+20 -1
View File
@@ -230,8 +230,25 @@ void loggerd_thread() {
std::unique_ptr<Context> ctx(Context::create());
std::unique_ptr<Poller> poller(Poller::create());
const bool lite = getenv("LITE");
const std::set<std::string> lite_skip_names = {
"driverCameraState",
"driverEncodeIdx",
"driverStateV2",
"driverMonitoringState",
"driverEncodeData",
"livestreamDriverEncodeIdx",
"livestreamDriverEncodeData",
// audio logs
"userBookmark",
"soundPressure",
"rawAudioData",
"audioFeedback",
};
// subscribe to all socks
for (const auto& [_, it] : services) {
if (lite && lite_skip_names.count(it.name)) continue;
const bool encoder = util::ends_with(it.name, "EncodeData");
const bool livestream_encoder = util::starts_with(it.name, "livestream");
const bool record_audio = (it.name == "rawAudioData") && Params().getBool("RecordAudio");
@@ -261,7 +278,9 @@ void loggerd_thread() {
std::vector<RemoteEncoder*> encoders_with_audio;
for (const auto &cam : cameras_logged) {
for (const auto &encoder_info : cam.encoder_infos) {
encoder_infos_dict[encoder_info.publish_name] = encoder_info;
const std::string &name = encoder_info.publish_name;
if (lite && lite_skip_names.count(name)) continue;
encoder_infos_dict[name] = encoder_info;
s.max_waiting++;
}
}
+1 -1
View File
@@ -93,7 +93,7 @@ const EncoderInfo main_wide_road_encoder_info = {
const EncoderInfo main_driver_encoder_info = {
.publish_name = "driverEncodeData",
.filename = "dcamera.hevc",
.record = Params().getBool("RecordFront"),
.record = !getenv("LITE") && Params().getBool("RecordFront"),
.get_settings = [](int in_width){return EncoderSettings::MainEncoderSettings(in_width);},
INIT_ENCODE_FUNCTIONS(DriverEncode),
};
+27 -2
View File
@@ -19,6 +19,8 @@ from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_I
from openpilot.common.swaglog import cloudlog, add_file_handler
from openpilot.system.version import get_build_metadata, terms_version, training_version
from openpilot.system.hardware.hw import Paths
from openpilot.system.manager.vehicle_model_collector import VehicleModelCollector
import time
def manager_init() -> None:
@@ -42,6 +44,7 @@ def manager_init() -> None:
default_value = params.get_default_value(k)
if default_value is not None and params.get(k) is None:
params.put(k, default_value)
params.put("dp_device_model_list", VehicleModelCollector().get())
# Create folders needed for msgq
try:
@@ -69,7 +72,8 @@ def manager_init() -> None:
if reg_res:
dongle_id = reg_res
else:
raise Exception(f"Registration failed for device {serial}")
dongle_id = "UnregisteredDevice"
# raise Exception(f"Registration failed for device {serial}")
os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog
os.environ['GIT_ORIGIN'] = build_metadata.openpilot.git_normalized_origin # Needed for swaglog
os.environ['GIT_BRANCH'] = build_metadata.channel # Needed for swaglog
@@ -126,6 +130,15 @@ def manager_thread() -> None:
ensure_running(managed_processes.values(), False, params=params, CP=sm['carParams'], not_run=ignore)
started_prev = False
dp_dev_delay_time_started: float = 0.
dp_dev_delay_loggerd = int(params.get('dp_dev_delay_loggerd') or 0)
# Dictionary of processes to be delayed [process_name: delay_seconds]
dp_dev_delay_start_times: dict[str, float] = {
'loggerd': dp_dev_delay_loggerd,
'encoderd': dp_dev_delay_loggerd
}
ignition_prev = False
while True:
@@ -146,10 +159,22 @@ def manager_thread() -> None:
if started != started_prev:
write_onroad_params(started, params)
dp_ignore: list[str] = []
if started and not started_prev:
dp_dev_delay_time_started = time.monotonic()
elif not started and started_prev:
dp_dev_delay_time_started = 0.
if dp_dev_delay_time_started > 0.:
cur_time = time.monotonic()
for name, delay_time in dp_dev_delay_start_times.items():
if cur_time - dp_dev_delay_time_started < delay_time: # type: ignore
dp_ignore.append(name)
started_prev = started
ignition_prev = ignition
ensure_running(managed_processes.values(), started, params=params, CP=sm['carParams'], not_run=ignore)
ensure_running(managed_processes.values(), started, params=params, CP=sm['carParams'], not_run=list(set(ignore) | set(dp_ignore)))
running = ' '.join("{}{}\u001b[0m".format("\u001b[32m" if p.proc.is_alive() else "\u001b[31m", p.name)
for p in managed_processes.values() if p.proc)
+17 -8
View File
@@ -55,6 +55,12 @@ def only_onroad(started: bool, params: Params, CP: car.CarParams) -> bool:
def only_offroad(started: bool, params: Params, CP: car.CarParams) -> bool:
return not started
def dashy(started: bool, params: Params, CP: car.CarParams) -> bool:
return int(params.get("dp_dev_dashy") or 0) > 0
def dashy_with_video(started: bool, params: Params, CP: car.CarParams) -> bool:
return int(params.get("dp_dev_dashy") or 0) == 2
def or_(*fns):
return lambda *args: operator.or_(*(fn(*args) for fn in fns))
@@ -66,23 +72,24 @@ procs = [
NativeProcess("loggerd", "system/loggerd", ["./loggerd"], logging),
NativeProcess("encoderd", "system/loggerd", ["./encoderd"], only_onroad),
NativeProcess("stream_encoderd", "system/loggerd", ["./encoderd", "--stream"], notcar),
NativeProcess("stream_encoderd", "system/loggerd", ["./encoderd", "--stream"], or_(notcar, and_(dashy_with_video, only_onroad))),
PythonProcess("logmessaged", "system.logmessaged", always_run),
NativeProcess("camerad", "system/camerad", ["./camerad"], driverview, enabled=not WEBCAM),
PythonProcess("webcamerad", "tools.webcam.camerad", driverview, enabled=WEBCAM),
NativeProcess("logcatd", "system/logcatd", ["./logcatd"], only_onroad, platform.system() != "Darwin"),
NativeProcess("proclogd", "system/proclogd", ["./proclogd"], only_onroad, platform.system() != "Darwin"),
PythonProcess("micd", "system.micd", iscar),
PythonProcess("micd", "system.micd", iscar, enabled=not os.getenv("LITE")),
PythonProcess("timed", "system.timed", always_run, enabled=not PC),
PythonProcess("modeld", "selfdrive.modeld.modeld", only_onroad),
PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)),
PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC) and not os.getenv("LITE")),
PythonProcess("sensord", "system.sensord.sensord", only_onroad, enabled=not PC),
NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)),
PythonProcess("raylib_ui", "selfdrive.ui.ui", always_run, enabled=False, watchdog_max_dt=(5 if not PC else None)),
PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad),
PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad, enabled=not os.getenv("LITE")),
PythonProcess("beepd", "dragonpilot.selfdrive.ui.beepd", only_onroad, enabled=(Params().get_bool("dp_device_beep") and os.getenv("LITE"))),
PythonProcess("locationd", "selfdrive.locationd.locationd", only_onroad),
NativeProcess("_pandad", "selfdrive/pandad", ["./pandad"], always_run, enabled=False),
PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad),
@@ -92,7 +99,8 @@ procs = [
PythonProcess("selfdrived", "selfdrive.selfdrived.selfdrived", only_onroad),
PythonProcess("card", "selfdrive.car.card", only_onroad),
PythonProcess("deleter", "system.loggerd.deleter", always_run),
PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(WEBCAM or not PC)),
PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(WEBCAM or not PC) and not os.getenv("LITE")),
PythonProcess("dpmonitoringd", "selfdrive.monitoring.dpmonitoringd", only_onroad, enabled=os.getenv("LITE")),
PythonProcess("qcomgpsd", "system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI),
PythonProcess("pandad", "selfdrive.pandad.pandad", always_run),
PythonProcess("paramsd", "selfdrive.locationd.paramsd", only_onroad),
@@ -107,13 +115,14 @@ procs = [
PythonProcess("updated", "system.updated.updated", only_offroad, enabled=not PC),
PythonProcess("uploader", "system.loggerd.uploader", always_run),
PythonProcess("statsd", "system.statsd", always_run),
PythonProcess("feedbackd", "selfdrive.ui.feedback.feedbackd", only_onroad),
PythonProcess("feedbackd", "selfdrive.ui.feedback.feedbackd", only_onroad, enabled=not os.getenv("LITE")),
# debug procs
NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar),
PythonProcess("webrtcd", "system.webrtc.webrtcd", notcar),
NativeProcess("bridge", "cereal/messaging", ["./bridge"], or_(notcar, and_(dashy_with_video, only_onroad))),
PythonProcess("webrtcd", "system.webrtc.webrtcd", or_(notcar, and_(dashy_with_video, only_onroad))),
PythonProcess("webjoystick", "tools.bodyteleop.web", notcar),
PythonProcess("joystick", "tools.joystick.joystick_control", and_(joystick, iscar)),
PythonProcess("dashy", "dragonpilot.dashy.backend.server", dashy),
]
managed_processes = {p.name: p for p in procs}
+163
View File
@@ -0,0 +1,163 @@
"""
Copyright (c) 2025 Rick Lan
This software is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0).
You are free to share and adapt this work for non-commercial purposes, provided you give appropriate credit and distribute any modifications under the same license.
To view a copy of this license, visit:
http://creativecommons.org/licenses/by-nc-sa/4.0/
---
**Commercial Licensing:**
Use of this software for commercial purposes is strictly prohibited without a separate, paid license.
To purchase a commercial license, please contact ricklan@gmail.com.
"""
import os
import importlib
import json
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
class VehicleModelCollector:
def __init__(self):
self.base_package = "opendbc.car"
self.base_path = f"{BASEDIR}/opendbc/car"
self.exclude_brands = ['body', 'mock']
# Define the lookup dictionary for brand-to-group mappings
self.brand_to_group_map = {
"chrysler": [
{"prefix": "DODGE_", "group": "Dodge"},
{"prefix": "RAM_", "group": "Ram"},
{"prefix": "JEEP_", "group": "Jeep"},
],
"gm": [
{"prefix": "BUICK_", "group": "Buick"},
{"prefix": "CADILLAC_", "group": "Cadillac"},
{"prefix": "CHEVROLET_", "group": "Chevrolet"},
{"prefix": "HOLDEN_", "group": "Holden"},
],
"honda": {"prefix": "ACURA_", "group": "Acura"},
"toyota": {"prefix": "LEXUS_", "group": "Lexus"},
"hyundai": [
{"prefix": "KIA_", "group": "Kia"},
{"prefix": "GENESIS_", "group": "Genesis"}
],
"volkswagen": [
{"prefix": "AUDI_", "group": "Audi"},
{"prefix": "SKODA_", "group": "Skoda"},
{"prefix": "SEAT_", "group": "Seat"}
]
}
# Define exceptions for group names
self.group_name_exceptions = {
"gm": "GM",
}
@staticmethod
def is_car_model(car_class, attr):
"""Check if the attribute is a car model (not callable and not a dunder attribute)"""
return not callable(getattr(car_class, attr)) and not attr.startswith("__")
@staticmethod
def move_to_proper_group(models, prefix):
"""
Moves models with a certain prefix to their respective group.
Example: Models starting with 'LEXUS_' should go to 'Lexus' group.
"""
moved_models = []
for model in models[:]: # Iterate over a copy to avoid modifying during iteration
if model.startswith(prefix):
moved_models.append(model)
models.remove(model) # Remove from the original group
return moved_models
def format_group_name(self, group_name):
"""
Formats group names according to the exceptions dictionary.
Groups in the exceptions dictionary are returned in all caps, others are title cased.
"""
return self.group_name_exceptions.get(group_name, group_name.title())
def collect_models(self):
"""Collect all car models and organize them by brand/group"""
# List all subdirectories (car brands)
car_brands = sorted([
name for name in os.listdir(self.base_path)
if os.path.isdir(os.path.join(self.base_path, name)) and not name.startswith("__")
])
grouped_models = {}
# Import CAR from each subdirectory and group models by brand
for brand in car_brands:
if brand in self.exclude_brands:
continue
module_name = f"{self.base_package}.{brand}.values"
try:
module = importlib.import_module(module_name)
if hasattr(module, "CAR"):
car_class = getattr(module, "CAR")
models = sorted([attr for attr in dir(car_class) if self.is_car_model(car_class, attr)])
# Check if the brand has a special group in the lookup map
if brand in self.brand_to_group_map:
group_info = self.brand_to_group_map[brand]
if isinstance(group_info, list): # If multiple prefixes for the brand
for prefix_info in group_info:
moved_models = self.move_to_proper_group(models, prefix_info["prefix"])
if moved_models:
if prefix_info["group"] not in grouped_models:
grouped_models[prefix_info["group"]] = []
grouped_models[prefix_info["group"]].extend(moved_models)
else: # Single prefix for the brand
moved_models = self.move_to_proper_group(models, group_info["prefix"])
if moved_models:
if group_info["group"] not in grouped_models:
grouped_models[group_info["group"]] = []
grouped_models[group_info["group"]].extend(moved_models)
# Add remaining models to the respective brand
if models:
grouped_models[brand] = models
except ModuleNotFoundError:
pass
# Sort the groups alphabetically
sorted_grouped_models = sorted(grouped_models.items(), key=lambda x: x[0])
# Convert to the desired output structure, ensuring models are sorted within each group
output = [{"group": self.format_group_name(group), "models": sorted(models)}
for group, models in sorted_grouped_models]
return output
# def save_to_params(self, output=None):
# """Save the collected model list to Params"""
# if output is None:
# output = self.collect_models()
# Params().put("dp_device_model_list", json.dumps(output))
# return output
#
# def run(self):
# """Collect models and save to params"""
# models = self.collect_models()
# self.save_to_params(models)
# return models
def get_json(self):
return self.collect_models()
def get(self):
return json.dumps(self.collect_models())
# Allow running as a script
if __name__ == "__main__":
collector = VehicleModelCollector()
print(collector.get())
+2 -1
View File
@@ -266,7 +266,8 @@ def init(pigeon: TTYPigeon) -> None:
set_power(False)
time.sleep(0.1)
set_power(True)
time.sleep(0.5)
# rick - make sleep twice long, give LITE more time.
time.sleep(1.0)
init_baudrate(pigeon)
init_pigeon(pigeon)
+32 -5
View File
@@ -42,7 +42,7 @@ class CerealOutgoingMessageProxy:
return msg_dict
def update(self):
async def update(self):
# this is blocking in async context...
self.sm.update(0)
for service, updated in self.sm.updated.items():
@@ -53,7 +53,10 @@ class CerealOutgoingMessageProxy:
outgoing_msg = {"type": service, "logMonoTime": mono_time, "valid": valid, "data": msg_dict}
encoded_msg = json.dumps(outgoing_msg).encode()
for channel in self.channels:
channel.send(encoded_msg)
if isinstance(channel, web.WebSocketResponse):
await channel.send_bytes(encoded_msg)
else:
channel.send(encoded_msg)
class CerealIncomingMessageProxy:
@@ -94,7 +97,7 @@ class CerealProxyRunner:
while True:
try:
self.proxy.update()
await self.proxy.update()
except InvalidStateError:
self.logger.warning("Cereal outgoing proxy invalid state (connection closed)")
break
@@ -229,7 +232,7 @@ async def get_stream(request: 'web.Request'):
stream_dict[session.identifier] = session
return web.json_response({"sdp": answer.sdp, "type": answer.type})
return web.json_response({"sdp": answer.sdp, "type": answer.type}, headers={'Access-Control-Allow-Origin': '*'})
async def get_schema(request: 'web.Request'):
@@ -246,23 +249,47 @@ async def on_shutdown(app: 'web.Application'):
del app['streams']
@web.middleware
async def cors_middleware(request, handler):
response = await handler(request)
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
return response
async def handle_cors_preflight(request):
if request.method == 'OPTIONS':
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
}
return web.Response(status=200, headers=headers)
return await request.app['handler'](request)
def webrtcd_thread(host: str, port: int, debug: bool):
logging.basicConfig(level=logging.CRITICAL, handlers=[logging.StreamHandler()])
logging_level = logging.DEBUG if debug else logging.INFO
logging.getLogger("WebRTCStream").setLevel(logging_level)
logging.getLogger("webrtcd").setLevel(logging_level)
app = web.Application()
app = web.Application(middlewares=[cors_middleware])
app['streams'] = dict()
app['debug'] = debug
app.on_shutdown.append(on_shutdown)
app.router.add_post("/stream", get_stream)
app.router.add_get("/schema", get_schema)
app.router.add_route('OPTIONS', '/{tail:.*}', handle_cors_preflight)
web.run_app(app, host=host, port=port)
def main():
parser = argparse.ArgumentParser(description="WebRTC daemon")
parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to listen on")