File size: 4,606 Bytes
d646dd7
 
 
 
 
 
 
 
 
95f5290
d646dd7
 
 
 
 
 
 
 
95f5290
d646dd7
95f5290
d646dd7
 
95f5290
d646dd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95f5290
d646dd7
 
 
 
 
 
 
 
 
bb59211
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d646dd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""Application configuration via environment variables."""

from pathlib import Path
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
    # Database. In local mode this normally points to the server through an
    # SSH tunnel (for example 127.0.0.1:15432).
    DATABASE_URL: str = "postgresql+asyncpg://odoo:myodoo@172.19.0.2:5432/data_acq"
    DB_CONNECT_TIMEOUT: float = 5.0
    DB_COMMAND_TIMEOUT: float = 10.0
    DB_POOL_TIMEOUT: float = 5.0

    # Local cache/staging directory. In SFTP mode the authoritative copy is
    # SFTP_ROOT on the storage server.
    STORAGE_DIR: str = "data"
    STORAGE_BACKEND: str = "local"  # local | sftp
    SFTP_HOST: str = "192.168.110.41"
    SFTP_PORT: int = 22
    SFTP_USERNAME: str = "Stouch"
    SFTP_PASSWORD: str = ""
    SFTP_KEY_FILE: str = ""
    SFTP_ROOT: str = "/vol1/docker/odoo-dev/Data Acquisition/data"
    SFTP_KNOWN_HOSTS: str = ""
    SFTP_STRICT_HOST_KEY: bool = False
    SFTP_CONNECT_TIMEOUT: int = 15

    # Auth
    API_KEY: str = "change-me"
    WORKER_API_KEY: str = ""
    DEFAULT_WORKFLOW_ID: str = ""
    # A cold NAS input-package build can take longer than the normal polling
    # request. Active jobs still send heartbeats during processing.
    WORKER_LEASE_SECONDS: int = 300

    # Upload limits
    MAX_UPLOAD_SIZE: int = 2 * 1024 * 1024 * 1024  # 2GB
    # Uploads are first spooled on the server's local disk.  The authoritative
    # dataset may live on the SSHFS/NAS mount, but receiving a large archive
    # must not make the API wait on remote filesystem writes.
    UPLOAD_STAGING_DIR: str = ""
    ALLOWED_VIDEO_EXTENSIONS: set[str] = {"mp4", "mkv", "mov", "avi", "m4v"}
    ALLOWED_IMAGE_EXTENSIONS: set[str] = {"jpg", "jpeg", "png", "bmp"}

    # Local server
    HOST: str = "0.0.0.0"
    PORT: int = 8000
    PUBLIC_BASE_URL: str = "http://192.168.110.41:2586"
    LOG_LEVEL: str = "info"

    # Cleaning
    CLEANING_ENABLED: bool = False

    # Export
    EXPORT_SHARD_SIZE: int = 100000
    VIDEO_SHARD_FRAMES: int = 5000

    # Training. Checkpoints are produced *outside* the platform by the official
    # ``lerobot-train`` CLI (conda env ``lerobot052_base_piper``), so this points
    # at an existing directory we only ever read. Empty = use the default below.
    TRAIN_OUTPUT_ROOT: str = ""

    @property
    def train_root(self) -> Path:
        """Trained-model directory — **read-only**, never created here.

        Defaults to ``<repo>/文件测试/umi_act_model``, i.e. beside ``processor/``.
        The directory is git-ignored and may legitimately be absent (fresh
        clone, or training never run on this host): callers must treat a
        missing path as "no runs" rather than creating it.  Creating it would
        make an empty training dir appear out of nowhere on every boot.
        """
        raw = str(self.TRAIN_OUTPUT_ROOT or "").strip()
        if raw:
            return Path(raw).expanduser()
        return (Path(__file__).resolve().parent.parent.parent
                / "文件测试" / "umi_act_model")

    model_config = {
        "env_prefix": "",
        "case_sensitive": False,
        "env_file": ".env",
        "extra": "ignore",
    }

    @property
    def storage_root(self) -> Path:
        """Absolute path to storage directory."""
        p = Path(self.STORAGE_DIR)
        if not p.is_absolute():
            p = Path(__file__).parent.parent / p
        p.mkdir(parents=True, exist_ok=True)
        return p

    @property
    def temp_root(self) -> Path:
        """Project-local temporary workspace; never falls back to the OS temp dir."""
        p = self.storage_root / "tmp"
        p.mkdir(parents=True, exist_ok=True)
        return p

    @property
    def upload_staging_root(self) -> Path:
        """Local durable spool for incoming archives.

        ``storage_root`` can be an SSHFS mount.  Keep the receive spool on the
        local machine so the upload response is independent of NAS latency.
        The queue persists its small job records in the authoritative state
        directory and resumes jobs whose spool file still exists.
        """
        raw = str(self.UPLOAD_STAGING_DIR or "").strip()
        p = Path(raw).expanduser() if raw else Path.home() / ".cache" / "egodata-ingest"
        p.mkdir(parents=True, exist_ok=True)
        return p


settings = Settings()

# Init subdirectories
# exports/cache 已弃用(导出改到 state/exports,缓存为内存级),不再自动创建
for sub in ["videos", "sessions", "tmp"]:
    (settings.storage_root / sub).mkdir(parents=True, exist_ok=True)