前言
笔者计划通过无线定位系列文章、系统的描述 TDOA 无线定位和混合定位相关技术知识点,
并以实践来验证此定位系统精度。
笔者从实践出发、本篇直接走读无线定位系统关键节点、网关 SX1302 源码框架,并在源码走读过程
中、着重分析与无线定位相关的PPS时间的来龙去脉、并在后期文章中以实际代码讲解 TDOA 无线定位
实现过程及多网关综合定位内容,敬请期待。
semtech 公司在 2020年06月份推出 LR1110\LR1120 两款GNSS、WIFI和Lora(LR-HFSS)混合
定位芯片、并提供’定位云服务’的接入、国内与腾讯云合作,腾讯云也提供定位云服务接入,这是
笔者对混合无线定位技术背景简单描述、此用意看官自行审度。
第1节 主程序代码走读
主线程基本功能:
<1>. 读取 *.conf.json 文件内容、并解析内容把变量赋值到相关全局变量中;
<2>. 启动各子线程、子线程清单如下所述;
<3>. 固定周期定时检测gps的时间戳、并上报网关的状态信息;
<4>. 等待退出信号量、网络断开信号量和各子线程退出.
子线程清单.
/* threads */
void thread_up(void); //> 上行线程:负责接收lora模块的数据、并把数据通过网络上传至网络服务器;
void thread_down(void); //> 下行线程:负责接收服务器的数据,并把数据通过lora无线下方给终端模块;
void thread_jit(void); //> jit 下行数据处理线程:
void thread_gps(void); //> gps 线程时间同步线程
void thread_valid(void); //> 时钟校正线程
void thread_spectral_scan(void); //> SCAN扫描线程:
主程序源码基本功能就这么多,笔者就不贴出源码对照了,下面进入我们本章主题 thread_down 线程的代码走读。
第2节 thread_down 程序框架描述
此线程是负责接收网络服务器数据内容,并把此内容下方至 Lora 模块。
线程代码基本逻辑如下:
<1>. 配置网络通讯接收超时、进入接收数据循环主体;
<2>. 最外侧循环主体先发送PULL请求、然后在保活时间内容接收数据;并检测是否有退出线程请求;
<3>. 中间循环主体在网络保活周期内、接收到网络服务器下方的数据,解析json数据并发送ACK内容给网络服务器;
<4>. 最内侧循环主体是获取gps时标同步信息、如果同步时间到、在 jit_enqueue() 函数同步时间;
线程大体功能就是这样、在 2.3 部分源码中笔者做了简单注解;
其中 jit_enqueue() 同步时标函数是关注重点。
2.1 通讯协议数据格式
看一下 lgw_pkt_tx_s 结构体内容定义:
/**
@struct lgw_pkt_tx_s
@brief Structure containing the configuration of a packet to send and a pointer to the payload
*/
struct lgw_pkt_tx_s {
uint32_t freq_hz; /*!> center frequency of TX */
uint8_t tx_mode; /*!> select on what event/time the TX is triggered, 发送模式 */
uint32_t count_us; /*!> timestamp or delay in microseconds for TX trigger, 延时发送 */
uint8_t rf_chain; /*!> through which RF chain will the packet be sent */
int8_t rf_power; /*!> TX power, in dBm */
uint8_t modulation; /*!> modulation to use for the packet */
int8_t freq_offset; /*!> frequency offset from Radio Tx frequency (CW mode) */
uint8_t bandwidth; /*!> modulation bandwidth (LoRa only),信道带宽 */
uint32_t datarate; /*!> TX datarate (baudrate for FSK, SF for LoRa),数据速率 */
uint8_t coderate; /*!> error-correcting code of the packet (LoRa only),码速率 */
bool invert_pol; /*!> invert signal polarity, for orthogonal downlinks (LoRa only), 信号极性 */
uint8_t f_dev; /*!> frequency deviation, in kHz (FSK only) */
uint16_t preamble; /*!> set the preamble length, 0 for default,前导码长度 */
bool no_crc; /*!> if true, do not send a CRC in the packet */
bool no_header; /*!> if true, enable implicit header mode (LoRa), fixed length (FSK) */
uint16_t size; /*!> payload size in bytes */
uint8_t payload[256]; /*!> buffer containing the payload */
};
此结构体内容注释已经描述很清晰了、我们关注的是 发送模式和延时发送部分内容,此部分都与定位精度相关。
2.2 thread_down 线程网络通讯建立
/* network socket creation */
struct addrinfo hints;
struct addrinfo *result; /* store result of getaddrinfo */
struct addrinfo *q; /* pointer to move into *result data */
char host_name[64];
char port_name[64];
/* prepare hints to open network sockets */
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; /* WA: Forcing IPv4 as AF_UNSPEC makes connection on localhost to fail */
hints.ai_socktype = SOCK_DGRAM; //> UDP 通讯方式
/* look for server address w/ downstream port */
i = getaddrinfo(serv_addr, serv_port_down, &hints, &result);
if (i != 0) {
MSG("ERROR: [down] getaddrinfo on address %s (port %s) returned %s\n", serv_addr, serv_port_down, gai_strerror(i));
exit(EXIT_FAILURE);
}
/* try to open socket for downstream traffic */
for (q=result; q!=NULL; q=q->ai_next) {
sock_down = socket(q->ai_family, q->ai_socktype,q->ai_protocol);
if (sock_down == -1) continue; /* try next field */
else break; /* success, get out of loop */
}
/* connect so we can send/receive packet with the server only */
i = connect(sock_down, q->ai_addr, q->ai_addrlen);
if (i != 0) {
MSG("ERROR: [down] connect returned %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* set downstream socket RX timeout */
i = setsockopt(sock_down, SOL_SOCKET, SO_RCVTIMEO, (void *)&pull_timeout, sizeof pull_timeout);
if (i != 0) {
MSG("ERROR: [down] setsockopt returned %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
网络服务器的ip地址和端口号在 *.conf.json 文件中,如下:
"gateway_conf": {
"gateway_ID": "AA555A0000000000",
/* change with default server address/ports */
"server_address": "localhost",
"serv_port_up": 1730,
"serv_port_down": 1730,
/* adjust the following parameters for your network */
"keepalive_interval": 10,
"stat_interval": 30,
"push_timeout_ms": 100,
/* forward only valid packets */
"forward_crc_valid": true,
"forward_crc_error": false,
"forward_crc_disabled": false,
/* GPS configuration */
"gps_tty_path": "/dev/ttyS0",
/* GPS reference coordinates */
"ref_latitude": 0.0,
"ref_longitude": 0.0,
"ref_altitude": 0,
/* Beaconing parameters */
"beacon_period": 0,
"beacon_freq_hz": 869525000,
"beacon_datarate": 9,
"beacon_bw_hz": 125000,
"beacon_power": 14,
"beacon_infodesc": 0
},
此文件中 server_address 和 serv_port_down 在主程序中解码json文件时、就把此内容放入到 serv_addr 中,由此知道
上行和下行线程采用 UDP socket 方式与网络服务器进行通讯;
其中 Beaconing parameters 样例是 869 MHz 欧洲频道、中国ISM频段是470MHz。
2.3 thread_down 代码框架
线程代码有删减、只提取功能性内容。
void thread_down(void) {
int i; /* loop variables */
/* configuration and metadata for an outbound packet */
struct lgw_pkt_tx_s txpkt;
bool sent_immediate = false; /* option to sent the packet immediately */
/* local timekeeping variables */
struct timespec send_time; /* time of the pull request */
struct timespec recv_time; /* time of return from recv socket call */
/* data buffers */
uint8_t buff_down[1000]; /* buffer to receive downstream packets */
uint8_t buff_req[12]; /* buffer to compose pull requests */
int msg_len;
/* variables to send on GPS timestamp */
struct tref local_ref; /* time reference used for GPS <-> timestamp conversion */
struct timespec gps_tx; /* GPS time that needs to be converted to timestamp */
/* beacon variables */
struct lgw_pkt_tx_s beacon_pkt;
uint8_t beacon_chan;
uint8_t beacon_loop;
size_t beacon_RFU1_size = 0;
size_t beacon_RFU2_size = 0;
uint8_t beacon_pyld_idx = 0;
time_t diff_beacon_time;
struct timespec next_beacon_gps_time; /* gps time of next beacon packet */
struct timespec last_beacon_gps_time; /* gps time of last enqueued beacon packet */
/* beacon variables initialization,构建发送数据对象 */
last_beacon_gps_time.tv_sec = 0;
last_beacon_gps_time.tv_nsec = 0;
/* beacon packet parameters */
beacon_pkt.tx_mode = ON_GPS; /* send on PPS pulse */
beacon_pkt.rf_chain = 0; /* antenna A */
beacon_pkt.rf_power = beacon_power;
beacon_pkt.modulation = MOD_LORA;
beacon_pkt.bandwidth = BW_125KHZ;
beacon_pkt.datarate = DR_LORA_SF8;
beacon_pkt.size = beacon_RFU1_size + 4 + 2 + 7 + beacon_RFU2_size + 2;
beacon_pkt.coderate = CR_LORA_4_5;
beacon_pkt.invert_pol = false;
beacon_pkt.preamble = 10;
beacon_pkt.no_crc = true;
beacon_pkt.no_header = true;
/* calculate the latitude and longitude that must be publicly reported */
field_latitude = (int32_t)((reference_coord.lat / 90.0) * (double)(1<<23));
field_longitude = (int32_t)((reference_coord.lon / 180.0) * (double)(1<<23));
/* gateway specific beacon fields */
beacon_pkt.payload[beacon_pyld_idx++] = beacon_infodesc;
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_latitude;
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_latitude >> 8);
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_latitude >> 16);
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_longitude;
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_longitude >> 8);
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_longitude >> 16);
while (!exit_sig && !quit_sig) {
/* auto-quit if the threshold is crossed */
if ((autoquit_threshold > 0) && (autoquit_cnt >= autoquit_threshold)) {
exit_sig = true;
MSG("INFO: [down] the last %u PULL_DATA were not ACKed, exiting application\n", autoquit_threshold);
break;
}
/* send PULL request and record time, 向网络服务器发送 PULL请求、获取任务队列内容 */
send(sock_down, (void *)buff_req, sizeof buff_req, 0);
clock_gettime(CLOCK_MONOTONIC, &send_time);
pthread_mutex_lock(&mx_meas_dw);
meas_dw_pull_sent += 1;
pthread_mutex_unlock(&mx_meas_dw);
req_ack = false;
autoquit_cnt++;
/* listen to packets and process them until a new PULL request must be sent */
recv_time = send_time;
while (((int)difftimespec(recv_time, send_time) < keepalive_time) && !exit_sig && !quit_sig) {
/* try to receive a datagram, 如果网络服务器对应的网关任务列表中有任务就下方 */
msg_len = recv(sock_down, (void *)buff_down, (sizeof buff_down)-1, 0);
clock_gettime(CLOCK_MONOTONIC, &recv_time);
while (beacon_loop && (beacon_period != 0)) {
pthread_mutex_lock(&mx_timeref);
/* Wait for GPS to be ready before inserting beacons in JiT queue */
if ((gps_ref_valid == true) && (xtal_correct_ok == true)) {
/* compute GPS time for next beacon to come */
/* LoRaWAN: T = k*beacon_period + TBeaconDelay */
/* with TBeaconDelay = [1.5ms +/- 1µs]*/
if (last_beacon_gps_time.tv_sec == 0) {
/* if no beacon has been queued, get next slot from current GPS time */
diff_beacon_time = time_reference_gps.gps.tv_sec % ((time_t)beacon_period);
next_beacon_gps_time.tv_sec = time_reference_gps.gps.tv_sec +
((time_t)beacon_period - diff_beacon_time);
} else {
/* if there is already a beacon, take it as reference */
next_beacon_gps_time.tv_sec = last_beacon_gps_time.tv_sec + beacon_period;
}
/* now we can add a beacon_period to the reference to get next beacon GPS time */
next_beacon_gps_time.tv_sec += (retry * beacon_period);
next_beacon_gps_time.tv_nsec = 0;
/* convert GPS time to concentrator time, and set packet counter for JiT trigger */
lgw_gps2cnt(time_reference_gps, next_beacon_gps_time, &(beacon_pkt.count_us));
/* load time in beacon payload */
beacon_pyld_idx = beacon_RFU1_size;
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & next_beacon_gps_time.tv_sec;
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 8);
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 16);
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 24);
/* calculate CRC */
field_crc1 = crc16(beacon_pkt.payload, 4 + beacon_RFU1_size); /* CRC for the network common part */
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_crc1;
beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_crc1 >> 8);
//> 获取 beacon_pkt 时间、并同步时间
jit_result = jit_enqueue(&jit_queue[0], current_concentrator_time, &beacon_pkt, JIT_PKT_TYPE_BEACON);
}
//> 退出 beacon_loop 循环
break;
}
//> 接收数据内容解析,数据格式不符合格式要求就放弃数据内容.
/* if the datagram does not respect protocol, just ignore it */
if ((msg_len < 4) || (buff_down[0] != PROTOCOL_VERSION) || ((buff_down[3] != PKT_PULL_RESP) && (buff_down[3] != PKT_PULL_ACK))) {
MSG("WARNING: [down] ignoring invalid packet len=%d, protocol_version=%d, id=%d\n",
msg_len, buff_down[0], buff_down[3]);
continue;
}
//> 如是 ACK数据包、也放弃接收到的数据内容
/* if the datagram is an ACK, check token */
if (buff_down[3] == PKT_PULL_ACK) {
if ((buff_down[1] == token_h) && (buff_down[2] == token_l)) {
if (req_ack) {
MSG("INFO: [down] duplicate ACK received :)\n");
} else { /* if that packet was not already acknowledged */
req_ack = true;
autoquit_cnt = 0;
pthread_mutex_lock(&mx_meas_dw);
meas_dw_ack_rcv += 1;
pthread_mutex_unlock(&mx_meas_dw);
MSG("INFO: [down] PULL_ACK received in %i ms\n", (int)(1000 * difftimespec(recv_time, send_time)));
}
} else { /* out-of-sync token */
MSG("INFO: [down] received out-of-sync ACK\n");
}
continue;
}
//> 接收到数据、并解析网络服务器发送过来的数据内容、处理json串
/* initialize TX struct and try to parse JSON */
memset(&txpkt, 0, sizeof txpkt);
root_val = json_parse_string_with_comments((const char *)(buff_down + 4)); /* JSON offset */
if (root_val == NULL) {
MSG("WARNING: [down] invalid JSON, TX aborted\n");
continue;
}
/* look for JSON sub-object 'txpk' */
txpk_obj = json_object_get_object(json_value_get_object(root_val), "txpk");
if (txpk_obj == NULL) {
MSG("WARNING: [down] no \"txpk\" object in JSON, TX aborted\n");
json_value_free(root_val);
continue;
}
/* Parse "immediate" tag, or target timestamp, or UTC time to be converted by GPS (mandatory) */
i = json_object_get_boolean(txpk_obj,"imme"); /* can be 1 if true, 0 if false, or -1 if not a JSON boolean */
if (i == 1) {
/* TX procedure: send immediately, 此处定义与Lora 模块之间通讯类型位 CLASS_C 模式 */
sent_immediate = true;
downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_C;
MSG("INFO: [down] a packet will be sent in \"immediate\" mode\n");
} else {
/* GPS timestamp is given, we consider it is a Class B downlink */
downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_B;
}
sent_immediate = false;
val = json_object_get_value(txpk_obj,"tmst");
/* TX procedure: send on GPS time (converted to timestamp value) */
val = json_object_get_value(txpk_obj, "tmms");
/* Get GPS time from JSON */
x2 = (uint64_t)json_value_get_number(val);
/* Convert GPS time from milliseconds to timespec */
x3 = modf((double)x2/1E3, &x4);
gps_tx.tv_sec = (time_t)x4; /* get seconds from integer part */
gps_tx.tv_nsec = (long)(x3 * 1E9); /* get nanoseconds from fractional part */
/* transform GPS time to timestamp */
i = lgw_gps2cnt(local_ref, gps_tx, &(txpkt.count_us));
downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_B;
/* Parse "No CRC" flag (optional field) */
val = json_object_get_value(txpk_obj,"ncrc");
/* 处理json串内容下列几项 */
val = json_object_get_value(txpk_obj,"nhdr");
val = json_object_get_value(txpk_obj,"freq");
val = json_object_get_value(txpk_obj,"rfch");
val = json_object_get_value(txpk_obj,"powe");
str = json_object_get_string(txpk_obj, "modu");
str = json_object_get_string(txpk_obj, "datr");
str = json_object_get_string(txpk_obj, "codr");
val = json_object_get_value(txpk_obj,"ipol");
val = json_object_get_value(txpk_obj,"prea");
/* Parse payload length (mandatory) */
val = json_object_get_value(txpk_obj,"size");
/* Parse payload data (mandatory) */
str = json_object_get_string(txpk_obj, "data");
}
/* Send acknoledge datagram to server */
send_tx_ack(buff_down[1], buff_down[2], jit_result, warning_value);
}
MSG("\nINFO: End of downstream thread\n");
}
在线程初始化时、设置 Lora 网络通讯类型是 CLASS A 模式,网络服务可以通过 imme 字段配置
Lora 通讯网络模式为 CLASS B 或 C 模式。
2.4 jit_enqueue 函数
源码路径@packet_forwarder/src/jitqueue.c
enum jit_error_e jit_enqueue(struct jit_queue_s *queue, uint32_t time_us, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e pkt_type) {
int i = 0;
uint32_t packet_post_delay = 0;
uint32_t packet_pre_delay = 0;
uint32_t target_pre_delay = 0;
enum jit_error_e err_collision;
uint32_t asap_count_us;
MSG_DEBUG(DEBUG_JIT, "Current concentrator time is %u, pkt_type=%d\n", time_us, pkt_type);
if (packet == NULL) {
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: invalid parameter\n");
return JIT_ERROR_INVALID;
}
if (jit_queue_is_full(queue)) {
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: cannot enqueue packet, JIT queue is full\n");
return JIT_ERROR_FULL;
}
/* Compute packet pre/post delays depending on packet's type */
switch (pkt_type) {
case JIT_PKT_TYPE_DOWNLINK_CLASS_A:
case JIT_PKT_TYPE_DOWNLINK_CLASS_B:
case JIT_PKT_TYPE_DOWNLINK_CLASS_C:
packet_pre_delay = TX_START_DELAY + TX_JIT_DELAY;
packet_post_delay = lgw_time_on_air(packet) * 1000UL; /* in us */
break;
case JIT_PKT_TYPE_BEACON:
/* As defined in LoRaWAN spec */
packet_pre_delay = TX_START_DELAY + BEACON_GUARD + TX_JIT_DELAY;
packet_post_delay = BEACON_RESERVED;
break;
default:
break;
}
pthread_mutex_lock(&mx_jit_queue);
/* An immediate downlink becomes a timestamped downlink "ASAP" */
/* Set the packet count_us to the first available slot */
if (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_C) {
/* change tx_mode to timestamped */
packet->tx_mode = TIMESTAMPED;
/* Search for the ASAP timestamp to be given to the packet */
asap_count_us = time_us + 2 * TX_JIT_DELAY; /* margin */
if (queue->num_pkt == 0) {
/* If the jit queue is empty, we can insert this packet */
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, first in JiT queue (count_us=%u)\n", asap_count_us);
} else {
/* Else we can try to insert it:
- ASAP meaning NOW + MARGIN
- at the last index of the queue
- between 2 downlinks in the queue
*/
/* First, try if the ASAP time collides with an already enqueued downlink */
for (i=0; i<queue->num_pkt; i++) {
if (jit_collision_test(asap_count_us, packet_pre_delay, packet_post_delay, queue->nodes[i].pkt.count_us, queue->nodes[i].pre_delay, queue->nodes[i].post_delay) == true) {
MSG_DEBUG(DEBUG_JIT, "DEBUG: cannot insert IMMEDIATE downlink at count_us=%u, collides with %u (index=%d)\n", asap_count_us, queue->nodes[i].pkt.count_us, i);
break;
}
}
if (i == queue->num_pkt) {
/* No collision with ASAP time, we can insert it */
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink ASAP at %u (no collision)\n", asap_count_us);
} else {
/* Search for the best slot then */
for (i=0; i<queue->num_pkt; i++) {
asap_count_us = queue->nodes[i].pkt.count_us + queue->nodes[i].post_delay + packet_pre_delay + TX_JIT_DELAY + TX_MARGIN_DELAY;
if (i == (queue->num_pkt - 1)) {
/* Last packet index, we can insert after this one */
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, last in JiT queue (count_us=%u)\n", asap_count_us);
} else {
/* Check if packet can be inserted between this index and the next one */
MSG_DEBUG(DEBUG_JIT, "DEBUG: try to insert IMMEDIATE downlink (count_us=%u) between index %d and index %d?\n", asap_count_us, i, i+1);
if (jit_collision_test(asap_count_us, packet_pre_delay, packet_post_delay, queue->nodes[i+1].pkt.count_us, queue->nodes[i+1].pre_delay, queue->nodes[i+1].post_delay) == true) {
MSG_DEBUG(DEBUG_JIT, "DEBUG: failed to insert IMMEDIATE downlink (count_us=%u), continue...\n", asap_count_us);
continue;
} else {
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink (count_us=%u)\n", asap_count_us);
break;
}
}
}
}
}
/* Set packet with ASAP timestamp */
packet->count_us = asap_count_us;
}
/* Check criteria_1: is it already too late to send this packet ?
* The packet should arrive at least at (tmst - TX_START_DELAY) to be programmed into concentrator
* Note: - Also add some margin, to be checked how much is needed, if needed
* - Valid for both Downlinks and Beacon packets
*
* Warning: unsigned arithmetic (handle roll-over)
* t_packet < t_current + TX_START_DELAY + MARGIN
*/
if ((packet->count_us - time_us) <= (TX_START_DELAY + TX_MARGIN_DELAY + TX_JIT_DELAY)) {
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet REJECTED, already too late to send it (current=%u, packet=%u, type=%d)\n", time_us, packet->count_us, pkt_type);
pthread_mutex_unlock(&mx_jit_queue);
return JIT_ERROR_TOO_LATE;
}
/* Check criteria_2: Does packet timestamp seem plausible compared to current time
* We do not expect the server to program a downlink too early compared to current time
* Class A: downlink has to be sent in a 1s or 2s time window after RX
* Class B: downlink has to occur in a 128s time window
* Class C: no check needed, departure time has been calculated previously
* So let's define a safe delay above which we can say that the packet is out of bound: TX_MAX_ADVANCE_DELAY
* Note: - Valid for Downlinks only, not for Beacon packets
*
* Warning: unsigned arithmetic (handle roll-over)
t_packet > t_current + TX_MAX_ADVANCE_DELAY
*/
if ((pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_A) || (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_B)) {
if ((packet->count_us - time_us) > TX_MAX_ADVANCE_DELAY) {
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet REJECTED, timestamp seems wrong, too much in advance (current=%u, packet=%u, type=%d)\n", time_us, packet->count_us, pkt_type);
pthread_mutex_unlock(&mx_jit_queue);
return JIT_ERROR_TOO_EARLY;
}
}
/* Check criteria_3: does this new packet overlap with a packet already enqueued ?
* Note: - need to take into account packet's pre_delay and post_delay of each packet
* - Valid for both Downlinks and beacon packets
* - Beacon guard can be ignored if we try to queue a Class A downlink
*/
for (i=0; i<queue->num_pkt; i++) {
/* We ignore Beacon Guard for Class A/C downlinks */
if (((pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_A) || (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_C)) && (queue->nodes[i].pkt_type == JIT_PKT_TYPE_BEACON)) {
target_pre_delay = TX_START_DELAY;
} else {
target_pre_delay = queue->nodes[i].pre_delay;
}
/* Check if there is a collision
* Warning: unsigned arithmetic (handle roll-over)
* t_packet_new - pre_delay_packet_new < t_packet_prev + post_delay_packet_prev (OVERLAP on post delay)
* t_packet_new + post_delay_packet_new > t_packet_prev - pre_delay_packet_prev (OVERLAP on pre delay)
*/
if (jit_collision_test(packet->count_us, packet_pre_delay, packet_post_delay, queue->nodes[i].pkt.count_us, target_pre_delay, queue->nodes[i].post_delay) == true) {
switch (queue->nodes[i].pkt_type) {
case JIT_PKT_TYPE_DOWNLINK_CLASS_A:
case JIT_PKT_TYPE_DOWNLINK_CLASS_B:
case JIT_PKT_TYPE_DOWNLINK_CLASS_C:
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet (type=%d) REJECTED, collision with packet already programmed at %u (%u)\n", pkt_type, queue->nodes[i].pkt.count_us, packet->count_us);
err_collision = JIT_ERROR_COLLISION_PACKET;
break;
case JIT_PKT_TYPE_BEACON:
if (pkt_type != JIT_PKT_TYPE_BEACON) {
/* do not overload logs for beacon/beacon collision, as it is expected to happen with beacon pre-scheduling algorith used */
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet (type=%d) REJECTED, collision with beacon already programmed at %u (%u)\n", pkt_type, queue->nodes[i].pkt.count_us, packet->count_us);
}
err_collision = JIT_ERROR_COLLISION_BEACON;
break;
default:
MSG("ERROR: Unknown packet type, should not occur, BUG?\n");
assert(0);
break;
}
pthread_mutex_unlock(&mx_jit_queue);
return err_collision;
}
}
/* Finally enqueue it */
/* Insert packet at the end of the queue */
memcpy(&(queue->nodes[queue->num_pkt].pkt), packet, sizeof(struct lgw_pkt_tx_s));
queue->nodes[queue->num_pkt].pre_delay = packet_pre_delay;
queue->nodes[queue->num_pkt].post_delay = packet_post_delay;
queue->nodes[queue->num_pkt].pkt_type = pkt_type;
if (pkt_type == JIT_PKT_TYPE_BEACON) {
queue->num_beacon++;
}
queue->num_pkt++;
/* Sort the queue in ascending order of packet timestamp */
jit_sort_queue(queue);
/* Done */
pthread_mutex_unlock(&mx_jit_queue);
jit_print_queue(queue, false, DEBUG_JIT);
MSG_DEBUG(DEBUG_JIT, "enqueued packet with count_us=%u (size=%u bytes, toa=%u us, type=%u)\n", packet->count_us, packet->size, packet_post_delay, pkt_type);
return JIT_ERROR_OK;
}
此函数代码基本功能:
<1>. 根据网络通讯模式CLASS A B C 计算数据传输时间;
<2>. 把数据包插入到发送队列、并排序发送时间的数据包;
数据的发送是在 thread_jit 线程中处理,此处理解为 thread_down 线程负责数据打包、并入队。
总结
此线程主要功能是推拉式、向网络服务器索要数据内容、并根据数据内容动态刷新网关状态参数;
如果本篇文章对您有所启发或帮助、请给笔者点赞助力、鼓励笔者坚持把此系列内容尽快梳理、分享出来。
谢谢。