本文整理汇总了Golang中github.com/brocaar/lorawan.PHYPayload.SetMIC方法的典型用法代码示例。如果您正苦于以下问题:Golang PHYPayload.SetMIC方法的具体用法?Golang PHYPayload.SetMIC怎么用?Golang PHYPayload.SetMIC使用的例子?那么恭喜您, 这里精选的方法代码示例或许可以为您提供帮助。您也可以进一步了解该方法所在类github.com/brocaar/lorawan.PHYPayload
的用法示例。
在下文中一共展示了PHYPayload.SetMIC方法的8个代码示例,这些例子默认根据受欢迎程度排序。您可以为喜欢或者感觉有用的代码点赞,您的评价将有助于系统推荐出更棒的Golang代码示例。
示例1: HandleDownlink
func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) {
// Get Device
dev, err := n.devices.Get(*message.AppEui, *message.DevEui)
if err != nil {
return nil, err
}
n.status.downlink.Mark(1)
dev.StartUpdate()
if dev.AppID != message.AppId || dev.DevID != message.DevId {
return nil, errors.NewErrInvalidArgument("Downlink", "AppID and DevID do not match AppEUI and DevEUI")
}
// Unmarshal LoRaWAN Payload
var phyPayload lorawan.PHYPayload
err = phyPayload.UnmarshalBinary(message.Payload)
if err != nil {
return nil, err
}
macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload)
if !ok {
return nil, errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload")
}
// Set DevAddr
macPayload.FHDR.DevAddr = lorawan.DevAddr(dev.DevAddr)
// FIRST set and THEN increment FCntDown
// TODO: For confirmed downlink, FCntDown should be incremented AFTER ACK
macPayload.FHDR.FCnt = dev.FCntDown
dev.FCntDown++
err = n.devices.Set(dev)
if err != nil {
return nil, err
}
// Sign MIC
phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey))
// Update message
bytes, err := phyPayload.MarshalBinary()
if err != nil {
return nil, err
}
message.Payload = bytes
return message, nil
}
示例2: doTestHandleActivation
func doTestHandleActivation(h *handler, appEUI types.AppEUI, devEUI types.DevEUI, devNonce [2]byte, appKey types.AppKey) (*pb.DeviceActivationResponse, error) {
devAddr := types.DevAddr{1, 2, 3, 4}
requestPHY := lorawan.PHYPayload{
MHDR: lorawan.MHDR{
MType: lorawan.JoinRequest,
Major: lorawan.LoRaWANR1,
},
MACPayload: &lorawan.JoinRequestPayload{
AppEUI: lorawan.EUI64(appEUI),
DevEUI: lorawan.EUI64(devEUI),
DevNonce: devNonce,
},
}
requestPHY.SetMIC(lorawan.AES128Key(appKey))
requestBytes, _ := requestPHY.MarshalBinary()
responsePHY := lorawan.PHYPayload{
MHDR: lorawan.MHDR{
MType: lorawan.JoinAccept,
Major: lorawan.LoRaWANR1,
},
MACPayload: &lorawan.JoinAcceptPayload{},
}
templateBytes, _ := responsePHY.MarshalBinary()
return h.HandleActivation(&pb_broker.DeduplicatedDeviceActivationRequest{
Payload: requestBytes,
AppEui: &appEUI,
AppId: appEUI.String(),
DevEui: &devEUI,
DevId: devEUI.String(),
ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{
Lorawan: &pb_lorawan.ActivationMetadata{
DevAddr: &devAddr,
},
}},
ResponseTemplate: &pb_broker.DeviceActivationResponse{
Payload: templateBytes,
},
})
}
示例3: handleDataDownReply
//.........这里部分代码省略.........
MType: lorawan.UnconfirmedDataDown,
Major: lorawan.LoRaWANR1,
},
}
macPL = &lorawan.MACPayload{
FHDR: lorawan.FHDR{
DevAddr: ns.DevAddr,
FCtrl: lorawan.FCtrl{
ACK: rxPacket.PHYPayload.MHDR.MType == lorawan.ConfirmedDataUp, // set ACK when uplink packet was of type ConfirmedDataUp
FPending: queueSize > 0 || len(allMACPayloads) != len(macPayloads), // items in the queue or not all mac commands being sent
},
FCnt: ns.FCntDown,
},
}
phy.MACPayload = macPL
if len(macCommmands) > 0 {
if frmMACCommands {
var fPort uint8 // 0
var frmPayload []lorawan.Payload
for i := range macCommmands {
frmPayload = append(frmPayload, &macCommmands[i])
}
macPL.FPort = &fPort
macPL.FRMPayload = frmPayload
} else {
macPL.FHDR.FOpts = macCommmands
}
}
// add the payload to FRMPayload field
// note that txPayload is by definition nil when there are mac commands
// to send in the FRMPayload field.
if txPayload != nil {
if txPayload.Confirmed {
phy.MHDR.MType = lorawan.ConfirmedDataDown
}
macPL.FPort = &txPayload.FPort
macPL.FRMPayload = []lorawan.Payload{
&lorawan.DataPayload{Bytes: txPayload.Data},
}
}
// if there is no payload set, encrypt will just do nothing
if len(macCommmands) > 0 && frmMACCommands {
if err := phy.EncryptFRMPayload(ns.NwkSKey); err != nil {
return fmt.Errorf("encrypt FRMPayload error: %s", err)
}
} else {
if err := phy.EncryptFRMPayload(ns.AppSKey); err != nil {
return fmt.Errorf("encrypt FRMPayload error: %s", err)
}
}
if err := phy.SetMIC(ns.NwkSKey); err != nil {
return fmt.Errorf("set MIC error: %s", err)
}
txPacket := models.TXPacket{
TXInfo: models.TXInfo{
MAC: rxPacket.RXInfo.MAC,
Timestamp: rxPacket.RXInfo.Timestamp + uint32(properties.rxDelay/time.Microsecond),
Frequency: properties.rx1Frequency,
Power: common.Band.DefaultTXPower,
DataRate: common.Band.DataRates[properties.rx1DR],
CodeRate: rxPacket.RXInfo.CodeRate,
},
PHYPayload: phy,
}
// window 1
if err := ctx.Gateway.SendTXPacket(txPacket); err != nil {
return fmt.Errorf("send tx packet (rx window 1) to gateway error: %s", err)
}
// increment the FCntDown when MType != ConfirmedDataDown and clear
// in-process queue. In case of ConfirmedDataDown we increment on ACK.
if phy.MHDR.MType != lorawan.ConfirmedDataDown {
ns.FCntDown++
if err = storage.SaveNodeSession(ctx.RedisPool, ns); err != nil {
return err
}
if txPayload != nil {
if _, err = storage.ClearInProcessTXPayload(ctx.RedisPool, ns.DevEUI); err != nil {
return err
}
}
}
// remove the mac commands from the queue
for _, pl := range macPayloads {
if err = storage.DeleteMACPayloadFromTXQueue(ctx.RedisPool, ns.DevAddr, pl); err != nil {
return fmt.Errorf("delete mac-payload from tx queue error: %s", err)
}
}
return nil
}
示例4: handleDataDownReply
//.........这里部分代码省略.........
MType: lorawan.UnconfirmedDataDown,
Major: lorawan.LoRaWANR1,
},
}
macPL = &lorawan.MACPayload{
FHDR: lorawan.FHDR{
DevAddr: ns.DevAddr,
FCtrl: lorawan.FCtrl{
ACK: rxPacket.PHYPayload.MHDR.MType == lorawan.ConfirmedDataUp, // set ACK to true when received packet needs an ACK
},
FCnt: ns.FCntDown,
},
}
phy.MACPayload = macPL
// add the payload from the queue
if txPayload != nil {
// validate the max payload size
if len(txPayload.Data) > Band.MaxPayloadSize[rx1DR].N {
// remove the payload from the queue regarding confirmed or not
if _, err := clearInProcessTXPayload(ctx.RedisPool, ns.DevEUI); err != nil {
return err
}
log.WithFields(log.Fields{
"dev_eui": ns.DevEUI,
"data_rate": rx1DR,
"frmpayload_size": len(txPayload.Data),
"max_frmpayload_size": Band.MaxPayloadSize[rx1DR].N,
}).Warning("downlink payload max size exceeded")
err = ctx.Application.SendNotification(ns.DevEUI, ns.AppEUI, models.ErrorNotificationType, models.ErrorNotification{
Reference: txPayload.Reference,
DevEUI: ns.DevEUI,
Message: fmt.Sprintf("downlink payload max size exceeded (dr: %d, allowed: %d, got: %d)", rx1DR, Band.MaxPayloadSize[rx1DR].N, len(txPayload.Data)),
})
if err != nil {
return err
}
} else {
// remove the payload from the queue when not confirmed
if !txPayload.Confirmed {
if _, err := clearInProcessTXPayload(ctx.RedisPool, ns.DevEUI); err != nil {
return err
}
}
macPL.FHDR.FCtrl.FPending = remaining
if txPayload.Confirmed {
phy.MHDR.MType = lorawan.ConfirmedDataDown
}
macPL.FPort = &txPayload.FPort
macPL.FRMPayload = []lorawan.Payload{
&lorawan.DataPayload{Bytes: txPayload.Data},
}
}
}
// when the payload did not pass the validation and there is no ACK set,
// there is nothing to send
if !macPL.FHDR.FCtrl.ACK && len(macPL.FRMPayload) == 0 {
return nil
}
// if there is no payload set, encrypt will just do nothing
if err := phy.EncryptFRMPayload(ns.AppSKey); err != nil {
return fmt.Errorf("encrypt FRMPayload error: %s", err)
}
if err := phy.SetMIC(ns.NwkSKey); err != nil {
return fmt.Errorf("set MIC error: %s", err)
}
txPacket := models.TXPacket{
TXInfo: models.TXInfo{
MAC: rxPacket.RXInfo.MAC,
Timestamp: rxPacket.RXInfo.Timestamp + uint32(rxDelay/time.Microsecond),
Frequency: Band.DownlinkChannels[rx1Channel].Frequency,
Power: Band.DefaultTXPower,
DataRate: Band.DataRates[rx1DR],
CodeRate: rxPacket.RXInfo.CodeRate,
},
PHYPayload: phy,
}
// window 1
if err := ctx.Gateway.Send(txPacket); err != nil {
return fmt.Errorf("send tx packet (rx window 1) to gateway error: %s", err)
}
// increment the FCntDown when MType != ConfirmedDataDown. In case of
// ConfirmedDataDown we increment on ACK.
if phy.MHDR.MType != lorawan.ConfirmedDataDown {
ns.FCntDown++
if err := saveNodeSession(ctx.RedisPool, ns); err != nil {
return err
}
}
return nil
}
示例5: TestHandleJoinRequestPackets
func TestHandleJoinRequestPackets(t *testing.T) {
conf := common.GetTestConfig()
Convey("Given a dummy gateway and application backend and a clean Postgres and Redis database", t, func() {
a := &testApplicationBackend{
rxPayloadChan: make(chan models.RXPayload, 1),
notificationPayloadChan: make(chan interface{}, 10),
}
g := &testGatewayBackend{
rxPacketChan: make(chan models.RXPacket, 1),
txPacketChan: make(chan models.TXPacket, 1),
}
p := storage.NewRedisPool(conf.RedisURL)
common.MustFlushRedis(p)
db, err := storage.OpenDatabase(conf.PostgresDSN)
So(err, ShouldBeNil)
common.MustResetDB(db)
ctx := Context{
RedisPool: p,
Gateway: g,
Application: a,
DB: db,
}
Convey("Given a node and application in the database", func() {
app := models.Application{
AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
Name: "test app",
}
So(storage.CreateApplication(ctx.DB, app), ShouldBeNil)
node := models.Node{
DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1},
AppEUI: app.AppEUI,
AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
RXDelay: 3,
RX1DROffset: 2,
}
So(storage.CreateNode(ctx.DB, node), ShouldBeNil)
Convey("Given a JoinRequest with correct DevEUI but incorrect AppEUI", func() {
phy := lorawan.PHYPayload{
MHDR: lorawan.MHDR{
MType: lorawan.JoinRequest,
Major: lorawan.LoRaWANR1,
},
MACPayload: &lorawan.JoinRequestPayload{
AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 9},
DevEUI: node.DevEUI,
DevNonce: [2]byte{1, 2},
},
}
So(phy.SetMIC(node.AppKey), ShouldBeNil)
rxPacket := models.RXPacket{
PHYPayload: phy,
RXInfo: models.RXInfo{
Frequency: common.Band.UplinkChannels[0].Frequency,
DataRate: common.Band.DataRates[common.Band.UplinkChannels[0].DataRates[0]],
},
}
Convey("then handleRXPacket returns an error", func() {
So(handleRXPacket(ctx, rxPacket), ShouldResemble, errors.New("node 0807060504030201 belongs to application 0102030405060708, 0102030405060709 was given"))
})
})
Convey("Given a JoinRequest packet", func() {
phy := lorawan.PHYPayload{
MHDR: lorawan.MHDR{
MType: lorawan.JoinRequest,
Major: lorawan.LoRaWANR1,
},
MACPayload: &lorawan.JoinRequestPayload{
AppEUI: app.AppEUI,
DevEUI: node.DevEUI,
DevNonce: [2]byte{1, 2},
},
}
So(phy.SetMIC(node.AppKey), ShouldBeNil)
rxPacket := models.RXPacket{
PHYPayload: phy,
RXInfo: models.RXInfo{
Frequency: common.Band.UplinkChannels[0].Frequency,
DataRate: common.Band.DataRates[common.Band.UplinkChannels[0].DataRates[0]],
},
}
Convey("When calling handleRXPacket", func() {
So(handleRXPacket(ctx, rxPacket), ShouldBeNil)
Convey("Then a JoinAccept was sent to the node", func() {
txPacket := <-g.txPacketChan
phy := txPacket.PHYPayload
So(phy.DecryptJoinAcceptPayload(node.AppKey), ShouldBeNil)
So(phy.MHDR.MType, ShouldEqual, lorawan.JoinAccept)
//.........这里部分代码省略.........
示例6: TestHandleUplink
func TestHandleUplink(t *testing.T) {
a := New(t)
b := getTestBroker(t)
gtwID := "eui-0102030405060708"
// Invalid Payload
err := b.HandleUplink(&pb.UplinkMessage{
Payload: []byte{0x01, 0x02, 0x03},
GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID},
ProtocolMetadata: &protocol.RxMetadata{},
})
a.So(err, ShouldNotBeNil)
// Valid Payload
phy := lorawan.PHYPayload{
MHDR: lorawan.MHDR{
MType: lorawan.UnconfirmedDataUp,
Major: lorawan.LoRaWANR1,
},
MACPayload: &lorawan.MACPayload{
FHDR: lorawan.FHDR{
DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}),
FCnt: 1,
},
},
}
bytes, _ := phy.MarshalBinary()
// Device not found
b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond)
b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(&pb_networkserver.DevicesResponse{
Results: []*pb_lorawan.Device{},
}, nil)
err = b.HandleUplink(&pb.UplinkMessage{
Payload: bytes,
GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID},
ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}},
})
a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{})
devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}
wrongDevEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 9}
appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}
appID := "appid-1"
nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}
// Add devices
b = getTestBroker(t)
nsResponse := &pb_networkserver.DevicesResponse{
Results: []*pb_lorawan.Device{
&pb_lorawan.Device{
DevEui: &wrongDevEUI,
AppEui: &appEUI,
AppId: appID,
NwkSKey: &nwkSKey,
FCntUp: 4,
},
&pb_lorawan.Device{
DevEui: &devEUI,
AppEui: &appEUI,
AppId: appID,
NwkSKey: &nwkSKey,
FCntUp: 3,
},
},
}
b.handlers["handlerID"] = &handler{uplink: make(chan *pb.DeduplicatedUplinkMessage, 10)}
// Device doesn't match
b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond)
b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil)
err = b.HandleUplink(&pb.UplinkMessage{
Payload: bytes,
GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID},
ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}},
})
a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{})
phy.SetMIC(lorawan.AES128Key{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8})
bytes, _ = phy.MarshalBinary()
// Wrong FCnt
b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond)
b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil)
err = b.HandleUplink(&pb.UplinkMessage{
Payload: bytes,
GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID},
ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}},
})
a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{})
// Disable FCnt Check
b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond)
nsResponse.Results[0].DisableFCntCheck = true
b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil)
b.ns.EXPECT().Uplink(gomock.Any(), gomock.Any())
b.discovery.EXPECT().GetAllHandlersForAppID("appid-1").Return([]*pb_discovery.Announcement{
&pb_discovery.Announcement{
//.........这里部分代码省略.........
示例7: ConvertToLoRaWAN
func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *types.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error {
// Find Device
dev, err := h.devices.Get(appDown.AppID, appDown.DevID)
if err != nil {
return err
}
// LoRaWAN: Unmarshal Downlink
var phyPayload lorawan.PHYPayload
err = phyPayload.UnmarshalBinary(ttnDown.Payload)
if err != nil {
return err
}
macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload)
if !ok {
return errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload")
}
if ttnDown.DownlinkOption != nil && ttnDown.DownlinkOption.ProtocolConfig.GetLorawan() != nil {
macPayload.FHDR.FCnt = ttnDown.DownlinkOption.ProtocolConfig.GetLorawan().FCnt
}
// Abort when downlink not needed
if len(appDown.PayloadRaw) == 0 && !macPayload.FHDR.FCtrl.ACK && len(macPayload.FHDR.FOpts) == 0 {
return ErrNotNeeded
}
// Set FPort
if appDown.FPort != 0 {
macPayload.FPort = &appDown.FPort
}
// Set Payload
if len(appDown.PayloadRaw) > 0 {
macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: appDown.PayloadRaw}}
if macPayload.FPort == nil || *macPayload.FPort == 0 {
macPayload.FPort = pointer.Uint8(1)
}
} else {
macPayload.FRMPayload = []lorawan.Payload{}
}
// Encrypt
err = phyPayload.EncryptFRMPayload(lorawan.AES128Key(dev.AppSKey))
if err != nil {
return err
}
// Set MIC
err = phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey))
if err != nil {
return err
}
// Marshal
phyPayloadBytes, err := phyPayload.MarshalBinary()
if err != nil {
return err
}
ttnDown.Payload = phyPayloadBytes
return nil
}
示例8: handleCollectedJoinRequestPackets
//.........这里部分代码省略.........
// get the (optional) CFList
cFList, err := getCFListForNode(ctx.DB, node)
if err != nil {
return fmt.Errorf("get CFList for node error: %s", err)
}
// get keys
nwkSKey, err := getNwkSKey(node.AppKey, ctx.NetID, appNonce, jrPL.DevNonce)
if err != nil {
return fmt.Errorf("get NwkSKey error: %s", err)
}
appSKey, err := getAppSKey(node.AppKey, ctx.NetID, appNonce, jrPL.DevNonce)
if err != nil {
return fmt.Errorf("get AppSKey error: %s", err)
}
ns := models.NodeSession{
DevAddr: devAddr,
DevEUI: jrPL.DevEUI,
AppSKey: appSKey,
NwkSKey: nwkSKey,
FCntUp: 0,
FCntDown: 0,
AppEUI: node.AppEUI,
RXDelay: node.RXDelay,
RX1DROffset: node.RX1DROffset,
}
if err = saveNodeSession(ctx.RedisPool, ns); err != nil {
return fmt.Errorf("save node-session error: %s", err)
}
// update the node (with updated used dev-nonces)
if err = updateNode(ctx.DB, node); err != nil {
return fmt.Errorf("update node error: %s", err)
}
// construct the lorawan packet
phy := lorawan.PHYPayload{
MHDR: lorawan.MHDR{
MType: lorawan.JoinAccept,
Major: lorawan.LoRaWANR1,
},
MACPayload: &lorawan.JoinAcceptPayload{
AppNonce: appNonce,
NetID: ctx.NetID,
DevAddr: ns.DevAddr,
RXDelay: ns.RXDelay,
DLSettings: lorawan.DLSettings{
RX2DataRate: uint8(Band.RX2DataRate),
RX1DROffset: ns.RX1DROffset,
},
CFList: cFList,
},
}
if err = phy.SetMIC(node.AppKey); err != nil {
return fmt.Errorf("set MIC error: %s", err)
}
if err = phy.EncryptJoinAcceptPayload(node.AppKey); err != nil {
return fmt.Errorf("encrypt join-accept error: %s", err)
}
// get TX DR
uplinkDR, err := Band.GetDataRate(rxPacket.RXInfo.DataRate)
if err != nil {
return err
}
// get TX channel
uplinkChannel, err := Band.GetChannel(rxPacket.RXInfo.Frequency, uplinkDR)
if err != nil {
return err
}
// get RX1 channel
rx1Channel := Band.GetRX1Channel(uplinkChannel)
// get RX1 DR
rx1DR := Band.RX1DataRate[uplinkDR][0]
txPacket := models.TXPacket{
TXInfo: models.TXInfo{
MAC: rxPacket.RXInfo.MAC,
Timestamp: rxPacket.RXInfo.Timestamp + uint32(Band.JoinAcceptDelay1/time.Microsecond),
Frequency: Band.DownlinkChannels[rx1Channel].Frequency,
Power: Band.DefaultTXPower,
DataRate: Band.DataRates[rx1DR],
CodeRate: rxPacket.RXInfo.CodeRate,
},
PHYPayload: phy,
}
// window 1
if err = ctx.Gateway.SendTXPacket(txPacket); err != nil {
return fmt.Errorf("send tx packet (rx window 1) to gateway error: %s", err)
}
// send a notification to the application that a node joined the network
return ctx.Application.SendNotification(ns.AppEUI, ns.DevEUI, models.JoinNotificationType, models.JoinNotification{
DevAddr: ns.DevAddr,
DevEUI: ns.DevEUI,
})
}