Repository navigation
Conversation
Some LoRa Basics Station implementations report the raw 32-bit concentrator timestamp as xtime, without a session and without accounting for rollovers. When the timestamp rolled over, the gateway clock went back in time and the sub band stayed blocked by the emissions scheduled before the rollover for up to 72 minutes. LoRa Basics Station never uses session 0, so for these gateways let the scheduler synchronize using the concentrator timestamp, which accounts for rollovers, and send downlink xtime values as 32-bit concentrator timestamps. Time transfers are not sent to these gateways. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Hardware validation on an affected gateway is still needed to confirm that truncated 32-bit xtime values produce successful downlinks with correct RX delay across rollover.
No open issues 🎉
What changed in this PR
Fixes Gateway Server downlink scheduling after 32-bit xtime rollover on LoRa Basics Station gateways without a session, while preserving behavior for gateways with a session.
Changes:
- Use rollover-aware scheduler clock synchronization for sessionless gateways.
- Encode their timestamp-based downlinks with 32-bit timestamps and skip time transfers.
- Add rollover regression tests, keep mock sessions nonzero, and document the fix.
| File | Description |
|---|---|
pkg/gatewayserver/io/semtechws/ws_util_test.go |
Prevent mock clocks from using session zero. |
pkg/gatewayserver/io/semtechws/time.go |
Add session detection and 32-bit xtime conversion helpers. |
pkg/gatewayserver/io/semtechws/time_test.go |
Test session detection and timestamp wrapping. |
pkg/gatewayserver/io/semtechws/lbslns/xtime_test.go |
Add a scheduler regression test spanning rollover. |
pkg/gatewayserver/io/semtechws/lbslns/upstream.go |
Defer sessionless clock synchronization to the rollover-aware scheduler. |
pkg/gatewayserver/io/semtechws/lbslns/downstream.go |
Wrap sessionless downlink timestamps and suppress time transfers. |
pkg/gatewayserver/io/semtechws/lbslns/downstream_test.go |
Test downlink rollover encoding and time-transfer suppression. |
CHANGELOG.md |
Document the sessionless gateway rollover fix. |
Review effort: Balanced
|
|
||
| sessionID, found := semtechws.GetSessionID(ctx) | ||
| if found && !semtechws.IsExtendedXTimeSession(sessionID) { | ||
| // Gateways that do not extend the concentrator timestamp do not implement time transfers. |
There was a problem hiding this comment.
I'm not sure if this is true though.
| // The gateway does not extend the 32-bit concentrator timestamp, so the XTime must wrap | ||
| // around in the same way. Subtract a second before truncating, since the RX delay is 1. | ||
| xTime = semtechws.ConcentratorTimeToTimestampXTime( | ||
| sessionID, concentratorTime-scheduling.ConcentratorTime(time.Second), | ||
| ) |
There was a problem hiding this comment.
If the gateway does not use a session ID and rolling over timestamp, this apparently creates an xtime which does, right?
lorawan-stack/pkg/gatewayserver/io/semtechws/time.go
Lines 108 to 115 in 93bcc1d
I would expect that gateways that simply send the rolling over 32-bit unsigned concentrator timestamp in xtime, also expect that for downlink.
Do not assume that LoRa Basics Station gateways without session do not implement time transfers; send the 32-bit concentrator timestamp as xtime instead. Remove the session ID from the 32-bit xtime conversion, as these gateways use the plain 32-bit concentrator timestamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Some LoRa Basics Station implementations report the raw 32-bit concentrator timestamp as
xtime, without a session (the upper bits are all zero) and without accounting for rollovers. When the timestamp rolled over (approximately every 72 minutes), the Gateway Server synchronized the gateway clock with the frontend-provided concentrator time, which went back in time by ~2³² µs. The emissions scheduled before the rollover then appeared to be in the future, and with the blocking window duty cycle style used for LBS gateways, all downlinks failed withsub band is blockedfor up to 72 minutes.Changes
xtime(bits 48-55). LoRa Basics Station never uses session 0 (ts_newXtimeSession).RolloverClock.Sync/SyncWithGatewayAbsolute), which accounts for rollovers.xtimeas a 32-bit concentrator timestamp (the RX delay is subtracted before truncating, so it wraps correctly).timesync) to these gateways.semtechwstests never use session 0.Testing
Steps
TestNonExtendedXTimeRolloverhandles an uplink at2³² - 60 swith a downlink, followed by an uplink after the rollover (xtime780557) with a downlink, through a realio.Connectionand scheduler with the blocking window duty cycle style.Results
All tests pass. Without the change in
upstream.go,TestNonExtendedXTimeRolloverfails withsub band is blocked for 1h10m37s, which reproduces the issue observed on a gateway.Regressions
LoRa Basics Station gateways with a session keep using the frontend clock synchronization and 48-bit
xtime, covered by the existingTestTraffic,TestFromDownlinkMessageandTestTransferTime.Notes for Reviewers
The downlink
xtimetruncation assumes that these gateways interpret the downlinkxtimethe same way they report it on uplinks, i.e. as the 32-bit concentrator timestamp to which the RX delay is added. Gateway-side logs confirm this before the rollover; a downlink right after a rollover still needs to be validated on a gateway.Checklist
README.mdfor the chosen target branch.CHANGELOG.md.CONTRIBUTING.md, there are no fixup commits left.🤖 Generated with Claude Code