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

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 = &timestamp
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
}