Skip to content

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

VariablePurpose
SECRET_KEY DEBUG ALLOWED_HOSTSDjango basics
DEVICE_TOKENSrobot_id:token_hash list. Do not put raw tokens here
EC2_DATA_DIRSession file root
CORS_EXTRA_ORIGINS CSRF_TRUSTED_ORIGINSFrontend 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

PathAuthPurpose
POST /api/v1/uploads/sessionsBearerSession upload. multipart
GET /api/v1/sessionsNonePublic session list
GET /api/v1/sessions/{id}NoneDetails and download links
GET /api/v1/sessions/{id}/data?dataset=NoneTime series. hand_command or motor_status
GET /api/v1/sessions/{id}/download/{kind}NoneFiles
GET /healthNoneDB 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.

  1. Authentication — Bearer token matches robot_id → 401
  2. Part composition — required parts present, unknown parts rejected → 400 / 415
  3. Size — per-part limits and total → 413
  4. Metadata schema — fields, timestamps, ended_at>started_at, result → 422
  5. Declared vs. actual match — size, SHA-256 → 422
  6. CSV contents — header, row count, finite values, stamp regression → 422
  7. content_digest recomputation — the server recalculates and compares → 422
  8. 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
partOptional. Uploads succeed without it because the uploader has not switched yet
ChecksUTF-8 decoding and JSON parsing only. CSV, binary, and truncated files are filtered out
content_digestIncluded. The robot exporter puts landmarks into the digest, so we match it (robots always send four files, so there is no conflict)
Count fieldNot required (the meaning of json_data is unclear)
DownloadLinks are shown only for sessions that were actually uploaded
Time-series queryNot provided (because columns cannot be built)

There are two files at the switchover point.

FileWhat it decides
apps/landmark_contract.pyFormat, whether it is required, and whether it is included in the digest
apps/limits.pyThe 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건
FileCountScope
tests_read.py55List, detail, time series, download, public boundary
tests_upload.py38Authentication, part, schema, digest, duplicates
tests_session.py28Path generation, staging→sessions atomicity
tests.py11Upper-bound relations, nginx conf comparison, health
tests_landmark.py8Landmark part boundary

tests_landmark.py fixes what is allowed and what is rejected while the format is still undecided.

  1. Does uploading three parts keep working?
  2. Do four-part uploads pass and can they be downloaded?
  3. Are broken JSON or CSV disguised files rejected?
  4. If parts arrive but the metadata declaration is missing, is the upload rejected?
  5. 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.