Project files
10 KB
Machine-translated by Robotheca from the original in Korean (opens in a new tab). License: Apache-2.0 (opens in a new tab).
thing — EC2 Data Portal
A read-only web portal that publishes completed session records from a tendon-driven robotic hand.
The robot (Raspberry Pi) pushes sessions over HTTPS once they have been judged. Visitors can view the list, details, and time series, and download files without logging in. There is no path here for operating the robot.
Basis: Requirements Specification V7.1
Where it lives
[내부망] [AWS EC2 · 본 저장소]
Raspberry Pi 5
└─ thing-data-uploader ──── Bearer Token HTTPS ────▶ Nginx
├─ React 정적 빌드
└─ Gunicorn · Django
├─ SQLite
└─ 세션 파일 디렉터리
▲
방문자 ── 로그인 없는 GET
We do not use Docker. Nginx, Gunicorn, Django, and SQLite run under systemd (see section 7.2).
The internal-network monitoring web app is a separate repository. This server does not join ROS 2 or DDS; it only receives uploads from the robots.
Two Principles
The only write path is upload
This is the boundary required by FR-50. POST /api/v1/uploads/sessions is the only write and requires a device token. Everything else is GET and requires no authentication.
It neither installs nor exposes robot commands, mode, records, Safety Reset, callbacks, sockets, ROS 2, or rclpy.
Server generates file names and paths
The file name sent by the client is never used for storage (FR-51). robot_id and session_id are checked with regular expressions to block directory traversal outright, and downloads are accepted only by kind name (enum). There is no point where a path is taken as input.
{EC2_DATA_DIR}/staging/{robot_id}/{session_id}/ 업로드 검증 중
{EC2_DATA_DIR}/sessions/{robot_id}/{session_id}/ 검증 통과. 공개 대상
Once validation completes, the file is moved from staging to sessions; if it fails, staging is deleted. The final reference for public data is status=READY SQLite row and the sessions directory.
Run
cd thing_database_web/backend
pip install -r requirements.txt
# config/.env 를 채운다. 아래 「설정」 표 참고
python manage.py migrate
python manage.py runserver
python manage.py test apps # 140건
cd thing_database_web/frontend
npm install
npm run dev
npm test # 48건
npm run build
Settings
| Variable | Purpose |
|---|---|
SECRET_KEY DEBUG ALLOWED_HOSTS | Django basics |
DEVICE_TOKENS | robot_id:token_hash list. Do not put raw tokens here |
EC2_DATA_DIR | Session file root |
CORS_EXTRA_ORIGINS CSRF_TRUSTED_ORIGINS | Frontend deployment domain |
DEVICE_TOKENS is injected via a 0600 systemd EnvironmentFile. Raw tokens exist only in the /etc/thing/uploader.env on the Raspberry Pi and must not be placed anywhere in Git, images, or logs (FR-51).
Deployment files are located in deploy/ — nginx_thing_database_web.conf, thing-database-web.service.
API
| Path | Auth | Purpose |
|---|---|---|
POST /api/v1/uploads/sessions | Bearer | Session upload. multipart |
GET /api/v1/sessions | None | Public session list |
GET /api/v1/sessions/{id} | None | Details and download links |
GET /api/v1/sessions/{id}/data?dataset= | None | Time series. hand_command or motor_status |
GET /api/v1/sessions/{id}/download/{kind} | None | Files |
GET /health | None | DB and disk check |
List and detail views expose only status=READY. STAGING and FAILED are not public.
Errors have the form {"error":{"code","message","details"},"request_id"}, and paths, tokens, and stack traces are never included in responses.
Checks the upload must pass
They are applied in order. If any one fails, the staging area is deleted and the stored files are left untouched.
- Authentication — Bearer token matches
robot_id→ 401 - Part composition — required parts present, unknown parts rejected → 400 / 415
- Size — per-part limits and total → 413
- Metadata schema — fields, timestamps,
ended_at>started_at, result → 422 - Declared vs. actual match — size, SHA-256 → 422
- CSV contents — header, row count, finite values, stamp regression → 422
content_digestrecomputation — the server recalculates and compares → 422- Duplicates — the same
(robot_id, session_id)with the same digest → 200; with a different digest → 409
content_digest is the idempotency key (NFR-26). So that re-exporting the same rosbag2 yields the same digest even when only exported_at differs, the computation excludes exported_at and content_digest themselves.
LandMark JSON — Before Format Is Finalized
Each session publishes four files (FR-49).
session_{id}_metadata.json
session_{id}_hand_command.csv
session_{id}_motor_status.csv
session_{id}_landmark.json ← 형식 미정
The internal structure of LandMark JSON is not yet defined. The two CSVs have headers and row rules, but landmarks do not. The actual size is also undecided.
So for now it is open to "accept anything that is valid JSON."
| Current behavior | |
|---|---|
| part | Optional. Uploads succeed without it because the uploader has not switched yet |
| Checks | UTF-8 decoding and JSON parsing only. CSV, binary, and truncated files are filtered out |
content_digest | Included. The robot exporter puts landmarks into the digest, so we match it (robots always send four files, so there is no conflict) |
| Count field | Not required (the meaning of json_data is unclear) |
| Download | Links are shown only for sessions that were actually uploaded |
| Time-series query | Not provided (because columns cannot be built) |
There are two files at the switchover point.
| File | What it decides |
|---|---|
apps/landmark_contract.py | Format, whether it is required, and whether it is included in the digest |
apps/limits.py | The part upper limit. The Django request limit follows automatically |
If limits.py is changed, Nginx must be changed too, but it is a configuration file and cannot be imported. So the test reads the conf, compares the values, and tells you which value to enter.
FAIL: test_nginx_conf_matches_part_limits
AssertionError: nginx conf 의 client_max_body_size 를 391M 이상으로 고치세요
The items that still need to be decided are summarized in docs/pending-decisions.md.
Structure
thing_database_web/
├─ docs/pending-decisions.md 확정이 필요한 항목
├─ deploy/ nginx conf · systemd unit
├─ backend/
│ ├─ config/ settings · urls
│ └─ apps/
│ ├─ models.py Session 1개 모델
│ ├─ upload_views.py 쓰기 엔드포인트. 8단계 검증
│ ├─ read_views.py 목록·상세·시계열·다운로드
│ ├─ validators.py part 상한 · metadata schema · CSV 규칙
│ ├─ digest.py content_digest canonical 계산
│ ├─ storage.py 경로 생성 · staging → sessions
│ ├─ device_auth.py Bearer 토큰
│ ├─ landmark_contract.py 미확정 landmark 계약 단일 출처
│ ├─ limits.py 업로드 크기 상한 단일 출처
│ ├─ errors.py throttles.py health.py serializers_v1.py
│ └─ tests*.py 140건
└─ frontend/
├─ src/views/ HomeView · SessionListView · SessionDetailView
├─ src/services/ axios 클라이언트
├─ src/ui/ src/utils/
└─ src/**/*.test.* 48건
React + Vite + Motion + recharts. Since most of the screen is in Korean, Pretendard is used, and measured values are set in JetBrains Mono so the digits align at a fixed width.
Tests
cd backend && python manage.py test apps # 140건
cd frontend && npm test # 48건
| File | Count | Scope |
|---|---|---|
tests_read.py | 55 | List, detail, time series, download, public boundary |
tests_upload.py | 38 | Authentication, part, schema, digest, duplicates |
tests_session.py | 28 | Path generation, staging→sessions atomicity |
tests.py | 11 | Upper-bound relations, nginx conf comparison, health |
tests_landmark.py | 8 | Landmark part boundary |
tests_landmark.py fixes what is allowed and what is rejected while the format is still undecided.
- Does uploading three parts keep working?
- Do four-part uploads pass and can they be downloaded?
- Are broken JSON or CSV disguised files rejected?
- If parts arrive but the metadata declaration is missing, is the upload rejected?
- Is the landmark included in
content_digest(matching the robot exporter)?
Item 5 is especially important. If the digest shifts, the same session conflicts with a 409.
What it does not do
- Robot control, mode, recording, Safety Reset (FR-50)
- ROS 2, rclpy, or DDS participation (Section 7.3)
- Publishing video or rosbag2
- S3 (excluded from the minimum review in Section 7.4)
- Login or member management — public GET requires no authentication, and uploads use only the device token
남은 항목
| 항목 | 상태 |
|---|---|
| LandMark JSON 형식 | pending-decisions.md P-1 |
| landmark 를 digest 에 넣는가 | 해결 — 포함(P-2, 로봇 따라) |
| landmark part 상한 | 같은 문서 P-3 |
| landmark 시계열 조회 | 같은 문서 P-4 |
| 업로더 네 part 전환 | 같은 문서 C-1 |
| 세션 보존·정리 정책 | NFR-29. 미구현 |
Files by @SeMinKong, at commit 4b39eab, read from GitHub in your browser. Hardware license: Apache-2.0; software license: Apache-2.0. Robotheca doesn't review code: read it before running it.