From 556f311044d44539844a62ebf379a7a0f6722859 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Tue, 12 Dec 2017 16:53:17 +0100 Subject: [PATCH] Correct tmms implementation (time since gps epoch). --- docs/content/overview/changelog.md | 11 ++- docs/content/use/data.md | 9 +- gateway/backend.go | 11 ++- gateway/backend_test.go | 85 ++++++++++--------- gateway/structs.go | 35 ++++---- .../brocaar/loraserver/api/gw/gw.go | 76 +++++++++++------ vendor/vendor.json | 4 +- 7 files changed, 139 insertions(+), 92 deletions(-) diff --git a/docs/content/overview/changelog.md b/docs/content/overview/changelog.md index c9b93b27..8db3f3ab 100644 --- a/docs/content/overview/changelog.md +++ b/docs/content/overview/changelog.md @@ -15,12 +15,17 @@ menu: * LoRa Gateway Bridge now publishes TX acknowledgement messages over MQTT. See [MQTT topics](https://docs.loraserver.io/lora-gateway-bridge/use/data/). -* TX (GPS) time field is now implemented to transmit at given timestamp - (only possible when the gateway has a GPS time-source). +* TX (GPS) `timeSinceGPSEpoch` field is now exposed to transmit at given + time since GPS epoch (1980-01-06, only possible when the gateway + has a GPS time source). + +* RX (GPS) `timeSinceGPSEpoch` field is now exposed, containing the time + since GPS epoch (1980-01-06, only available when the gateway has a GPS + time source). **Bugfixes:** -* Without GPS time-source, the gateway would use `0001-01-01T00:00:00Z` +* Without GPS time source, the gateway would use `0001-01-01T00:00:00Z` as RX `time`. The `time` field is now omitted when unavailable. diff --git a/docs/content/use/data.md b/docs/content/use/data.md index d5c54502..25e58913 100644 --- a/docs/content/use/data.md +++ b/docs/content/use/data.md @@ -64,7 +64,8 @@ Topic for received packets (from nodes). Example payload: "rfChain": 1, "rssi": -57, "size": 23, - "time": "2017-12-11T10:14:54.619571Z", // timestamp (only set when the gateway has a GPS time-source) + "time": "2017-12-12T15:28:53.222434Z", // timestamp (only set when the gateway has a GPS time source) + "timeSinceGPSEpoch": "332535h29m12.222s", // time since GPS epoch (only set when the gateway has a GPS time source) "timestamp": 2074240683 // gateway internal timestamp (32 bit) with microsecond precision } } @@ -92,14 +93,14 @@ Example payload: "immediately": false, "mac": "1dee08d0b691d149", "power": 14, - "timestamp": 2079240683, // gateway internal timestamp for transmission -OR- - "time": "2017-12-11T10:14:54.619571Z" // timestamp for transmission (only when the gateway has a GPS time-source) + "timestamp": 2079240683, // gateway internal timestamp for transmission -OR- + "timeSinceGPSEpoch": "332535h29m12.222s" // time since GPS epoch (only when the gateway has a GPS time source) } } ``` When `immediately` is set to `false`, either the `timestamp` **or** the -`time` field must be present to tell the gateway at what (internal) time +`timeSinceGPSEpoch` field must be present to tell the gateway at what (internal) time the frame must be transmitted. Optionally, the field `iPol` (type `bool`) can be used to control the diff --git a/gateway/backend.go b/gateway/backend.go index e7fb757b..fe35470c 100644 --- a/gateway/backend.go +++ b/gateway/backend.go @@ -448,6 +448,11 @@ func newRXPacketsFromRXPK(mac lorawan.EUI64, rxpk RXPK) ([]gw.RXPacketBytes, err rxPacket.RXInfo.Time = &ts } + if rxpk.Tmms != nil { + d := gw.Duration(time.Duration(*rxpk.Tmms) * time.Millisecond) + rxPacket.RXInfo.TimeSinceGPSEpoch = &d + } + if len(rxpk.RSig) == 0 { rxPackets = append(rxPackets, rxPacket) } @@ -481,9 +486,9 @@ func newTXPKFromTXPacket(txPacket gw.TXPacketBytes) (TXPK, error) { Brd: uint8(txPacket.TXInfo.Board), } - if txPacket.TXInfo.Time != nil { - ct := CompactTime(*txPacket.TXInfo.Time) - txpk.Tmms = &ct + if txPacket.TXInfo.TimeSinceGPSEpoch != nil { + tmms := int64(time.Duration(*txPacket.TXInfo.TimeSinceGPSEpoch) / time.Millisecond) + txpk.Tmms = &tmms } if txPacket.TXInfo.DataRate.Modulation == band.FSKModulation { diff --git a/gateway/backend_test.go b/gateway/backend_test.go index 613e9b82..bb74bc26 100644 --- a/gateway/backend_test.go +++ b/gateway/backend_test.go @@ -161,6 +161,7 @@ func TestBackend(t *testing.T) { Convey("When sending a PUSH_DATA packet with RXPK (CRC OK + GPS timestamp)", func() { ts := CompactTime(time.Now().UTC()) + tmms := int64(time.Second / time.Millisecond) p := PushDataPacket{ ProtocolVersion: ProtocolVersion2, @@ -171,6 +172,7 @@ func TestBackend(t *testing.T) { { Time: &ts, Tmst: 708016819, + Tmms: &tmms, Freq: 868.5, Chan: 2, RFCh: 1, @@ -359,17 +361,16 @@ func TestBackend(t *testing.T) { Convey("Given a TXPacket", func() { internalTS := uint32(12345) - ts := time.Now().UTC() - tsCompact := CompactTime(ts) + timeSinceGPSEpoch := gw.Duration(time.Second) txPacket := gw.TXPacketBytes{ TXInfo: gw.TXInfo{ - MAC: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, - Immediately: true, - Timestamp: &internalTS, - Time: &ts, - Frequency: 868100000, - Power: 14, + MAC: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, + Immediately: true, + Timestamp: &internalTS, + TimeSinceGPSEpoch: &timeSinceGPSEpoch, + Frequency: 868100000, + Power: 14, DataRate: band.DataRate{ Modulation: band.LoRaModulation, SpreadFactor: 12, @@ -419,13 +420,14 @@ func TestBackend(t *testing.T) { var pullResp PullRespPacket So(pullResp.UnmarshalBinary(buf[:i]), ShouldBeNil) + tmms := int64(time.Second / time.Millisecond) So(pullResp, ShouldResemble, PullRespPacket{ ProtocolVersion: p.ProtocolVersion, Payload: PullRespPayload{ TXPK: TXPK{ Imme: true, Tmst: &internalTS, - Tmms: &tsCompact, + Tmms: &tmms, Freq: 868.1, Powe: 14, Modu: "LORA", @@ -520,17 +522,17 @@ func TestNewGatewayStatPacket(t *testing.T) { func TestNewTXPKFromTXPacket(t *testing.T) { internalTS := uint32(12345) - ts := time.Now().UTC() - tsCompact := CompactTime(ts) Convey("Given a TXPacket", t, func() { + timeSinceGPSEpoch := gw.Duration(time.Second) + txPacket := gw.TXPacketBytes{ TXInfo: gw.TXInfo{ - Timestamp: &internalTS, - Time: &ts, - Frequency: 868100000, - Power: 14, - CodeRate: "4/5", + Timestamp: &internalTS, + TimeSinceGPSEpoch: &timeSinceGPSEpoch, + Frequency: 868100000, + Power: 14, + CodeRate: "4/5", DataRate: band.DataRate{ Modulation: band.LoRaModulation, SpreadFactor: 9, @@ -543,12 +545,13 @@ func TestNewTXPKFromTXPacket(t *testing.T) { } Convey("Then te expected TXPK is returned (with default IPol", func() { + tmms := int64(time.Second / time.Millisecond) txpk, err := newTXPKFromTXPacket(txPacket) So(err, ShouldBeNil) So(txpk, ShouldResemble, TXPK{ Imme: false, Tmst: &internalTS, - Tmms: &tsCompact, + Tmms: &tmms, Freq: 868.1, Powe: 14, Modu: "LORA", @@ -581,9 +584,12 @@ func TestNewRXPacketFromRXPK(t *testing.T) { Convey("Given a RXPK and gateway MAC", t, func() { now := time.Now().UTC() nowCompact := CompactTime(now) + tmms := int64(time.Second / time.Millisecond) + timeSinceGPSEpoch := gw.Duration(time.Second) rxpk := RXPK{ Time: &nowCompact, + Tmms: &tmms, Tmst: 708016819, Freq: 868.5, Chan: 2, @@ -608,13 +614,14 @@ func TestNewRXPacketFromRXPK(t *testing.T) { So(rxPackets[0].PHYPayload, ShouldResemble, []byte{1, 2, 3, 4}) So(rxPackets[0].RXInfo, ShouldResemble, gw.RXInfo{ - MAC: mac, - Time: &now, - Timestamp: 708016819, - Frequency: 868500000, - Channel: 2, - RFChain: 1, - CRCStatus: 1, + MAC: mac, + Time: &now, + TimeSinceGPSEpoch: &timeSinceGPSEpoch, + Timestamp: 708016819, + Frequency: 868500000, + Channel: 2, + RFChain: 1, + CRCStatus: 1, DataRate: band.DataRate{ Modulation: band.LoRaModulation, SpreadFactor: 7, @@ -651,13 +658,14 @@ func TestNewRXPacketFromRXPK(t *testing.T) { Convey("Then all fields are set correctly", func() { So(rxPackets[0].PHYPayload, ShouldResemble, []byte{1, 2, 3, 4}) So(rxPackets[0].RXInfo, ShouldResemble, gw.RXInfo{ - MAC: mac, - Time: &now, - Timestamp: 708016819, - Frequency: 868500000, - Channel: 3, - RFChain: 1, - CRCStatus: 1, + MAC: mac, + Time: &now, + TimeSinceGPSEpoch: &timeSinceGPSEpoch, + Timestamp: 708016819, + Frequency: 868500000, + Channel: 3, + RFChain: 1, + CRCStatus: 1, DataRate: band.DataRate{ Modulation: band.LoRaModulation, SpreadFactor: 7, @@ -673,13 +681,14 @@ func TestNewRXPacketFromRXPK(t *testing.T) { So(rxPackets[1].PHYPayload, ShouldResemble, []byte{1, 2, 3, 4}) So(rxPackets[1].RXInfo, ShouldResemble, gw.RXInfo{ - MAC: mac, - Time: &now, - Timestamp: 708016819, - Frequency: 868500000, - Channel: 3, - RFChain: 1, - CRCStatus: 1, + MAC: mac, + Time: &now, + TimeSinceGPSEpoch: &timeSinceGPSEpoch, + Timestamp: 708016819, + Frequency: 868500000, + Channel: 3, + RFChain: 1, + CRCStatus: 1, DataRate: band.DataRate{ Modulation: band.LoRaModulation, SpreadFactor: 7, diff --git a/gateway/structs.go b/gateway/structs.go index 0c7355f4..1b1571e4 100644 --- a/gateway/structs.go +++ b/gateway/structs.go @@ -377,6 +377,7 @@ func (d *DatR) UnmarshalJSON(data []byte) error { // RXPK contain a RF packet and associated metadata. type RXPK struct { Time *CompactTime `json:"time"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format (e.g. 2013-03-31T16:21:17.528002Z) + Tmms *int64 `json:"tmms"` // GPS time of pkt RX, number of milliseconds since 06.Jan.1980 Tmst uint32 `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) Freq float64 `json:"freq"` // RX central frequency in MHz (unsigned float, Hz precision) Brd uint8 `json:"brd"` // Concentrator board used for RX (unsigned integer) @@ -417,23 +418,23 @@ type Stat struct { // TXPK contains a RF packet to be emitted and associated metadata. type TXPK struct { - Imme bool `json:"imme"` // Send packet immediately (will ignore tmst & time) - Tmst *uint32 `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) - Tmms *CompactTime `json:"tmms,omitempty"` // Send packet at a certain time (GPS synchronization required) - Freq float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) - RFCh uint8 `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) - Powe uint8 `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) - Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" - DatR DatR `json:"datr"` // LoRa datarate identifier (eg. SF12BW500) || FSK datarate (unsigned, in bits per second) - CodR string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - FDev uint16 `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - IPol bool `json:"ipol"` // Lora modulation polarization inversion - Prea uint16 `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Size uint16 `json:"size"` // RF packet payload size in bytes (unsigned integer) - NCRC bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Data string `json:"data"` // Base64 encoded RF packet payload, padding optional - Brd uint8 `json:"brd"` // Concentrator board used for RX (unsigned integer) - Ant uint8 `json:"ant"` // Antenna number on which signal has been received + Imme bool `json:"imme"` // Send packet immediately (will ignore tmst & time) + Tmst *uint32 `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) + Tmms *int64 `json:"tmms,omitempty"` // Send packet at a certain GPS time (GPS synchronization required) + Freq float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) + RFCh uint8 `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) + Powe uint8 `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) + Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" + DatR DatR `json:"datr"` // LoRa datarate identifier (eg. SF12BW500) || FSK datarate (unsigned, in bits per second) + CodR string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + FDev uint16 `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + IPol bool `json:"ipol"` // Lora modulation polarization inversion + Prea uint16 `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Size uint16 `json:"size"` // RF packet payload size in bytes (unsigned integer) + NCRC bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Data string `json:"data"` // Base64 encoded RF packet payload, padding optional + Brd uint8 `json:"brd"` // Concentrator board used for RX (unsigned integer) + Ant uint8 `json:"ant"` // Antenna number on which signal has been received } // GetPacketType returns the packet type for the given packet data. diff --git a/vendor/github.com/brocaar/loraserver/api/gw/gw.go b/vendor/github.com/brocaar/loraserver/api/gw/gw.go index 9487ab8a..09cc3a4c 100644 --- a/vendor/github.com/brocaar/loraserver/api/gw/gw.go +++ b/vendor/github.com/brocaar/loraserver/api/gw/gw.go @@ -3,6 +3,7 @@ package gw import ( + "errors" "time" "github.com/brocaar/lorawan" @@ -23,20 +24,21 @@ type RXPacketBytes struct { // RXInfo contains the RX information. type RXInfo struct { - MAC lorawan.EUI64 `json:"mac"` // MAC address of the gateway - Time *time.Time `json:"time,omitempty"` // receive timestamp (only set when gateway has a GPS time-source) - Timestamp uint32 `json:"timestamp"` // gateway internal receive timestamp with microsecond precision, will rollover every ~ 72 minutes - Frequency int `json:"frequency"` // frequency in Hz - Channel int `json:"channel"` // concentrator IF channel used for RX - RFChain int `json:"rfChain"` // RF chain used for RX - CRCStatus int `json:"crcStatus"` // 1 = OK, -1 = fail, 0 = no CRC - CodeRate string `json:"codeRate"` // ECC code rate - RSSI int `json:"rssi"` // RSSI in dBm - LoRaSNR float64 `json:"loRaSNR"` // LoRa signal-to-noise ratio in dB - Size int `json:"size"` // packet payload size - DataRate band.DataRate `json:"dataRate"` // RX datarate (either LoRa or FSK) - Board int `json:"board"` // Concentrator board used for RX - Antenna int `json:"antenna"` // Antenna number on which signal has been received + MAC lorawan.EUI64 `json:"mac"` // MAC address of the gateway + Time *time.Time `json:"time,omitempty"` // Receive timestamp (only set when the gateway has a GPS time-source) + TimeSinceGPSEpoch *Duration `json:"timeSinceGPSEpoch,omitempty"` // Time since GPS epoch (1980-01-06, only set when the gateway has a GPS time source) + Timestamp uint32 `json:"timestamp"` // gateway internal receive timestamp with microsecond precision, will rollover every ~ 72 minutes + Frequency int `json:"frequency"` // frequency in Hz + Channel int `json:"channel"` // concentrator IF channel used for RX + RFChain int `json:"rfChain"` // RF chain used for RX + CRCStatus int `json:"crcStatus"` // 1 = OK, -1 = fail, 0 = no CRC + CodeRate string `json:"codeRate"` // ECC code rate + RSSI int `json:"rssi"` // RSSI in dBm + LoRaSNR float64 `json:"loRaSNR"` // LoRa signal-to-noise ratio in dB + Size int `json:"size"` // packet payload size + DataRate band.DataRate `json:"dataRate"` // RX datarate (either LoRa or FSK) + Board int `json:"board"` // Concentrator board used for RX + Antenna int `json:"antenna"` // Antenna number on which signal has been received } // TXPacket contains the PHYPayload which should be send to the @@ -55,19 +57,43 @@ type TXPacketBytes struct { PHYPayload []byte `json:"phyPayload"` } +// Duration implements time.Duration, but encoded to json, uses the String +// formatting. +type Duration time.Duration + +// MarshalJSON implements the json.Marshaler interface. +func (d Duration) MarshalJSON() ([]byte, error) { + return []byte(`"` + time.Duration(d).String() + `"`), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (d *Duration) UnmarshalJSON(data []byte) error { + if len(data) < 2 { + return errors.New("invalid duration string") + } + data = data[1 : len(data)-1] + + du, err := time.ParseDuration(string(data)) + if err != nil { + return err + } + *d = Duration(du) + return nil +} + // TXInfo contains the information used for TX. type TXInfo struct { - MAC lorawan.EUI64 `json:"mac"` // MAC address of the gateway - Immediately bool `json:"immediately"` // send the packet immediately (ignore Time) - Time *time.Time `json:"time,omitempty"` // transmit at timestamp (only possible when gateway has a GPS time-source) - Timestamp *uint32 `json:"timestamp,omitempty"` // transmit at gateway internal timestamp (microsecond precision, will rollover every ~ 72 minutes) - Frequency int `json:"frequency"` // frequency in Hz - Power int `json:"power"` // TX power to use in dBm - DataRate band.DataRate `json:"dataRate"` // TX datarate (either LoRa or FSK) - CodeRate string `json:"codeRate"` // ECC code rate - IPol *bool `json:"iPol"` // when left nil, the gateway-bridge will use the default (true for LoRa modulation) - Board int `json:"board"` // Concentrator board used for RX - Antenna int `json:"antenna"` // Antenna number on which signal has been received + MAC lorawan.EUI64 `json:"mac"` // MAC address of the gateway + Immediately bool `json:"immediately"` // send the packet immediately (ignore Time) + TimeSinceGPSEpoch *Duration `json:"timeSinceGPSEpoch,omitempty"` // Transmit at time since GPS epoch (since 1980-01-06, only possible when the gateway has a GPS time source) + Timestamp *uint32 `json:"timestamp,omitempty"` // transmit at gateway internal timestamp (microsecond precision, will rollover every ~ 72 minutes) + Frequency int `json:"frequency"` // frequency in Hz + Power int `json:"power"` // TX power to use in dBm + DataRate band.DataRate `json:"dataRate"` // TX datarate (either LoRa or FSK) + CodeRate string `json:"codeRate"` // ECC code rate + IPol *bool `json:"iPol"` // when left nil, the gateway-bridge will use the default (true for LoRa modulation) + Board int `json:"board"` // Concentrator board used for RX + Antenna int `json:"antenna"` // Antenna number on which signal has been received } // GatewayStatsPacket contains the information of a gateway. diff --git a/vendor/vendor.json b/vendor/vendor.json index 3ab898ce..ff4e581a 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,9 +3,9 @@ "ignore": "test", "package": [ { - "checksumSHA1": "VyoxN6hXttd3vCGIRENZhe31qIs=", + "checksumSHA1": "p6QgiezAbI49QUNRCJpiOHSPd4o=", "path": "github.com/brocaar/loraserver/api/gw", - "revision": "ce8511fb0b34878edd221b6b2568457cd4e0c9b6", + "revision": "8c78862f992c63e4f57842a1acb17b7fe763749c", "revisionTime": "2017-12-11T09:21:49Z" }, {