diff --git a/pkg/gatewayserver/io/io.go b/pkg/gatewayserver/io/io.go index 276a40291e..da078401c1 100644 --- a/pkg/gatewayserver/io/io.go +++ b/pkg/gatewayserver/io/io.go @@ -655,6 +655,11 @@ func (c *Connection) ScheduleDown(path *ttnpb.DownlinkPath, msg *ttnpb.DownlinkM settings.Time = request.AbsoluteTime case ttnpb.Class_CLASS_C: if request.AbsoluteTime != nil { + if !c.scheduler.IsGatewayTimeSynced() { + rxErrs = append(rxErrs, errNoGPSSync.New()) + continue + } + f = c.scheduler.ScheduleAt settings.Time = request.AbsoluteTime } else { diff --git a/pkg/gatewayserver/scheduling/clock.go b/pkg/gatewayserver/scheduling/clock.go index c440a41142..79b66e290a 100644 --- a/pkg/gatewayserver/scheduling/clock.go +++ b/pkg/gatewayserver/scheduling/clock.go @@ -38,16 +38,19 @@ type Clock interface { // RolloverClock is a Clock that takes roll-over of a uint32 microsecond concentrator time into account. type RolloverClock struct { - synced bool - relative uint32 - absolute ConcentratorTime - server *time.Time - gateway *time.Time + synced bool + relative uint32 + absolute ConcentratorTime + server *time.Time + gateway *time.Time + absoluteSynced bool } // IsSynced implements Clock. func (c *RolloverClock) IsSynced() bool { return c.synced } +func (c *RolloverClock) IsAbsoluteSynced() bool { return c.absoluteSynced } + // SyncTime implements Clock. func (c *RolloverClock) SyncTime() (time.Time, bool) { if !c.synced { @@ -82,6 +85,7 @@ func (c *RolloverClock) Sync(timestamp uint32, server time.Time) ConcentratorTim func (c *RolloverClock) SyncWithGatewayAbsolute(timestamp uint32, server, gateway time.Time) ConcentratorTime { ct := c.Sync(timestamp, server) c.gateway = &gateway + c.absoluteSynced = true return ct } diff --git a/pkg/gatewayserver/scheduling/scheduler.go b/pkg/gatewayserver/scheduling/scheduler.go index e574bc8f92..358e48740c 100644 --- a/pkg/gatewayserver/scheduling/scheduler.go +++ b/pkg/gatewayserver/scheduling/scheduler.go @@ -296,7 +296,7 @@ func (s *Scheduler) ScheduleAt(ctx context.Context, opts Options) (res Emission, if opts.UplinkToken != nil { s.syncWithUplinkToken(opts.UplinkToken) } - if !s.clock.IsSynced() { + if !s.clock.IsAbsoluteSynced() { return Emission{}, 0, errNoClockSync.New() } minScheduleTime := ScheduleTimeShort @@ -477,7 +477,7 @@ func (s *Scheduler) SyncWithGatewayConcentrator(timestamp uint32, server time.Ti func (s *Scheduler) IsGatewayTimeSynced() bool { s.mu.RLock() defer s.mu.RUnlock() - return s.clock.IsSynced() && s.clock.gateway != nil + return s.clock.IsAbsoluteSynced() && s.clock.gateway != nil } // Now returns an indication of the current concentrator time.