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.
163 lines
5.8 KiB
163 lines
5.8 KiB
5 years ago
|
package packets
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"time"
|
||
|
|
||
|
"github.com/golang/protobuf/ptypes"
|
||
|
"github.com/pkg/errors"
|
||
|
|
||
|
"github.com/brocaar/loraserver/api/common"
|
||
|
"github.com/brocaar/loraserver/api/gw"
|
||
|
)
|
||
|
|
||
|
// PullRespPacket is used by the server to send RF packets and associated
|
||
|
// metadata that will have to be emitted by the gateway.
|
||
|
type PullRespPacket struct {
|
||
|
ProtocolVersion uint8
|
||
|
RandomToken uint16
|
||
|
Payload PullRespPayload
|
||
|
}
|
||
|
|
||
|
// MarshalBinary marshals the object in binary form.
|
||
|
func (p PullRespPacket) MarshalBinary() ([]byte, error) {
|
||
|
pb, err := json.Marshal(&p.Payload)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
out := make([]byte, 4, 4+len(pb))
|
||
|
out[0] = p.ProtocolVersion
|
||
|
|
||
|
if p.ProtocolVersion != ProtocolVersion1 {
|
||
|
// these two bytes are unused in ProtocolVersion1
|
||
|
binary.LittleEndian.PutUint16(out[1:3], p.RandomToken)
|
||
|
}
|
||
|
out[3] = byte(PullResp)
|
||
|
out = append(out, pb...)
|
||
|
return out, nil
|
||
|
}
|
||
|
|
||
|
// UnmarshalBinary decodes the object from binary form.
|
||
|
func (p *PullRespPacket) UnmarshalBinary(data []byte) error {
|
||
|
if len(data) < 5 {
|
||
|
return errors.New("gateway: at least 5 bytes of data are expected")
|
||
|
}
|
||
|
if data[3] != byte(PullResp) {
|
||
|
return errors.New("gateway: identifier mismatch (PULL_RESP expected)")
|
||
|
}
|
||
|
if !protocolSupported(data[0]) {
|
||
|
return ErrInvalidProtocolVersion
|
||
|
}
|
||
|
p.ProtocolVersion = data[0]
|
||
|
p.RandomToken = binary.LittleEndian.Uint16(data[1:3])
|
||
|
return json.Unmarshal(data[4:], &p.Payload)
|
||
|
}
|
||
|
|
||
|
// PullRespPayload represents the downstream JSON data structure.
|
||
|
type PullRespPayload struct {
|
||
|
TXPK TXPK `json:"txpk"`
|
||
|
}
|
||
|
|
||
|
// 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)
|
||
|
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)
|
||
|
Ant uint8 `json:"ant"` // Antenna number on which signal has been received
|
||
|
Brd uint32 `json:"brd"` // Concentrator board used for RX (unsigned integer)
|
||
|
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)
|
||
|
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)
|
||
|
NCRC bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional)
|
||
|
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)
|
||
|
Data []byte `json:"data"` // Base64 encoded RF packet payload, padding optional
|
||
|
}
|
||
|
|
||
|
// GetPullRespPacket returns a PullRespPacket for the given gw.DownlinkFrame.
|
||
|
func GetPullRespPacket(protoVersion uint8, randomToken uint16, frame gw.DownlinkFrame) (PullRespPacket, error) {
|
||
|
packet := PullRespPacket{
|
||
|
ProtocolVersion: protoVersion,
|
||
|
RandomToken: randomToken,
|
||
|
Payload: PullRespPayload{
|
||
|
TXPK: TXPK{
|
||
|
Freq: float64(frame.TxInfo.Frequency) / 1000000,
|
||
|
Powe: uint8(frame.TxInfo.Power),
|
||
|
Modu: frame.TxInfo.Modulation.String(),
|
||
|
Size: uint16(len(frame.PhyPayload)),
|
||
|
Data: frame.PhyPayload,
|
||
|
Ant: uint8(frame.TxInfo.Antenna),
|
||
|
Brd: uint32(frame.TxInfo.Board),
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
if frame.TxInfo.Modulation == common.Modulation_LORA {
|
||
|
modInfo := frame.TxInfo.GetLoraModulationInfo()
|
||
|
if modInfo == nil {
|
||
|
return packet, errors.New("gateway: lora_modulation_info must not be nil")
|
||
|
}
|
||
|
packet.Payload.TXPK.DatR.LoRa = fmt.Sprintf("SF%dBW%d", modInfo.SpreadingFactor, modInfo.Bandwidth)
|
||
|
packet.Payload.TXPK.CodR = modInfo.CodeRate
|
||
|
packet.Payload.TXPK.IPol = modInfo.PolarizationInversion
|
||
|
}
|
||
|
|
||
|
if frame.TxInfo.Modulation == common.Modulation_FSK {
|
||
|
modInfo := frame.TxInfo.GetFskModulationInfo()
|
||
|
if modInfo == nil {
|
||
|
return packet, errors.New("gateway: fsk_modulation_info must not be nil")
|
||
|
}
|
||
|
packet.Payload.TXPK.DatR.FSK = modInfo.Bitrate
|
||
|
packet.Payload.TXPK.FDev = uint16(modInfo.Bitrate / 2) // TODO: is this correct?!
|
||
|
}
|
||
|
|
||
|
switch frame.TxInfo.Timing {
|
||
|
case gw.DownlinkTiming_IMMEDIATELY:
|
||
|
packet.Payload.TXPK.Imme = true
|
||
|
|
||
|
case gw.DownlinkTiming_DELAY:
|
||
|
timingInfo := frame.TxInfo.GetDelayTimingInfo()
|
||
|
if timingInfo == nil {
|
||
|
return packet, errors.New("delay_timing_info must not be nil")
|
||
|
}
|
||
|
|
||
|
delay, err := ptypes.Duration(timingInfo.Delay)
|
||
|
if err != nil {
|
||
|
return packet, errors.Wrap(err, "get delay duration error")
|
||
|
}
|
||
|
|
||
|
if len(frame.TxInfo.Context) < 4 {
|
||
|
return packet, fmt.Errorf("context must contain at least 4 bytes, got: %d", len(frame.TxInfo.Context))
|
||
|
}
|
||
|
timestamp := binary.BigEndian.Uint32(frame.TxInfo.Context[0:4])
|
||
|
timestamp += uint32(delay / time.Microsecond)
|
||
|
packet.Payload.TXPK.Tmst = ×tamp
|
||
|
|
||
|
case gw.DownlinkTiming_GPS_EPOCH:
|
||
|
timingInfo := frame.TxInfo.GetGpsEpochTimingInfo()
|
||
|
if timingInfo == nil {
|
||
|
return packet, errors.New("gps_epoch_timing must not be nil")
|
||
|
}
|
||
|
|
||
|
dur, err := ptypes.Duration(timingInfo.TimeSinceGpsEpoch)
|
||
|
if err != nil {
|
||
|
return packet, errors.Wrap(err, "parse time_since_gps_epoch error")
|
||
|
}
|
||
|
|
||
|
durMS := int64(dur / time.Millisecond)
|
||
|
packet.Payload.TXPK.Tmms = &durMS
|
||
|
|
||
|
default:
|
||
|
return packet, fmt.Errorf("unexpected downlink timing: %s", frame.TxInfo.Timing)
|
||
|
}
|
||
|
|
||
|
return packet, nil
|
||
|
}
|