diff --git a/README.md b/README.md
index 6bfd4fa..7807bfb 100644
--- a/README.md
+++ b/README.md
@@ -266,6 +266,8 @@ See [docs/COMMON-ERRORS.md](docs/COMMON-ERRORS.md#upgrades) if the service fails
**Chip version 0x00:** Concentrator not responding. Check that the concentrator module is seated, SPI is enabled (`raspi-config` → Interface Options → SPI), and try a full power cycle (unplug for 10+ seconds). Normal chip versions are `0x10` (SX1302) and `0x12` (SX1303).
+**`sx1261_check_status` / `lgw_start() failed` on RAK V2:** Clear `radio.sx1261_spi_path` in `local.yaml` (leave it `""`). The SX1261 is not Pi-visible on RAK/SenseCap boards. Use `location.source: uart` for onboard HAT GPS or `gpsd` for USB GPS. See [Common Errors](docs/COMMON-ERRORS.md).
+
**No packets:** Verify antenna is connected and frequency matches your region. Check `meshpoint logs` for `lgw_receive returned N packet(s)`.
**Upstream 401:** Bad API key. Get a free one at [meshradar.io](https://meshradar.io) and re-run `sudo meshpoint setup`.
diff --git a/config/default.yaml b/config/default.yaml
index b8f7bad..28fe903 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -129,12 +129,14 @@ location:
# and are not overwritten by gpsd.
# static : use device.latitude/longitude/altitude (default)
# gpsd : poll a local or remote gpsd daemon for live fixes
- # uart : reserved (RAK Pi HAT GPS, not yet wired)
+ # uart : on-board RAK Pi HAT GPS via /dev/ttyAMA0 (NMEA GGA)
source: "static"
# gpsd connection. Defaults match the well-known local socket; only
# change when running gpsd on a peer machine on the LAN.
gpsd_host: "127.0.0.1"
gpsd_port: 2947
+ uart_path: "/dev/ttyAMA0"
+ uart_baud: 9600
# How often the coordinator wakes to read the active source.
update_interval_seconds: 5
# Minimum acceptable fix mode: 0=any (incl. no-fix), 1=2D, 2=3D.
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index b561b94..8134d0a 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -4,6 +4,11 @@
- **MQTT broker TLS.** Transport TLS (`mqtts`, CA bundle, cert validation) is not implemented on `mqtt_publisher.py` (plain TCP only). Until then use plain port 1883 or a LAN broker without TLS.
+- **UART GPS.** `location.source: uart` reads NMEA GGA from the RAK Pi HAT (`/dev/ttyAMA0` by default). Configuration → GPS UART fieldset; `meshpoint setup` can select uart when the wizard probe gets a fix. Requires `pyserial` in the venv (`pip install -r requirements.txt` after upgrade).
+- **Concentrator model ID.** Startup logs SX1302 vs SX1303 when `libloragw` exposes `sx1302_get_model_id` (e.g. `Concentrator model ID: 0x12 (SX1303)`).
+- **SX1261 guard.** Non-empty `radio.sx1261_spi_path` on RAK/SenseCap carriers (`radio.carrier_type`) is cleared at configure time with an explicit warning, preventing `lgw_start()` failures from misconfigured spectral scan on Pi-invisible SX1261 wiring.
+- **SPI preflight.** Missing `/dev/spidev0.0` raises a clear error before `lgw_start()` (raspi-config / spi group hints).
+
### v0.7.6 (June 2026)
Meshtastic mesh participant release on `main` (merge `feat/v0.7.6`). Edge-only, pure Python, no concentrator recompile. **Upgrade:** Settings → Updates → **Stable**, or the full SSH block in `docs/COMMON-ERRORS.md` (`git fetch`, `checkout main`, `pull`, `scripts/install.sh`, `restart`). Required this release: new `cryptography` dependency for PKI and an updated `meshpoint.service` unit (RAK V2 reset fix). Pull-only upgrades can miss both. Witness-tested on RAK V2. Settings → Updates RC picker now points at **v0.7.7** on `feat/v0.7.7`.
diff --git a/docs/COMMON-ERRORS.md b/docs/COMMON-ERRORS.md
index 0616469..6c2f4f1 100644
--- a/docs/COMMON-ERRORS.md
+++ b/docs/COMMON-ERRORS.md
@@ -430,16 +430,54 @@ disabled in `raspi-config`, or the SPI bus latched after a hard power cut.
3. Full power cycle: `sudo poweroff`, wait for green LED to stop, unplug for
10+ seconds, then plug back in.
-Normal chip versions are `0x10` (SX1302) and `0x12` (SX1303).
+Normal chip versions are `0x10` (SX1302) and `0x12` (SX1303). Startup
+also logs `Concentrator model ID: 0x12 (SX1303)` when the HAL exposes
+`sx1302_get_model_id`.
+
+### `sx1261_check_status: got:0x00 expected:0x22` / `failed to patch sx1261`
+
+**Cause:** `radio.sx1261_spi_path` is set on a carrier where the SX1261
+companion chip is **not** wired to a Pi-visible SPI bus (RAK2287, RAK5146,
+SenseCap M1, most RAK Hotspot V2 units). The HAL probes a chip that is not
+there and may refuse `lgw_start()`.
+
+**Fix:**
+
+1. Edit `config/local.yaml` and clear the path:
+
+ ```yaml
+ radio:
+ sx1261_spi_path: ""
+ ```
+
+2. Restart: `sudo systemctl restart meshpoint`.
+
+3. Confirm the journal shows `SX1261 spi_path empty; spectral scan disabled`
+ and `Application startup complete`.
+
+On current Meshpoint builds with `radio.carrier_type: rak` or
+`sensecap_m1` (written by `meshpoint setup`), a mistaken path is cleared
+automatically at startup with a warning. Re-run `sudo meshpoint setup` if
+`carrier_type` is missing.
+
+See [Configuration > Spectral Scan](CONFIGURATION.md#spectral-scan-noise-floor).
### `lgw_start() failed` or `Failed to set SX1250_0 in STANDBY_RC mode`
-**Cause:** SPI bus latch from a hard power cut. The Meshpoint shutdown
-handler holds the concentrator in reset on `sudo reboot` and
-`sudo systemctl restart`, so this only appears after yanked-cable shutdowns,
-breaker trips, or outages.
+**Cause:** Usually one of:
+
+1. **SX1261 misconfiguration** — see the entry above if the log mentions
+ `sx1261_check_status` or `failed to patch sx1261`.
+2. **SPI bus latch** from a hard power cut. The Meshpoint shutdown handler
+ holds the concentrator in reset on `sudo reboot` and
+ `sudo systemctl restart`, so latch typically follows yanked-cable
+ shutdowns, breaker trips, or outages.
+3. **SPI disabled or device missing** — `/dev/spidev0.0` not present;
+ enable SPI in `raspi-config` and confirm the `meshpoint` user is in
+ the `spi` group.
-**Fix:** Full power cycle:
+**Fix:** If SX1261 lines appear in the log, fix `sx1261_spi_path` first.
+Otherwise full power cycle:
```bash
sudo poweroff
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index f12b077..cf94dd7 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -26,6 +26,7 @@ radio:
tx_power_dbm: 22 # SX1302 concentrator output power
spectral_scan_interval_seconds: 60 # noise floor sampler cadence (0 disables)
sx1261_spi_path: "" # SX1261 SPI device for spectral scan (empty = disabled)
+ carrier_type: "" # rak | sensecap_m1 (set by meshpoint setup; guards SX1261 path)
```
The region sets the base frequency, spreading factor, and bandwidth automatically. You only need `region` in most cases. Override `frequency_mhz`, `spreading_factor`, or `bandwidth_khz` individually to tune for non-default presets (MediumFast, ShortFast, etc.) or custom frequency slots.
@@ -76,6 +77,8 @@ ERROR: failed to patch sx1261 radio for LBT/Spectral Scan
…and `lgw_start()` may then refuse to bring up the concentrator. If you see that, revert `sx1261_spi_path` to `""`, restart the service, and stay on the packet-derived fallback.
+On RAK and SenseCap carriers, `meshpoint setup` writes `radio.carrier_type`. When that field is `rak` or `sensecap_m1`, Meshpoint **clears** a non-empty `sx1261_spi_path` at startup and logs a warning instead of calling `lgw_sx1261_setconf`.
+
If your `libloragw` build does not expose the spectral scan symbols at all (older HAL revisions), the service logs a single info line at startup and falls back automatically.
### Standard Meshtastic Presets
@@ -169,8 +172,10 @@ location:
source: "static" # static | gpsd | uart
gpsd_host: "127.0.0.1" # gpsd TCP host (only when source=gpsd)
gpsd_port: 2947 # gpsd TCP port
+ uart_path: "/dev/ttyAMA0" # serial device (only when source=uart)
+ uart_baud: 9600 # NMEA baud (uart only)
update_interval_seconds: 5 # how often the coordinator polls the source
- min_fix_quality: 1 # minimum NMEA fix quality (1=2D, 3=3D)
+ min_fix_quality: 1 # minimum fix quality (1=2D, 2=3D for gpsd/uart)
```
`location.source` selects where the Meshpoint reads **live GPS fixes**
@@ -185,7 +190,7 @@ coordinates and mesh position settings hot-reload from the dashboard.
|---|---|
| `static` (default) | No live GPS hardware. Registered coordinates live in `device.*` only. Skyplot shows the static pin. |
| `gpsd` | Reads live fixes from the system `gpsd` daemon over TCP (`127.0.0.1:2947`). Recommended for any USB GPS receiver (u-blox 7, u-blox 8, VFAN puck, generic CDC ACM sticks). Skyplot and stats update from the live fix. |
-| `uart` | Reserved for direct-serial reads from a Pi HAT GPS (e.g. RAK 7248). Currently a placeholder; falls back to static and surfaces an explanatory error in the dashboard. |
+| `uart` | Reads NMEA GGA from the on-board RAK Pi HAT GPS on `/dev/ttyAMA0` (or `uart_path`). Fix and satellite count update live; full skyplot az/el/SNR needs `gpsd` + USB receiver. |
### Mesh position broadcasts (LoRa / Meshtastic app map)
@@ -253,13 +258,35 @@ in `gpsd-clients`) or `gpsmon`.
| u-blox 7 USB stick | USB CDC ACM, NMEA + UBX | yes (RAK V2 .141) |
| u-blox 8 USB stick | USB CDC ACM, NMEA + UBX | yes |
| VFAN ublox 7 USB puck | USB CDC ACM, NMEA + UBX | yes |
-| RAK 7248 onboard u-blox via UART (`/dev/ttyAMA0`) | NMEA over UART | placeholder (`source: uart`, not yet wired) |
+| RAK 7248 onboard u-blox via UART (`/dev/ttyAMA0`) | NMEA over UART | yes (`source: uart`; `install.sh` enables UART) |
Other USB receivers should work as long as `gpsd` recognizes the
device's VID. If `cgps` shows data but the dashboard does not,
check `journalctl -u meshpoint | grep -i gpsd` for connection
errors and confirm `source: gpsd` in `local.yaml`.
+### Using uart (RAK Pi HAT onboard GPS)
+
+`scripts/install.sh` enables the primary UART (`/dev/ttyAMA0`), disables
+the serial console, and turns off Bluetooth on the UART pins so the HAT
+GPS module can talk to the Pi.
+
+1. Run `sudo meshpoint setup` outdoors and accept **Use live UART GPS**
+ when the wizard acquires a fix, **or** set in `local.yaml`:
+
+ ```yaml
+ location:
+ source: "uart"
+ uart_path: "/dev/ttyAMA0"
+ uart_baud: 9600
+ ```
+
+2. Restart: `sudo systemctl restart meshpoint`.
+
+3. Open **Configuration → GPS** → **UART**. Coordinates and satellite
+ count update every few seconds outdoors. The skyplot stays empty
+ until GSV parsing is added (use `gpsd` for a full skyplot today).
+
### Privacy
Three independent surfaces:
@@ -693,6 +720,8 @@ location: # GPS / location source
source: "static" # static | gpsd | uart
gpsd_host: "127.0.0.1"
gpsd_port: 2947
+ uart_path: "/dev/ttyAMA0"
+ uart_baud: 9600
update_interval_seconds: 5
min_fix_quality: 1
diff --git a/docs/HARDWARE-MATRIX.md b/docs/HARDWARE-MATRIX.md
index f751fcc..e651c6b 100644
--- a/docs/HARDWARE-MATRIX.md
+++ b/docs/HARDWARE-MATRIX.md
@@ -17,6 +17,7 @@ Application code is plain Python (v0.7.0+); **aarch64** is required.
|---|---|---|---|---|
| **Host** | Pi 4 (SD) | Pi 4 (SD) | CM4 (eMMC) | Pi 4 (SD) |
| **Concentrator** | RAK2287 (SX1302) | WM1303 (SX1303) | SX1302 (onboard) | RAK2287 (SX1302) |
+| **HAL chip version log** | `0x10` (SX1302) | `0x12` (SX1303) | `0x10` (SX1302) | `0x10` (SX1302) |
| **TX support** | Yes (native, with HAL patch) | Yes (native, with HAL patch) | Yes (native, with HAL patch) | Yes (native, with HAL patch) |
| **RX channels** | 8 simultaneous | 8 simultaneous | 8 simultaneous | 8 simultaneous |
| **Spreading factors** | SF7-SF12 simultaneous | SF7-SF12 simultaneous | SF7-SF12 simultaneous | SF7-SF12 simultaneous |
@@ -212,8 +213,10 @@ window-mounted deployments. For better coverage:
GPS antenna (u.FL to SMA pigtail) is optional. If your carrier board has a
u-blox GPS module, plugging in a GPS antenna gives you automatic
-positioning during the setup wizard. Otherwise enter coordinates manually
-(right-click any spot in Google Maps to copy in decimal format).
+positioning during the setup wizard. Set `location.source: uart` for the
+on-board RAK HAT module, or `location.source: gpsd` for a USB GPS stick.
+Otherwise enter coordinates manually (right-click any spot in Google Maps
+to copy in decimal format).
---
diff --git a/frontend/js/configuration/gps_card.js b/frontend/js/configuration/gps_card.js
index 3f90211..8352776 100644
--- a/frontend/js/configuration/gps_card.js
+++ b/frontend/js/configuration/gps_card.js
@@ -34,9 +34,10 @@ class GpsConfigCard {
GPS and placement
- Registered coordinates go to Meshradar. Choose whether
- the LoRa mesh hears your registered pin or a live GPS
- fix, with optional privacy rounding on live.
+ Registered coordinates go to Meshradar. Live fixes from
+ gpsd (USB), UART (RAK Pi HAT), or static entry feed the
+ skyplot. Mesh position broadcasts on the LoRa mesh are
+ configured separately below.