You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

284 lines
9.5 KiB

5 years ago
package packets
import (
"encoding/binary"
"encoding/json"
"regexp"
"strconv"
"time"
"github.com/golang/protobuf/ptypes"
"github.com/pkg/errors"
"github.com/brocaar/loraserver/api/common"
"github.com/brocaar/loraserver/api/gw"
"github.com/brocaar/lorawan"
)
// loRaDataRateRegex contains a regexp for parsing the data-rate string.
var loRaDataRateRegex = regexp.MustCompile(`SF(\d+)BW(\d+)`)
// PushDataPacket type is used by the gateway mainly to forward the RF packets
// received, and associated metadata, to the server.
type PushDataPacket struct {
ProtocolVersion uint8
RandomToken uint16
GatewayMAC lorawan.EUI64
Payload PushDataPayload
}
// MarshalBinary encodes the packet into binary form compatible with the
// Semtech UDP protocol.
func (p PushDataPacket) MarshalBinary() ([]byte, error) {
pb, err := json.Marshal(&p.Payload)
if err != nil {
return nil, err
}
out := make([]byte, 4, len(pb)+12)
out[0] = p.ProtocolVersion
binary.LittleEndian.PutUint16(out[1:3], p.RandomToken)
out[3] = byte(PushData)
out = append(out, p.GatewayMAC[0:len(p.GatewayMAC)]...)
out = append(out, pb...)
return out, nil
}
// GetGatewayStats returns the gw.GatewayStats object (if the packet contains stats).
func (p PushDataPacket) GetGatewayStats() (*gw.GatewayStats, error) {
if p.Payload.Stat == nil {
return nil, nil
}
stats := gw.GatewayStats{
GatewayId: p.GatewayMAC[:],
RxPacketsReceived: p.Payload.Stat.RXNb,
RxPacketsReceivedOk: p.Payload.Stat.RXOK,
TxPacketsEmitted: p.Payload.Stat.TXNb,
TxPacketsReceived: p.Payload.Stat.DWNb,
}
// time
ts, err := ptypes.TimestampProto(time.Time(p.Payload.Stat.Time))
if err != nil {
return nil, errors.Wrap(err, "timestamp proto error")
}
stats.Time = ts
// location
if p.Payload.Stat.Lati != 0 && p.Payload.Stat.Long != 0 && p.Payload.Stat.Alti != 0 {
stats.Location = &common.Location{
Latitude: p.Payload.Stat.Lati,
Longitude: p.Payload.Stat.Long,
Altitude: float64(p.Payload.Stat.Alti),
Source: common.LocationSource_GPS,
}
}
return &stats, nil
}
// GetUplinkFrames returns a slice of gw.UplinkFrame.
func (p PushDataPacket) GetUplinkFrames(skipCRCCheck bool, FakeRxInfoTime bool) ([]gw.UplinkFrame, error) {
var frames []gw.UplinkFrame
for i := range p.Payload.RXPK {
// validate CRC
if p.Payload.RXPK[i].Stat != 1 && !skipCRCCheck {
continue
}
if len(p.Payload.RXPK[i].RSig) == 0 {
frame, err := getUplinkFrame(p.GatewayMAC[:], p.Payload.RXPK[i], FakeRxInfoTime)
if err != nil {
return nil, errors.Wrap(err, "gateway: get uplink frame error")
}
frames = append(frames, frame)
} else {
for j := range p.Payload.RXPK[i].RSig {
frame, err := getUplinkFrame(p.GatewayMAC[:], p.Payload.RXPK[i], FakeRxInfoTime)
if err != nil {
return nil, errors.Wrap(err, "gateway: get uplink frame error")
}
frame = setUplinkFrameRSig(frame, p.Payload.RXPK[i], p.Payload.RXPK[i].RSig[j])
frames = append(frames, frame)
}
}
}
return frames, nil
}
func setUplinkFrameRSig(frame gw.UplinkFrame, rxPK RXPK, rSig RSig) gw.UplinkFrame {
frame.RxInfo.Antenna = uint32(rSig.Ant)
frame.RxInfo.Channel = uint32(rSig.Chan)
frame.RxInfo.Rssi = int32(rSig.RSSIC)
frame.RxInfo.LoraSnr = rSig.LSNR
if len(rSig.ETime) != 0 {
frame.RxInfo.FineTimestampType = gw.FineTimestampType_ENCRYPTED
frame.RxInfo.FineTimestamp = &gw.UplinkRXInfo_EncryptedFineTimestamp{
EncryptedFineTimestamp: &gw.EncryptedFineTimestamp{
EncryptedNs: rSig.ETime,
AesKeyIndex: uint32(rxPK.AESK),
},
}
}
return frame
}
func getUplinkFrame(gatewayID []byte, rxpk RXPK, FakeRxInfoTime bool) (gw.UplinkFrame, error) {
frame := gw.UplinkFrame{
PhyPayload: rxpk.Data,
TxInfo: &gw.UplinkTXInfo{
Frequency: uint32(rxpk.Freq * 1000000),
},
RxInfo: &gw.UplinkRXInfo{
GatewayId: gatewayID,
Rssi: int32(rxpk.RSSI),
LoraSnr: rxpk.LSNR,
Channel: uint32(rxpk.Chan),
RfChain: uint32(rxpk.RFCh),
Board: uint32(rxpk.Brd),
Context: make([]byte, 4),
},
}
// Context
binary.BigEndian.PutUint32(frame.RxInfo.Context, rxpk.Tmst)
// Time.
if rxpk.Time != nil && !time.Time(*rxpk.Time).IsZero() {
ts, err := ptypes.TimestampProto(time.Time(*rxpk.Time))
if err != nil {
return frame, errors.Wrap(err, "gateway: timestamp proto error")
}
frame.RxInfo.Time = ts
} else if FakeRxInfoTime {
ts, _ := ptypes.TimestampProto(time.Now().UTC())
frame.RxInfo.Time = ts
}
// Time since GPS epoch
if rxpk.Tmms != nil {
d := time.Duration(*rxpk.Tmms) * time.Millisecond
frame.RxInfo.TimeSinceGpsEpoch = ptypes.DurationProto(d)
}
// LoRa data-rate
if rxpk.DatR.LoRa != "" {
frame.TxInfo.Modulation = common.Modulation_LORA
match := loRaDataRateRegex.FindStringSubmatch(rxpk.DatR.LoRa)
// parse e.g. SF12BW250 into separate variables
if len(match) != 3 {
return frame, errors.New("gateway: could not parse LoRa data-rate")
}
// cast variables to ints
sf, err := strconv.Atoi(match[1])
if err != nil {
return frame, errors.Wrap(err, "gateway: could not convert sf to int")
}
bw, err := strconv.Atoi(match[2])
if err != nil {
return frame, errors.Wrap(err, "gateway: could not parse bandwidth to int")
}
frame.TxInfo.ModulationInfo = &gw.UplinkTXInfo_LoraModulationInfo{
LoraModulationInfo: &gw.LoRaModulationInfo{
Bandwidth: uint32(bw),
SpreadingFactor: uint32(sf),
CodeRate: rxpk.CodR,
},
}
}
// FSK data-rate
if rxpk.DatR.FSK != 0 {
frame.TxInfo.Modulation = common.Modulation_FSK
frame.TxInfo.ModulationInfo = &gw.UplinkTXInfo_FskModulationInfo{
FskModulationInfo: &gw.FSKModulationInfo{
Bitrate: uint32(rxpk.DatR.FSK),
},
}
}
return frame, nil
}
// UnmarshalBinary decodes the packet from Semtech UDP binary form.
func (p *PushDataPacket) UnmarshalBinary(data []byte) error {
if len(data) < 13 {
return errors.New("gateway: at least 13 bytes are expected")
}
if data[3] != byte(PushData) {
return errors.New("gateway: identifier mismatch (PUSH_DATA expected)")
}
if !protocolSupported(data[0]) {
return ErrInvalidProtocolVersion
}
p.ProtocolVersion = data[0]
p.RandomToken = binary.LittleEndian.Uint16(data[1:3])
for i := 0; i < 8; i++ {
p.GatewayMAC[i] = data[4+i]
}
return json.Unmarshal(data[12:], &p.Payload)
}
// PushDataPayload represents the upstream JSON data structure.
type PushDataPayload struct {
RXPK []RXPK `json:"rxpk,omitempty"`
Stat *Stat `json:"stat,omitempty"`
}
// Stat contains the status of the gateway.
type Stat struct {
Time ExpandedTime `json:"time"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format (e.g 2014-01-12 08:59:28 GMT)
Lati float64 `json:"lati"` // GPS latitude of the gateway in degree (float, N is +)
Long float64 `json:"long"` // GPS latitude of the gateway in degree (float, E is +)
Alti int32 `json:"alti"` // GPS altitude of the gateway in meter RX (integer)
RXNb uint32 `json:"rxnb"` // Number of radio packets received (unsigned integer)
RXOK uint32 `json:"rxok"` // Number of radio packets received with a valid PHY CRC
RXFW uint32 `json:"rxfw"` // Number of radio packets forwarded (unsigned integer)
ACKR float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged
DWNb uint32 `json:"dwnb"` // Number of downlink datagrams received (unsigned integer)
TXNb uint32 `json:"txnb"` // Number of packets emitted (unsigned integer)
}
// 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)
AESK uint8 `json:"aesk"` //AES key index used for encrypting fine timestamps
Chan uint8 `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer)
RFCh uint8 `json:"rfch"` // Concentrator "RF chain" used for RX (unsigned integer)
Stat int8 `json:"stat"` // CRC status: 1 = OK, -1 = fail, 0 = no CRC
Freq float64 `json:"freq"` // RX central frequency in MHz (unsigned float, Hz precision)
Brd uint32 `json:"brd"` // Concentrator board used for RX (unsigned integer)
RSSI int16 `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision)
Size uint16 `json:"size"` // RF packet payload size in bytes (unsigned integer)
DatR DatR `json:"datr"` // LoRa datarate identifier (eg. SF12BW500) || FSK datarate (unsigned, in bits per second)
Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK"
CodR string `json:"codr"` // LoRa ECC coding rate identifier
LSNR float64 `json:"lsnr"` // Lora SNR ratio in dB (signed float, 0.1 dB precision)
Data []byte `json:"data"` // Base64 encoded RF packet payload, padded
RSig []RSig `json:"rsig"` // Received signal information, per antenna (Optional)
}
// RSig contains the received signal information per antenna.
type RSig struct {
Ant uint8 `json:"ant"` // Antenna number on which signal has been received
Chan uint8 `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer)
RSSIC int16 `json:"rssic"` // RSSI in dBm of the channel (signed integer, 1 dB precision)
LSNR float64 `json:"lsnr"` // Lora SNR ratio in dB (signed float, 0.1 dB precision)
ETime []byte `json:"etime"` // Encrypted 'main' fine timestamp, ns precision [0..999999999] (Optional)
}