loraserver 源码解析 (四) lora-gateway-bridge

7 篇文章 6 订阅

lora-gateway-bridge  负责接收 gateway 通过 udp 发送的 packet-forwarder 数据

然后通过 MQTT broker 将报文转发给 LoRa Server

 

安装

go get -u github.com/brocaar/lora-gateway-bridge

构建后提示缺乏依赖库 

~/go/gopath/src/github.com/brocaar/lora-gateway-bridge $ make build                                                                                   
Compiling source                                                                                                                                      
go build  -ldflags "-s -w -X main.version=2.6.2-5-g8b51594" -o build/lora-gateway-bridge cmd/lora-gateway-bridge/main.go                              
internal/backend/mqtt/auth/gcp_cloud_iot_core.go:9:2: cannot find package "github.com/dgrijalva/jwt-go" in any of:                                    
        /home/zlgmcu/go/go/src/github.com/dgrijalva/jwt-go (from $GOROOT)                                                                             
        /home/zlgmcu/go/gopath/src/github.com/dgrijalva/jwt-go (from $GOPATH)                                                                         
internal/metrics/prometheus.go:6:2: cannot find package "github.com/prometheus/client_golang/prometheus" in any of:                                   
        /home/zlgmcu/go/go/src/github.com/prometheus/client_golang/prometheus (from $GOROOT)                                                          
        /home/zlgmcu/go/gopath/src/github.com/prometheus/client_golang/prometheus (from $GOPATH)                                                      
cmd/lora-gateway-bridge/cmd/root_run.go:12:2: cannot find package ">github.com/prometheus/client_golang/prometheus/promhttp" in any of:               
        /home/zlgmcu/go/go/src/github.com/prometheus/client_golang/prometheus/promhttp (from $GOROOT)                                                 
        /home/zlgmcu/go/gopath/src/github.com/prometheus/client_golang/prometheus/promhttp (from $GOPATH)                                             
Makefile:6: recipe for target 'build' failed                                                                                                          
make: *** [build] Error 1                     

 我的依赖库安装

~/go/gopath/src/github.com/brocaar/lora-gateway-bridge $ go get -u -v github.com/dgrijalva/jwt-go                                                     
github.com/dgrijalva/jwt-go (download)                                                                                                                
github.com/dgrijalva/jwt-go                                                                                                                           
~/go/gopath/src/github.com/brocaar/lora-gateway-bridge $ go get -u -v github.com/prometheus/client_golang/prometheus                                  
github.com/prometheus/client_golang (download)                                                                                                        
github.com/beorn7/perks (download)                                                                                                                    
github.com/golang/protobuf (download)                                                                                                                 
github.com/prometheus/client_model (download)                                                                                                         
github.com/prometheus/common (download)                                                                                                               
github.com/matttproud/golang_protobuf_extensions (download)                                                                                           
github.com/prometheus/procfs (download)                                                                                                               
github.com/beorn7/perks/quantile                                                                                                                      
github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg                                                                                      
github.com/prometheus/procfs/internal/util                                                                                                            
github.com/prometheus/common/model                                                                                                                    
github.com/prometheus/client_model/go                                                                                                                 
github.com/matttproud/golang_protobuf_extensions/pbutil                                                                                               
github.com/prometheus/procfs/nfs                                                                                                                      
github.com/prometheus/procfs/xfs                                                                                                                      
github.com/prometheus/client_golang/prometheus/internal                                                                                               
github.com/prometheus/procfs                                                                                                                          
github.com/prometheus/common/expfmt                                                                                                                   
github.com/prometheus/client_golang/prometheus                                                                                                        
~/go/gopath/src/github.com/brocaar/lora-gateway-bridge $ go get -u -v github.com/prometheus/client_golang/prometheus/promhttp                         
github.com/prometheus/client_golang (download)                                                                                                        
github.com/beorn7/perks (download)                                                                                                                    
github.com/golang/protobuf (download)                                                                                                                 
github.com/prometheus/client_model (download)                                                                                                         
github.com/prometheus/common (download)                                                                                                               
github.com/matttproud/golang_protobuf_extensions (download)                                                                                           
github.com/prometheus/procfs (download)                                                                                                               
github.com/prometheus/client_golang/prometheus/promhttp 

 

构建

~/go/gopath/src/github.com/brocaar/lora-gateway-bridge $ make build
Compiling source

 

若构建失败,请参考loraserver 源码解析 (一) 构建,安装必要的依赖库(之前编译loraserver时应该已经都装好了的)

 

 

生成配置文件

~/go/gopath/src/github.com/brocaar/lora-gateway-bridge/build $ lora-gateway-bridge configfile > lora-gateway-bridge.toml

打开 lora-gateway-bridge.toml

删除第一行

 

运行

~/go/gopath/src/github.com/brocaar/lora-gateway-bridge/build $ lora-gateway-bridge 
INFO[0000] starting LoRa Gateway Bridge                  docs="https://www.loraserver.io/lora-gateway-bridge/" version=2.4.1-3-g01d8569
INFO[0000] backend: set max reconnect interval: 10m0s   
INFO[0000] backend: TLS config is empty                 
INFO[0000] backend: connecting to mqtt broker            server="tcp://127.0.0.1:1883"
INFO[0000] gateway: starting gateway udp listener        addr="0.0.0.0:1700"
INFO[0000] backend: connected to mqtt broker  

 

gateway 和 lora-gateway-bridge 的通信协议

LoRaWan标准中只有大概的说明,没有具体的标准

semtech 公司提供了 基于 UDP 的 packet_forwarder 协议,鉴于LoRa芯片都是 semtech 提供的,它很可能成为事实标准。

semtech 在github上开源了 packet_forwarder 工程

packet_forwarder

 

源码分析

bridge 接收 来自 semtech 的 packet_forwarder   udp 数据报, 收到后 通过 mqtt broker 发布给 loraserver

loraserver 有下发命令时,通过mqtt broker 发送给 bridge

 

从main函数开始

bridge 基于 cobra 完成命令行的处理

cobra 装好后

cd $GOPATH/src
cobra init demo 后

demo        
 | 
 |---- main.go
 |-- cmd
      |---- root.go

要加命令就 cobra add cmd_name

bridge 源码的 cmd 目录大致就是这么构建出来的。

 

然后在自动生成的 root.go 中解析配置文件,初始化程序

作者搞了个 root_run.go 单独存放 run 函数

run 函数 是整个 bridge 的核心

 

lora-gateway-bridge.toml 的 packet_forwarder.configuration 的 mac="0102030405060708" 中 可以配置常驻gateway的 mac

这些gateway mac 将被加载到  BackendConfig 的 AlwaysSubscribeMACs 中

这些mac 讲常驻,不论这些 packet_forwarder 是否断线, 这些mac相关的  mqtt 主题都会一直 订阅不会删除

 

run 初始化2个后端模块

  • 1. mqttpubsub 的 Backend  , 负责mqtt broker 收发数据

初始化并生成mqttpubsub 的 Backend变量 pubsub

对mqtt不太熟悉的可参考 本文末尾的 mqtt 简短示例代码。

// Backend implements a MQTT pub-sub backend.
type Backend struct {
	conn             mqtt.Client
	txPacketChan     chan gw.TXPacketBytes
	configPacketChan chan gw.GatewayConfigPacket
	gateways         map[lorawan.EUI64]bool // the bool indicates if the gateway must always be subscribed
	mutex            sync.RWMutex
	config           BackendConfig

	UplinkTemplate   *template.Template
	DownlinkTemplate *template.Template
	StatsTemplate    *template.Template
	AckTemplate      *template.Template
	ConfigTemplate   *template.Template
}

lora-gateway-bridge.toml 的   [backend.mqtt] 保存的

uplink_topic_template="gateway/{{ .MAC }}/rx"
downlink_topic_template="gateway/{{ .MAC }}/tx"
stats_topic_template="gateway/{{ .MAC }}/stats"
ack_topic_template="gateway/{{ .MAC }}/ack"
config_topic_template="gateway/{{ .MAC }}/config"

将读取上来, 做成  Backend 的UplinkTemplate 等变量 template

go template 是文本模板,相关用法不了解的话就问问 baidu

Backend 结构体

gateways  记录了当前哪些gateway相关的mqtt 主题 在订阅发布中 

conn   保存了 mqtt 客户端的连接, mqtt相关的操作都是通过它来处理的,

           bridge 是 mqtt主题的发布者,主动发布mqtt主题消息

           loraserver 是 订阅者,接收 bridge 推送上来的 packet_forwarder udp 报文

           bridge 也是 mqtt主题的订阅者,订阅loraserver发布的一些主题,这些主题会把命令推送给 bridge

backend 有4个 chan  

 

  • 2.  gateway 的 Backend

负责管理 gateway, conn负责具体的udp通信

初始化并生成 gateway.Backend 变量 gw

// Backend implements a Semtech packet-forwarder gateway backend.
type Backend struct {
	conn           *net.UDPConn
	txAckChan      chan gw.TXAck              // received TX ACKs
	rxChan         chan gw.RXPacketBytes      // received uplink frames
	statsChan      chan gw.GatewayStatsPacket // received gateway stats
	udpSendChan    chan udpPacket
	closed         bool
	gateways       gateways
	configurations []Configuration
	wg             sync.WaitGroup
	skipCRCCheck   bool
}

gateway模块一共开启3个 骨干goroutine

G1. cleanup    间隔1分钟 死循环监控是否有gateway下线了, 下线了则调用 OnDelete , 取消 mqtt 该gateway 相关的主题订阅。

G2. read packets  侦听来自 lorawan 网关的 数据报(  [packet_forwarder] 的 udp_bind = "0.0.0.0:1700" 配置了 udp 侦听端口号 )

   本 goroutine 不堵塞,一旦报文到达,简单拷贝数据后 再开启一共新的 goroutine 来处理报文,如果报文需要应答,则填充完应答报文后,推送给   Backend.udpSendChan 管道。

    为方便后续文章编写,G2 新建的goroutine 称为 G2G 

   看到好多个 goroutine 一起写 udpSendChan,看 bridge 源码是没有加锁的,再看了看 《The Go Programming Language》,也没加锁,恩,那应该就是不用加锁了。

G3. send packets , 此goroutine一直读取 udpSendChan 管道,管道里没数据则阻塞,一旦有数据就通过 conn 经udp发送给 网关。

run 开启5个goroutine

初始化完 mqttpubsub 和 gateway 2个模块后,run开启了5个goroutine

G4. 不断读取 gw 的rxChan, 有数据则 经由 pubsub  发布 mqtt 主题,这个主题由前面提到的UplinkTemplate 文本模板生成。

       UplinkTemplate 的默认配置值     uplink_topic_template="gateway/{{ .MAC }}/rx"   当semtech gateway的mac填充后

       如果mac=1234567812345678   那么主题的实际值就是  gateway/1234567812345678/rx

INFO[61640] backend: publishing packet                    qos=0 topic=gateway/1234567812345678/rx

       G2 收到 udp 传上来的报文后新开goroutine解析,如果是 PKT_PUSH_DATA,则把报文打包后 推送给 gw 的 rxChan

       semtech gateway  ------>    G2  -->  G2G -->  gw.rxChan -->  G4  --> mqtt broker --> loraserver

semtech网关 上传报文 类型
#define PKT_PUSH_DATA   0
#define PKT_PUSH_ACK    1
#define PKT_PULL_DATA   2
#define PKT_PULL_RESP   3
#define PKT_PULL_ACK    4
#define PKT_TX_ACK      5

G5.  不断读取 gw的 statusChan,有数据则 经由 pubsub  发布 mqtt 主题

        主题 由 StatsTemplate 构成,默认值为 "gateway/{{ .MAC }}/stats" 

        G2 收到 udp 传上来的报文后,新开goroutine解析, 如果是 PKT_PUSH_DATA,会查看是否包含 json中是否包含"stat"字段,如果包含则处理状态,处理完状态后 推送给 gw的statusChan

         semtech gateway  ------>    G2  -->  G2G 如果包含 stat 字段 -->  gw.statusChan-->  G4  --> mqtt broker --> loraserver

         注意 这个 stat 字段是和  rxpk 平级的。 不是rxpk里的,rxpk里也有stat 那个是 CRC校验的,请注意区分。 本文末尾的附录包含详细的协议,可仔细研究。

The root object can also contain an object named "stat" :

``` json
{
	"rxpk":[ {...}, ...],
	"stat":{...}
}
```

G6.  读取  pubsub 的 txPacketChan         

         PKT_PULL_DATA 报文,注意这里是PULL_DATA,不是PUSH_DATA。

         阅读 semtech 的 packet_forwarder源码, PULL_DATA 类型的报文是 thread_down 线程负责发送的。thread_down线程每隔一段时间就会发一个PULL_DATA报文(心跳包),bridge 收到后 回复一个 PKT_PULL_ACK 包。 在 packet_forwarder看来,和 netserver 通信是有两个udp通道的。一个用于 thread_up 上传设备的数据包, 一个用于 thread_down 接收来自 netserver的下发命令。

        bridge (在LoRaWan 看来 bridge 是netserver一部分。 bridge 和 loraserver 同属于 netserver。 lora-app-server 则属于 appserver)并没有去侦听2个udp端口,而仅仅侦听了一个。于是 packet_forwarder  thread_up  thread_down 2个线程收发的数据都由G2统一接收。

         thread_up 上传的是  PUSH_DATA 

         thread_down 上传的心跳包是 PULL_DATA 类型

PULL_DATA 报文

 Bytes  | Function
:------:|---------------------------------------------------------------------
 0      | protocol version = 2
 1-2    | random token
 3      | PULL_DATA identifier 0x02
 4-11   | Gateway unique identifier (MAC address)

 

         run 函数有2个回调函数, 创建gateway  Backend 对象 gw 的时候传入,

	onNew := func(mac lorawan.EUI64) error {
		return pubsub.SubscribeGatewayTopics(mac)
	}

	onDelete := func(mac lorawan.EUI64) error {
		return pubsub.UnSubscribeGatewayTopics(mac)
	}

    gw, err := gateway.NewBackend(config.C.PacketForwarder.UDPBind, onNew, onDelete,     config.C.PacketForwarder.SkipCRCCheck, config.C.PacketForwarder.Configuration)

          G2G 收到PULL_DATA报文后,解析出 Gateway的 MAC 字段,看看这个MAC是否已经存在于 gw.gateways map中了,如果不存在,那说明这是一个新的gateway, 那就加到 gw.gateways 中。

       然后调用 回调函数 OnNew(mac lorawan.EUI64) ,在 OnNew 中 通过pubsub 订阅了 这个 mac 相关的主题。就是DownlinkTemplate (默认为 "gateway/{{ .MAC }}/tx" )的主题 。这个主题是loraserver发布,但凡有命令需要下发给终端,就通过这个主题发布。

        bridge 这个主题 的接收回调是 txPacketHandler 函数。具体如下:

func (b *Backend) txPacketHandler(c mqtt.Client, msg mqtt.Message) {
	log.WithField("topic", msg.Topic()).Info("backend: downlink packet received")
	var txPacket gw.TXPacketBytes
	if err := json.Unmarshal(msg.Payload(), &txPacket); err != nil {
		log.WithError(err).Error("backend: decode tx packet error")
		return
	}
	b.txPacketChan <- txPacket
}

         不难看出,收到来自 mqtt broker 发送过来的 message 后,解析成 txPacket 包,直接丢给 txPacketChan 管道。

         G6 不断读取 txPacketChan 管道 报文。 读到后通过 gw.Send(txPacket); 产生合适的回复报文后 转发给  gw.udpSendChan

         G3 不断读取 gw.udpSendChan,读到后,发送给 packet_forwarder 

         整理下,大概的流程是这样的:

packet_forwarder 的 thread_down 不断上传PULL_DATA 心跳包
bridge G2 收到后,转交给 G2G 处理, G2G发现 这个gateway是第一次上传心跳包
把这个 gateway 登记到 自己的 gateways map中,并订阅这个gateway的相关主题

loraserver 有命令要下发给 packet_forwarder  时,发布 mqtt 主题
由于bridge 之前订阅了这个主题,且登记了回调函数 txPacketHandler
命令传递给了 txPacketHandler 函数, txPacketHandler 将命令传递给 txPacketChan 管道

G6 不断读取 txPacketChan 管道,收到命令后 经 gw.Send() 函数再度处理后  转交给 udpSendChan
G3 不断读取 udpSendChan 管道, 读到后 发送给 packet_forwarder 

G7.  读取 gw.txAckChan  

       G2G处理来自 packet_forwarder 的报文,如果报文是 PKT_TX_ACK(5) 类型,则通过 txAckChan 传递给G7

       G7 接收后 从 "gateway/{{ .MAC }}/ack" 主题经 mqtt broker 发布给 loraserver

G8.  读取 pubsub 的 configPacketChan 

       参考前面G6的描述,如果packet_forwarder thread_down推送上来的PULL_DATA是第一个,则调用回调函数 OnNew(mac lorawan.EUI64) ,在 OnNew 中 通过pubsub 订阅了 这个 mac 的2个主题。一个是G6提到的DownlinkTemplate,这个用于命令的下发,还有一个就是 ConfigTemplate ("gateway/{{ .MAC }}/config")。  mqtt broker 的 ConfigTemplate主题传送过来的数据,通过configPacketChan 管道 转交给 G8

        G8 更具收到的配置,更新 gateway 的配置

 

附录: mqtt 客户端 代码示例

package main

import (
	log "github.com/sirupsen/logrus"
	"github.com/eclipse/paho.mqtt.golang"
)

func main() {
	log.Println("receive start")

	opts := mqtt.NewClientOptions().AddBroker("tcp://127.0.0.1:1883").SetClientID("test1")
	
	c := mqtt.NewClient(opts)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		panic(token.Error())
	}

	msgRcvd := func(client mqtt.Client, message mqtt.Message) {
		log.Printf("Received message on topic: %s Message: %s\n", message.Topic(), message.Payload())
	}
	
	if token := c.Subscribe("test/topic", 0, msgRcvd); token.Wait() && token.Error() != nil {
		log.Println(token.Error())
	}

	for {
		
	}
}

附录: mqtt 发布者代码示例

package main

import (
	"time"
	"strconv"
	
	log "github.com/sirupsen/logrus"
	"github.com/eclipse/paho.mqtt.golang"
)

func main() {
	log.Println("hello world")

	opts := mqtt.NewClientOptions().AddBroker("tcp://127.0.0.1:1883").SetClientID("test")

	c := mqtt.NewClient(opts)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		panic(token.Error())
	}

	for i:=0; true; i++ {
		log.Println("publish a topic")

		token := c.Publish("test/topic", 1, false, "Example Payload"+strconv.Itoa(i))
		
		if token.Wait() && token.Error() != nil {
			log.Error(token.Error())
		}

		time.Sleep(time.Second)
	}
}


 

附录: 通信协议

	  ______                              _
	 / _____)             _              | |    
	( (____  _____ ____ _| |_ _____  ____| |__  
	 \____ \| ___ |    (_   _) ___ |/ ___)  _ \ 
	 _____) ) ____| | | || |_| ____( (___| | | |
	(______/|_____)_|_|_| \__)_____)\____)_| |_|
	  (C)2013 Semtech-Cycleo

Basic communication protocol between Lora gateway and server
=============================================================


1. Introduction
----------------

The protocol between the gateway and the server is purposefully very basic and 
for demonstration purpose only, or for use on private and reliable networks.

There is no authentication of the gateway or the server, and the acknowledges 
are only used for network quality assessment, not to correct UDP datagrams 
losses (no retries).


2. System schematic and definitions
------------------------------------

	 ((( Y )))
	     |
	     |
	+ - -|- - - - - - - - - - - - - +        xxxxxxxxxxxx          +--------+
	| +--+-----------+     +------+ |       xx x  x     xxx        |        |
	| |              |     |      | |      xx  Internet  xx        |        |
	| | Concentrator |<--->| Host |<-------xx     or    xx-------->|        |
	| |              | SPI |      | |      xx  Intranet  xx        | Server |
	| +--------------+     +------+ |       xxxx   x   xxxx        |        |
	|    ^                     ^    |           xxxxxxxx           |        |
	|    | PPS +-------+ NMEA  |    |                              |        |
	|    +-----|  GPS  |-------+    |                              +--------+
	|          | (opt) |            |
	|          +-------+            |
	|                               |
	|             Gateway           |
	+- - - - - - - - - - - - - - - -+

__Concentrator__: radio RX/TX board, based on Semtech multichannel modems 
(SX130x), transceivers (SX135x) and/or low-power stand-alone modems (SX127x). 

__Host__: embedded computer on which the packet forwarder is run. Drives the 
concentrator through a SPI link.

__GPS__: GNSS (GPS, Galileo, GLONASS, etc) receiver with a "1 Pulse Per Second"
output and a serial link to the host to send NMEA frames containing time and
geographical coordinates data. Optional.

__Gateway__: a device composed of at least one radio concentrator, a host, some 
network connection to the internet or a private network (Ethernet, 3G, Wifi, 
microwave link), and optionally a GPS receiver for synchronization. 

__Server__: an abstract computer that will process the RF packets received and 
forwarded by the gateway, and issue RF packets in response that the gateway 
will have to emit.

It is assumed that the gateway can be behind a NAT or a firewall stopping any 
incoming connection.
It is assumed that the server has an static IP address (or an address solvable 
through a DNS service) and is able to receive incoming connections on a 
specific port.


3. Upstream protocol
---------------------

### 3.1. Sequence diagram ###

	+---------+                                                    +---------+
	| Gateway |                                                    | Server  |
	+---------+                                                    +---------+
	     | -----------------------------------\                         |
	     |-| When 1-N RF packets are received |                         |
	     | ------------------------------------                         |
	     |                                                              |
	     | PUSH_DATA (token X, GW MAC, JSON payload)                    |
	     |------------------------------------------------------------->|
	     |                                                              |
	     |                                           PUSH_ACK (token X) |
	     |<-------------------------------------------------------------|
	     |                              ------------------------------\ |
	     |                              | process packets *after* ack |-|
	     |                              ------------------------------- |
	     |                                                              |

### 3.2. PUSH_DATA packet ###

That packet type is used by the gateway mainly to forward the RF packets 
received, and associated metadata, to the server.

 Bytes  | Function
:------:|---------------------------------------------------------------------
 0      | protocol version = 2
 1-2    | random token
 3      | PUSH_DATA identifier 0x00
 4-11   | Gateway unique identifier (MAC address)
 12-end | JSON object, starting with {, ending with }, see section 4

### 3.3. PUSH_ACK packet ###

That packet type is used by the server to acknowledge immediately all the 
PUSH_DATA packets received.

 Bytes  | Function
:------:|---------------------------------------------------------------------
 0      | protocol version = 2
 1-2    | same token as the PUSH_DATA packet to acknowledge
 3      | PUSH_ACK identifier 0x01


4. Upstream JSON data structure
--------------------------------

The root object can contain an array named "rxpk":

``` json
{
	"rxpk":[ {...}, ...]
}
```

That array contains at least one JSON object, each object contain a RF packet 
and associated metadata with the following fields:

 Name |  Type  | Function
:----:|:------:|--------------------------------------------------------------
 time | string | UTC time of pkt RX, us precision, ISO 8601 'compact' format
 tmms | number | GPS time of pkt RX, number of milliseconds since 06.Jan.1980
 tmst | number | Internal timestamp of "RX finished" event (32b unsigned)
 freq | number | RX central frequency in MHz (unsigned float, Hz precision)
 chan | number | Concentrator "IF" channel used for RX (unsigned integer)
 rfch | number | Concentrator "RF chain" used for RX (unsigned integer)
 stat | number | CRC status: 1 = OK, -1 = fail, 0 = no CRC
 modu | string | Modulation identifier "LORA" or "FSK"
 datr | string | LoRa datarate identifier (eg. SF12BW500)
 datr | number | FSK datarate (unsigned, in bits per second)
 codr | string | LoRa ECC coding rate identifier
 rssi | number | RSSI in dBm (signed integer, 1 dB precision)
 lsnr | number | Lora SNR ratio in dB (signed float, 0.1 dB precision)
 size | number | RF packet payload size in bytes (unsigned integer)
 data | string | Base64 encoded RF packet payload, padded

Example (white-spaces, indentation and newlines added for readability):

``` json
{"rxpk":[
	{
		"time":"2013-03-31T16:21:17.528002Z",
		"tmst":3512348611,
		"chan":2,
		"rfch":0,
		"freq":866.349812,
		"stat":1,
		"modu":"LORA",
		"datr":"SF7BW125",
		"codr":"4/6",
		"rssi":-35,
		"lsnr":5.1,
		"size":32,
		"data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"
	},{
		"time":"2013-03-31T16:21:17.530974Z",
		"tmst":3512348514,
		"chan":9,
		"rfch":1,
		"freq":869.1,
		"stat":1,
		"modu":"FSK",
		"datr":50000,
		"rssi":-75,
		"size":16,
		"data":"VEVTVF9QQUNLRVRfMTIzNA=="
	},{
		"time":"2013-03-31T16:21:17.532038Z",
		"tmst":3316387610,
		"chan":0,
		"rfch":0,
		"freq":863.00981,
		"stat":1,
		"modu":"LORA",
		"datr":"SF10BW125",
		"codr":"4/7",
		"rssi":-38,
		"lsnr":5.5,
		"size":32,
		"data":"ysgRl452xNLep9S1NTIg2lomKDxUgn3DJ7DE+b00Ass"
	}
]}
```

The root object can also contain an object named "stat" :

``` json
{
	"rxpk":[ {...}, ...],
	"stat":{...}
}
```

It is possible for a packet to contain no "rxpk" array but a "stat" object.

``` json
{
	"stat":{...}
}
```

That object contains the status of the gateway, with the following fields:

 Name |  Type  | Function
:----:|:------:|--------------------------------------------------------------
 time | string | UTC 'system' time of the gateway, ISO 8601 'expanded' format
 lati | number | GPS latitude of the gateway in degree (float, N is +)
 long | number | GPS latitude of the gateway in degree (float, E is +)
 alti | number | GPS altitude of the gateway in meter RX (integer)
 rxnb | number | Number of radio packets received (unsigned integer)
 rxok | number | Number of radio packets received with a valid PHY CRC
 rxfw | number | Number of radio packets forwarded (unsigned integer)
 ackr | number | Percentage of upstream datagrams that were acknowledged
 dwnb | number | Number of downlink datagrams received (unsigned integer)
 txnb | number | Number of packets emitted (unsigned integer)

Example (white-spaces, indentation and newlines added for readability):

``` json
{"stat":{
	"time":"2014-01-12 08:59:28 GMT",
	"lati":46.24000,
	"long":3.25230,
	"alti":145,
	"rxnb":2,
	"rxok":2,
	"rxfw":2,
	"ackr":100.0,
	"dwnb":2,
	"txnb":2
}}
```


5. Downstream protocol
-----------------------

### 5.1. Sequence diagram ###

	+---------+                                                    +---------+
	| Gateway |                                                    | Server  |
	+---------+                                                    +---------+
	     | -----------------------------------\                         |
	     |-| Every N seconds (keepalive time) |                         |
	     | ------------------------------------                         |
	     |                                                              |
	     | PULL_DATA (token Y, MAC@)                                    |
	     |------------------------------------------------------------->|
	     |                                                              |
	     |                                           PULL_ACK (token Y) |
	     |<-------------------------------------------------------------|
	     |                                                              |

	+---------+                                                    +---------+
	| Gateway |                                                    | Server  |
	+---------+                                                    +---------+
	     |      ------------------------------------------------------\ |
	     |      | Anytime after first PULL_DATA for each packet to TX |-|
	     |      ------------------------------------------------------- |
	     |                                                              |
	     |                            PULL_RESP (token Z, JSON payload) |
	     |<-------------------------------------------------------------|
	     |                                                              |
	     | TX_ACK (token Z, JSON payload)                               |
	     |------------------------------------------------------------->|

### 5.2. PULL_DATA packet ###

That packet type is used by the gateway to poll data from the server.

This data exchange is initialized by the gateway because it might be 
impossible for the server to send packets to the gateway if the gateway is 
behind a NAT.

When the gateway initialize the exchange, the network route towards the 
server will open and will allow for packets to flow both directions.
The gateway must periodically send PULL_DATA packets to be sure the network 
route stays open for the server to be used at any time.

 Bytes  | Function
:------:|---------------------------------------------------------------------
 0      | protocol version = 2
 1-2    | random token
 3      | PULL_DATA identifier 0x02
 4-11   | Gateway unique identifier (MAC address)

### 5.3. PULL_ACK packet ###

That packet type is used by the server to confirm that the network route is 
open and that the server can send PULL_RESP packets at any time.

 Bytes  | Function
:------:|---------------------------------------------------------------------
 0      | protocol version = 2
 1-2    | same token as the PULL_DATA packet to acknowledge
 3      | PULL_ACK identifier 0x04

### 5.4. PULL_RESP packet ###

That packet type is used by the server to send RF packets and associated 
metadata that will have to be emitted by the gateway.

 Bytes  | Function
:------:|---------------------------------------------------------------------
 0      | protocol version = 2
 1-2    | random token
 3      | PULL_RESP identifier 0x03
 4-end  | JSON object, starting with {, ending with }, see section 6

### 5.5. TX_ACK packet ###

That packet type is used by the gateway to send a feedback to the server
to inform if a downlink request has been accepted or rejected by the gateway.
The datagram may optionnaly contain a JSON string to give more details on
acknoledge. If no JSON is present (empty string), this means than no error
occured.

 Bytes  | Function
:------:|---------------------------------------------------------------------
 0      | protocol version = 2
 1-2    | same token as the PULL_RESP packet to acknowledge
 3      | TX_ACK identifier 0x05
 4-11   | Gateway unique identifier (MAC address)
 12-end | [optional] JSON object, starting with {, ending with }, see section 6

6. Downstream JSON data structure
----------------------------------

The root object of PULL_RESP packet must contain an object named "txpk":

``` json
{
	"txpk": {...}
}
```

That object contain a RF packet to be emitted and associated metadata with the following fields: 

 Name |  Type  | Function
:----:|:------:|--------------------------------------------------------------
 imme | bool   | Send packet immediately (will ignore tmst & time)
 tmst | number | Send packet on a certain timestamp value (will ignore time)
 tmms | number | Send packet at a certain GPS time (GPS synchronization required)
 freq | number | TX central frequency in MHz (unsigned float, Hz precision)
 rfch | number | Concentrator "RF chain" used for TX (unsigned integer)
 powe | number | TX output power in dBm (unsigned integer, dBm precision)
 modu | string | Modulation identifier "LORA" or "FSK"
 datr | string | LoRa datarate identifier (eg. SF12BW500)
 datr | number | FSK datarate (unsigned, in bits per second)
 codr | string | LoRa ECC coding rate identifier
 fdev | number | FSK frequency deviation (unsigned integer, in Hz) 
 ipol | bool   | Lora modulation polarization inversion
 prea | number | RF preamble size (unsigned integer)
 size | number | RF packet payload size in bytes (unsigned integer)
 data | string | Base64 encoded RF packet payload, padding optional
 ncrc | bool   | If true, disable the CRC of the physical layer (optional)

Most fields are optional.
If a field is omitted, default parameters will be used.

Examples (white-spaces, indentation and newlines added for readability):

``` json
{"txpk":{
	"imme":true,
	"freq":864.123456,
	"rfch":0,
	"powe":14,
	"modu":"LORA",
	"datr":"SF11BW125",
	"codr":"4/6",
	"ipol":false,
	"size":32,
	"data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"
}}
```

``` json
{"txpk":{
	"imme":true,
	"freq":861.3,
	"rfch":0,
	"powe":12,
	"modu":"FSK",
	"datr":50000,
	"fdev":3000,
	"size":32,
	"data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"
}}
```

The root object of TX_ACK packet must contain an object named "txpk_ack":

``` json
{
	"txpk_ack": {...}
}
```

That object contain status information concerning the associated PULL_RESP packet.

 Name |  Type  | Function
:----:|:------:|------------------------------------------------------------------------------
error | string | Indication about success or type of failure that occured for downlink request.

The possible values of "error" field are:

 Value             | Definition
:-----------------:|---------------------------------------------------------------------
 NONE              | Packet has been programmed for downlink
 TOO_LATE          | Rejected because it was already too late to program this packet for downlink
 TOO_EARLY         | Rejected because downlink packet timestamp is too much in advance
 COLLISION_PACKET  | Rejected because there was already a packet programmed in requested timeframe
 COLLISION_BEACON  | Rejected because there was already a beacon planned in requested timeframe
 TX_FREQ           | Rejected because requested frequency is not supported by TX RF chain
 TX_POWER          | Rejected because requested power is not supported by gateway
 GPS_UNLOCKED      | Rejected because GPS is unlocked, so GPS timestamp cannot be used

Examples (white-spaces, indentation and newlines added for readability):

``` json
{"txpk_ack":{
	"error":"COLLISION_PACKET"
}}
```

7. Revisions
-------------

### v1.4 ###
* Added "tmms" field for GPS time as a monotonic number of milliseconds
ellapsed since January 6th, 1980 (GPS Epoch). No leap second.

### v1.3 ###

* Added downlink feedback from gateway to server (PULL_RESP -> TX_ACK)

### v1.2 ###

* Added value of FSK bitrate for upstream.
* Added parameters for FSK bitrate and frequency deviation for downstream.

### v1.1 ###

* Added syntax for status report JSON object on upstream.

### v1.0 ###

* Initial version.

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值