Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
🚋 HSL - LIVE — Live Helsinki Tram & Bus Tracker
A premium, high-performance web application mapping all active Helsinki trams, buses, metro, commuter trains, ferries, and City Bike stations in real-time. Built with stunning glassmorphism aesthetics, fluid 60fps telemetry interpolation, and immersive interactive modes.
👉 Experience the live dashboard at hsl-live.duckdns.org
📖 Check out recent updates and release history on the Live ChangelogREADME 전문 보기 ↗A premium, high-performance web application mapping all active Helsinki trams, buses, metro, commuter trains, and ferries in real-time. Built with stunning glassmorphism aesthetics, fluid 60fps telemetry interpolation, and immersive interactive modes.
👉 Experience the live dashboard at hsl.saavuori.live
📖 Check out recent updates and release history on the Live Changelog
The mobile UI on an iPhone-sized viewport: the live map with its "Where to?" search, mode and view toggles; the Lines bottom sheet for filtering the network; and the Details sheet with live telemetry for a selected vehicle.
Destination Journey Search: A top-center "Where to?" search plans a trip from your current location (used by default) to any searched destination, ranks the routes that get you there, and highlights the exact stops to use — boarding stop in green, final stop in coral, transfers in gold — while drawing each transit and walking leg on the map and fitting the journey into view.
Live Journey Monitoring: Choose departure or arrival date/time in Europe/Helsinki, then monitor the selected trips without silently switching to a different itinerary. Updated boarding/arrival predictions, platforms, cancellation/stale labels, relevant disruptions and estimated transfer margins remain available when the planner is minimized. Find alternatives explicitly when plans change.
Your Journey's Vehicles: Fresh, unambiguously matched vehicles receive gold map rings and can be selected from the journey panel. Matching requires the operating day and trip identity, never just a line number. The journey temporarily requests its bus/metro/train feeds without changing saved mode preferences; missing telemetry is shown as unavailable.
Live Departures & Saved Stops: The Departures panel finds nearby stops after an explicit location request and provides locally saved stops with departure previews. Selected stop boards refresh automatically, show departure countdowns and platforms, and distinguish live predictions, scheduled times, cancellations and stale data. Saved stops stay in this browser; no account is required.
Real-time 60fps Vehicle Interpolation: Live MQTT vehicle coordinate updates for trams, buses, metro, commuter trains and ferries are mathematically interpolated (lerp) for buttery-smooth vehicle movement.
Trams and Metro Trains Ride Their Own Rails: rail vehicles are drawn on the track geometry of the direction their journey is running in — taken from the HFP dir and Digitransit's pattern directionId — so a tram sits on its own side of the street instead of between the two tracks the map draws, and follows the rails through curves rather than cutting across them. Where a route passes through the same junction twice, the arm that continues the run the vehicle is already making wins; a vehicle genuinely off its route (diversion, depot run) is left where the feed puts it rather than dragged onto rails it is not using.
State-Coded Vehicle Markers: Every vehicle renders as a labelled circle carrying its line number, coloured by live state — #0984e3 blue while moving, #e17055 coral while stopped or with doors open. A separate heading arrow encodes the mode: a round green (#00985f) pointer for trams, a rounded-square blue (#007ac9) pointer for buses.
Immersive Chase Mode (Follow Vehicle): Lock onto any tram or bus to automatically track it. The camera auto-centers and auto-rotates (bearing) matching the vehicle's live heading, and releases as soon as you drag the map.
Ride Along — the map finds the vehicle you are in: tap I'm on board and your own position is matched against the live feed to work out which vehicle is carrying you; the app then selects it, opens its route and stop list, and locks the camera to it, so the map is about the run you are actually on. One fix cannot tell a ride from a tram passing the pavement you are standing on, so the evidence is gathered over a rolling window — how long a vehicle has stayed within reach, how close it has been, and how much ground the two of you have covered together — and how far away still counts is the vehicle's own length plus the phone's reported error plus the distance it has run since its last report. A vehicle is claimed outright only after a hundred and fifty metres of shared travel; on weaker evidence it is offered as a question. Two vehicles on different journeys that fit equally well are reported as ambiguous rather than guessed between, and stepping off ends the ride and releases the camera. Nothing starts on its own: the watch begins on a tap, and resumes unprompted only where the permission is already granted.
Tells You When To Get Off: a selected journey watches the leg you are riding and counts your stop down at the bottom of the map — gold two stops out, coral and a buzz when the vehicle's next stop is yours, Get off here when the doors open at it, and the same two moments on the lock screen once notifications are allowed. The count comes from the vehicle running your leg, looked up in the leg's own stop list; where no vehicle can be matched unambiguously it falls back to the arrival prediction and says so, since "the tram says your stop is next" and "the timetable thinks you arrive in ninety seconds" are different claims. A stop already behind the vehicle is reported as passed rather than counted down to.
Live Traffic Light Priority: Helsinki's trams do not simply wait for a green — they ask for one, and the junction answers. Both halves of that exchange ride the same HFP feed as the positions (tlr requests, tla answers), so the map shows it as it happens: the junction a tram is asking lights the lens it asked for — amber while the request is open, green when the junction acknowledges it, red when it refuses — and a signal a vehicle deliberately declined to ask is reported as that rather than as silence. The junction is joined by ID, not by proximity: HFP's sid is Helsinki's own junction number, which is the same number the open-data junction points carry. A stopped tram's popup therefore says waiting at the lights it named, not probably near some lights, and the Diagnostics tab carries the whole exchange — request type, priority level, attempt number, signal group and radio protocol.
Who Is Asking This Crossing: click a traffic light and the exchange reads from the junction's side — every vehicle currently negotiating with it, grouped into priority granted, still requesting, refused, and reached-it-but-did-not-ask. Each row carries the line in its own colour, the distance still to run, whether the vehicle is rolling or standing, what it asked for and on which attempt, and how long ago; picking one opens that vehicle's own panel. The selected junction takes the selection gold on its ring — never on its lenses, which go on saying what was asked.
Bunched Trams and the Gaps They Leave: two trams of the same line nose to tail, then nothing for a quarter of an hour, is the commonest way a line fails its riders, and no single vehicle's delay shows it — only the pair does. The backend times every vehicle out of every stop (the feed names the stop each vehicle is heading for, so the moment that changes is the moment it left), and each position carries how far it is running behind the vehicle ahead: the gap a rider at that stop actually waited. What the gap should be comes from the same passages — each vehicle's departure minus its reported delay is when it was due — and a line's headway is the median across its pairs, so a cancelled trip shows as the hole it leaves rather than as a new timetable. Bunched pairs (within 30% of the headway) are tied together on the map with a coral bar; a gap (1.8× the headway, and three minutes over it) runs in amber dashes along the rails between the two vehicles on any line you pick out. The Lines panel lists the worst of both with a dot on each affected line, and a vehicle's own panel shows the vehicles either side with the spacing to each drawn to scale. Between stops a gap is known only to be at least so long, and says so; a tram laying over at its terminus is never called bunched on a bound alone.
Traffic-Sign Stop Symbols: From zoom 15.5 the map swaps flat stop dots for sign-on-a-pole symbols, colour-coded per mode (tram #00985f, bus #007ac9, trunk bus #CA4300), with gold-bordered variants for the selected and next stop.
Interactive Route Network & Highlights: Toggle the background route network on the map. Click a stop to see all routes serving it highlighted, or click a vehicle to highlight its specific path plus a gold segment running to its next stop.
Light / Dark / Satellite Basemaps & 3D Buildings: Switch between the Digitransit HSL light basemap, a dark basemap, and the National Land Survey's aerial orthophotos (MML_API_KEY required — without it the satellite chip is not shown), and toggle pitched 3D building extrusions. Both preferences persist in localStorage.
The Ferry, and How Full It Is: The fifth mode is the Suomenlinna crossing, and it is the only one whose HFP occu field is a measurement rather than a placeholder — every road and rail vehicle sends a constant 0, the boats count passengers. So the ferry is the one vehicle on the map that shows its own load. Its marker is a vessel rather than a carriage — raked bow, wide beam, deckhouse inset between open decks — and the deckhouse is a saloon that fills from the stern forward in a colour that runs green through amber to red; the 3D body carries the same gauge along its saloon roof, where a pitched camera looks straight down on it; the telemetry panel states it in words and a meter. A vessel nobody has counted gets an empty grey gauge, never a green one: "no report" is not "nobody aboard".
Detailed 3D Vehicles: Distinct tram, bus, metro, commuter-train and ferry bodies have articulated sections, windows, running gear and roof equipment — the ferry instead a hull, a saloon, a bridge and a funnel, with a wake astern while it is making way and no wheels at all. A vehicle running on rails bends at its joints: each rigid section is placed at its own point on the track at the bearing the rails have there, with the gangway stretching between them, so a 27 m tram takes a street corner instead of ploughing its nose through the building outside the curve. Models fade in between zoom 13 and 14 (previously 15–16), retaining real-world dimensions. The vehicle button in the map-view controls enables Always show 3D vehicles, including on the flat map, independently of map tilt; this preference persists. Below zoom 13, icons remain visible for readability.
Live Doors & Lights: At close zooms, door leaves slide open and closed from reported drst changes, with a locally animated transition. Head/tail lamps mark direction; bus/tram rear lamps brighten while braking, and an amber roof indicator shows inferred braking for every mode. Braking is inferred from acceleration, standstill or open doors—not a directly reported lamp signal. Missing door telemetry leaves doors closed; rail door-side information is unavailable, so both sides are illustrated.
Self-Location (GPS): A geolocate control tracks and centres on your own position, styled to match the glassmorphic theme.
Timelapse — Replay the Last Week: Double-click the version badge in the bottom-left corner and the map stops being about now. A scrubber covers the last seven days of tram movement, with the recorded stretches shaded so the gaps a deploy or a broker reconnect left are visible rather than discovered; play it back at 1× through 240×, and a whole day runs past in six minutes. A replayed tram is not a different kind of thing from a live one — it carries the same telemetry, rides the same rails, opens the same doors — because the history stores exactly what the live map was shown. The panel is deliberately not part of the map's ordinary furniture: watching the city's past is a different errand from watching it now.
The archive behind it is a packed 28-byte record per reading, with each journey's line, trip and operating day interned once per day rather than repeated on all ~1,700 of its readings — so a week of trams is about 1.1 GB rather than fifteen, small enough that the page cache simply holds it. That is also what makes a place answerable rather than only a moment: asking what passed one junction all week is a scan of eight bytes per reading, most of them rejected without decoding anything else. Trams alone are recorded, because trams are the only mode ingested unconditionally — a bus history would have holes wherever nobody happened to be watching buses.
Telemetry / Schedule / Diagnostics sidebar sourced from raw HSL HFP v2 fields.drst flag, blink boarding indicators, and spin wheels at a rate proportional to velocity. See docs/ICONS_AND_ANIMATIONS.md for a full catalogue of every icon and animation with source examples.singleflight.Group deduplicates concurrent upstream queries, backed by an in-memory response cache — trip timetables and stop departures (10s TTL), service alerts (60s), and route geometries (1h) — keeping vehicle-selection latency in the low milliseconds.http.ServeMux method-and-pattern routing (Go 1.22+), coder/websocket for streaming, eclipse/paho.mqtt.golang to ingest live telemetry from HSL's public broker, and golang.org/x/sync/singleflight for query deduplication./hfp/v2/journey/ongoing/vp/tram/# on tls://mqtt.hsl.fi:8883, plus the bus, metro, train and ferry topics on demand — each only while a connected client has that mode switched on.REPLAY_DIR, REPLAY_RETENTION_DAYS and REPLAY_MODES; unset REPLAY_DIR to record nothing.allkeys-lru cap, tracking unique operator-prefixed vehicle IDs ({operator}-{vehicle}). An in-memory map is used instead when Redis is disabled.style.json, stop POI tiles, and rental-station tiles)./metrics, scraped by a Grafana Alloy sidecar and remote-written to Grafana Cloud.linux/amd64, linux/arm64) to GitHub Packages, plus a Pages workflow publishing the changelog. Full pipeline, release rules and runbook: docs/CICD.md.All endpoints are served by the Go backend under /api/v1. See docs/API_REFERENCE.md for payload schemas.
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/health | Liveness plus mqtt_connected, redis_connected, active_vehicles, uptime_seconds |
GET | /api/v1/version | Build version, build_date, and git_sha (injected via ldflags) |
GET | /api/v1/config | Browser-side map keys: the Digitransit map subscription key, and the National Land Survey key for the satellite basemap (empty when unconfigured) |
GET | /api/v1/alerts | Active HSL service disruptions |
GET | /api/v1/trip/{tripId} | Trip route, headsign, stop timeline, and geometry |
GET | /api/v1/stop/{stopId} | Stop details and upcoming departures (?departures=N) |
GET | /api/v1/stops/nearby | Nearby stops (?lat=&lon=&radius=), with distances |
GET | /api/v1/route/{shortName} | Route geometry and colour by short name |
GET | /api/v1/geocode | Destination search (Digitransit geocoding, ?text=&lat=&lon=) |
GET | /api/v1/plan | Journey planning between two points (?fromLat=&fromLon=&toLat=&toLon=) |
GET | /api/v1/journey/monitor | Refresh selected journey legs (?legId=..., repeated per leg) |
GET | /api/v1/replay/index | What history is recorded, and where its gaps are |
GET | /api/v1/replay/window | A slice of recorded history for playback (?from=&to=&step=) |
GET |
ratikka/
├── backend/ # Go application source
│ ├── cmd/ratikka/ # main entry point
│ ├── internal/ # config, cache, mqtt, ws, api packages
│ └── go.mod
├── frontend/ # React 19 TypeScript client source
│ ├── src/ # components, hooks, lib, styles, types
│ └── package.json
├── docs/ # Detailed architectural documents
│ ├── API_REFERENCE.md # REST/WS/external endpoint specs
│ ├── CICD.md # Workflows, release rules, deploy path, runbook
│ ├── ICONS_AND_ANIMATIONS.md # Tram/bus marker icons and animation reference
│ ├── LOCAL_DEVELOPMENT.md # How to run and test locally
│ ├── MONITORING.md # Metrics pipeline and dashboard import
│ ├── PLAN.md # Feature lists and mermaid architecture
│ ├── VERIFICATION.md # Quality gates and validation plans
│ └── screenshots/
├── monitoring/
│ ├── alloy/config.alloy # Grafana Alloy scrape + remote_write config
│ └── grafana/dashboard.json# Importable APM dashboard
├── scripts/ # CHANGELOG.md -> dist-changelog/ site generator
├── dist-changelog/ # Generated changelog site (GitHub Pages)
├── .github/workflows/ # Multi-arch image build + Pages deploy
├── .agents/workflows/ # Custom pair-programming guidelines
│ ├── committing.md # Commit rules
│ ├── map-features.md # Map layer conventions
│ └── versioning.md # CI/CD version bump rules
├── Caddyfile # Caddy reverse proxy rules
├── Dockerfile # Multi-stage build context
├── docker-compose.yml # Orchestrated compose definition
├── deploy.sh # One-shot RHEL/Podman provisioning script
└── CHANGELOG.md # Release history
Set the following in .env or in your environment. The backend auto-loads a .env file from the working directory, its parent, or backend/; real environment variables always take precedence.
| Variable | Description | Default |
|---|---|---|
DIGITRANSIT_API_KEY | Subscription key for the Digitransit GraphQL and Map APIs | (Required) |
DIGITRANSIT_MAP_API_KEY | Optional separate key served to browsers for map tiles (/api/v1/config). Use a dedicated rate-limited key here so the server-side routing key stays private | Falls back to DIGITRANSIT_API_KEY |
MML_API_KEY | Key for the National Land Survey's open map image service, served to browsers alongside the map key. It signs the orthophoto tiles behind the map's satellite view; without it that view is not offered. Free from MML's OmaTili | (Optional) |
REDIS_URL | Redis cache connection string | redis://ratikka-cache:6379 |
MQTT_BROKER | HSL public MQTT endpoint | tls://mqtt.hsl.fi:8883 |
PORT | Go backend server port | 8080 |
NO_REDIS | Set to true to use an in-memory cache instead of Redis (same as --no-redis) | false |
INFLUX_URL | InfluxDB v2 base URL. When set, every recorded reading is written to it as a vehicle_position point, for Grafana dashboards; see docs/MONITORING.md | (Off) |
INFLUX_TOKEN | InfluxDB API token with write access to the bucket | |
INFLUX_ORG | InfluxDB organization | |
INFLUX_BUCKET | InfluxDB bucket | ratikka |
INFLUX_MODES | Comma-separated modes written to InfluxDB | tram |
Consumed by the Grafana Alloy container in docker-compose.yml, not by the Go backend. Omit them to run without remote metrics.
| Variable | Description |
|---|---|
GRAFANA_CLOUD_PROMETHEUS_URL | Prometheus remote-write URL |
GRAFANA_CLOUD_PROMETHEUS_USER | Prometheus username / instance ID |
GRAFANA_CLOUD_PROMETHEUS_TOKEN | Prometheus access token |
See docs/MONITORING.md for the full telemetry pipeline and dashboard import steps.
To run a fast development loop locally, see docs/LOCAL_DEVELOPMENT.md for full options.
Pass --no-redis to skip running a local Redis container:
cd backend
go run ./cmd/ratikka --no-redis
(Server listens on port :8080)
cd frontend
npm install
npm run dev
(Vite runs on port :5173 and automatically proxies /api and the /api/v1/stream WebSocket to :8080)
cd backend && go test ./... — covers MQTT payload parsing/thinning, cache behaviour, config loading, WebSocket hub fan-out, and REST serialization.cd frontend && npx vitest run — covers linear coordinate interpolation and heading wrap-around maths.cd frontend && npm run lintBuilds the Node frontend, embeds the assets into the Go binary, and launches the full stack (Caddy, backend, Redis, Alloy):
# Set your API Key
export DIGITRANSIT_API_KEY="your-key" # Linux/macOS
# or $env:DIGITRANSIT_API_KEY="your-key" # Windows PowerShell
# Build and start services
docker compose up --build -d
# Verify server health
curl http://localhost/api/v1/health
Access the map dashboard in your web browser at http://localhost.
To deploy the application on a clean RHEL system:
curl -sSL -O https://raw.githubusercontent.com/Saavuori/ratikka/main/deploy.sh && bash deploy.sh
(The script configures unprivileged port binding, sets the firewall, installs Podman, downloads docker-compose.yml, Caddyfile, and config.alloy from the repository, prompts for API/monitoring keys, and starts the container stack. Images are refreshed by an update.sh cron job every 5 minutes rather than Watchtower, which is incompatible with rootless Podman.)
Dependabot opens one grouped minor/patch pull request per ecosystem every Monday morning, covering every place the repo pins a version — backend/go.mod, frontend/package.json, the GitHub Actions in .github/workflows/, the Dockerfile build and runtime stages, and the images in docker-compose.yml. Major updates come as their own PR; MapLibre majors are ignored entirely, because they need all three map checks run by hand (see .github/copilot-instructions.md). Config lives in .github/dependabot.yml.
Dependabot needs no secrets, no GitHub App and no scheduled workflow of its own — GitHub runs it. That is the whole reason it replaced the self-hosted Renovate setup. Renovate was self-hosted only because it had to run a post-upgrade command to write a CHANGELOG.md entry, and that entry was required because the top changelog heading was the release tag. The version now comes from the commit messages instead, so nothing has to be written for a dependency merge to ship, and the App could go away.
Dependency PRs use the chore(deps) / chore(deps-dev) commit prefix, which keeps them out of the feat: pattern — a batch of them cuts a patch release, not a minor. They write no changelog entry; fold them into the next hand-written one, and expand it when a bump actually matters.
The full flow — grouping rules, majors, hand sweeps, and what to do when a Monday passes with no PR — is docs/CICD.md §7.
/api/v1/replay/timelapseEverything that passed one place over a long span (?from=&to=&bbox=) |
GET | /api/v1/stream | WebSocket stream of live vehicle positions |
GET | /metrics | Prometheus exposition format |
GET | / | Embedded React SPA (go:embed static fallback) |