= Development guide :toc: :toclevels: 2 == Web app only [source,shell] ---- bundle install bundle exec puma -C config/puma.rb ---- Open http://127.0.0.1:4567. For auto-reload during development, run via the `rerun` gem instead: [source,shell] ---- bundle exec rerun -- bundle exec puma -C config/puma.rb ---- TIP: Browsers apply heuristic caching to static files even without explicit cache headers, so an edit to `public/` (or `views/`) can appear not to have taken effect on the next reload. Force a real reload (e.g. Ctrl/Cmd+Shift+R) if that happens. == Offline map data To enable the offline style, place a PMTiles file at `data/offline.pmtiles` (see `docs/architecture.adoc` for how it is served and styled). It is not part of the repository — obtain or build one locally: * The simplest source is a pre-built extract from https://app.protomaps.com[protomaps.com's extract builder] — pick a region and download it as `.pmtiles`. Its schema is what `protomaps-themes-base` (the styling library used for the offline style) expects. * To convert some other vector tile source (e.g. an `.mbtiles` file) into PMTiles, use the https://github.com/protomaps/go-pmtiles[go-pmtiles] CLI: `pmtiles convert in.mbtiles data/offline.pmtiles`. Once the file is in place, `GET /api/offline-tiles` reports `available: true` and the map defaults to the offline style. == GNSS position (gpsd) The "GNSS" control needs https://gpsd.io[gpsd] running and pointed at a receiver (`gpsd /dev/ttyUSB0`, or whatever device the receiver shows up as). On openSUSE, `gpsd` is a package with systemd units (`gpsd.service` / `gpsd.socket`); on other distributions, check your package manager. No app-side configuration is needed — it just connects to `127.0.0.1:2947`, gpsd's default port. To test without hardware, feed gpsd a fake NMEA stream over TCP instead of a real serial device — gpsd accepts a `tcp://host:port` source: [source,shell] ---- # terminal 1: a TCP server repeating one NMEA GGA/RMC sentence pair python3 - <<'PY' 5001 import socket, sys, time def checksum(s): cs = 0 for ch in s: cs ^= ord(ch) return f"{cs:02X}" def sentence(body): return f"${body}*{checksum(body)}\r\n" port = int(sys.argv[1]) srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind(("127.0.0.1", port)) srv.listen(1) conn, _ = srv.accept() gga = "GPGGA,123519,4808.916,N,01706.462,E,1,08,0.9,140.4,M,46.9,M,," rmc = "GPRMC,123519,A,4808.916,N,01706.462,E,000.5,084.4,230824,003.1,W" while True: conn.sendall((sentence(gga) + sentence(rmc)).encode()) time.sleep(1) PY # terminal 2: point gpsd at it instead of a real device gpsd -N -n tcp://127.0.0.1:5001 ---- `GET /api/gnss/position` should then report the fake fix (Bratislava Castle, in the sentences above) within a couple of seconds.