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 工程
源码分析
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.