当前位置: 首页 > article >正文

无线定位之 二 SX1302 网关源码 thread_down 线程详解

前言

笔者计划通过无线定位系列文章、系统的描述 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 线程负责数据打包、并入队。

总结

此线程主要功能是推拉式、向网络服务器索要数据内容、并根据数据内容动态刷新网关状态参数;
如果本篇文章对您有所启发或帮助、请给笔者点赞助力、鼓励笔者坚持把此系列内容尽快梳理、分享出来。
谢谢。

相关文章:

无线定位之 二 SX1302 网关源码 thread_down 线程详解

前言 笔者计划通过无线定位系列文章、系统的描述 TDOA 无线定位和混合定位相关技术知识点, 并以实践来验证此定位系统精度。 笔者从实践出发、本篇直接走读无线定位系统关键节点、网关 SX1302 源码框架,并在源码走读过程 中、着重分析与无线定位相关的PPS时间的来龙去脉、并在…...

对心理幸福感含义的探索 | 幸福就是一切吗?

注&#xff1a;机翻&#xff0c;未校。 Happiness Is Everything, or Is It? Explorations on the Meaning of Psychological Well-Being 幸福就是一切吗&#xff1f;对心理幸福感含义的探索 Journal of Personality and Social Psychology 1989, Vol. 57, No. 6,1069-1081 …...

多平台图标设计与管理的终极解决方案

IconWorkshop Pro 是一款由Axialis团队开发的专业图标设计与制作软件&#xff0c;专注于为设计师、开发者及企业用户提供高效且灵活的图标创作解决方案。该软件凭借其强大的功能与跨平台适配性&#xff0c;成为Windows、macOS、iOS、Android等多系统图标设计的首选工具之一。 …...

ngx_http_keyval_module动态键值管理

一、模块安装与验证 检查模块是否可用 nginx -V 2>&1 | grep --color -o ngx_http_keyval_module如果看到 ngx_http_keyval_module&#xff0c;说明模块已编译进 NGINX。 若未找到&#xff0c;请联系你的 NGINX 供应商&#xff0c;获取商业版或重新编译并启用该模块&am…...

【高频面试题】LRU缓存

文章目录 1 相关前置知识&#xff08;OS&#xff09;2 面试题 16.25. LRU 缓存2.1 题面2.2 示例2.3 解法1 &#xff08;双端队列哈希表&#xff09;思路 2.4 解法2思路 3 参考 1 相关前置知识&#xff08;OS&#xff09; 为什么需要页面置换算法&#xff1a;当进程运行时&…...

讯联云库项目开发日志(二)AOP参数拦截

目录 利用AOP实现参数拦截: 一、​​HTTP请求进入Controller​&#xff08;发送邮件验证码&#xff09; 二、AOP切面触发 1. 切面拦截&#xff08;GlobalOperactionAspect.class&#xff09; method.getAnnotation()​​ null interceptor 判断​​ 2.参数校验注解 3. 参…...

龙虎榜——20250515

上证指数缩量收阴线&#xff0c;个股跌多涨少&#xff0c;上涨波段4月9日以来已有24个交易日&#xff0c;时间周期上处于上涨末端&#xff0c;注意风险。 深证指数缩量收阴线&#xff0c;日线上涨结束的概率在增大&#xff0c;注意风险。 2025年5月15日龙虎榜行业方向分析 一…...

知识图谱重构电商搜索:下一代AI搜索引擎的底层逻辑

1. 搜索引擎的进化论 从雅虎目录式搜索到Google的PageRank算法&#xff0c;搜索引擎经历了三次技术跃迁。而AI搜索引擎正在掀起第四次革命&#xff1a;在电商场景中&#xff0c;传统的「关键词匹配」已无法满足个性化购物需求&#xff0c;MOE搜索等新一代架构开始融合知识图谱…...

python-修改图片背景色

在Python中&#xff0c;可以使用图像处理库&#xff08;如OpenCV或Pillow&#xff09;来修改图片的背景色。通常&#xff0c;修改背景色的流程包括以下步骤&#xff1a; 1、对图片进行分割&#xff0c;识别前景和背景。 2、对背景区域进行颜色替换。 下面是两种实现方法&#x…...

卡洛诗,将高端西餐的冗余价值转化为普惠体验

西餐市场正经历一场结构性变革&#xff0c;一二线城市的高端西餐陷入内卷&#xff0c;而下沉市场却因品质与价格断层陷入选择困境——消费者既不愿为高价西餐的面子溢价买单&#xff0c;又难以忍受快餐式西餐的粗糙体验。这一矛盾催生了万亿级的市场真空地带&#xff0c;萨莉亚…...

【ROS2】ROS节点启动崩溃:rclcpp::exceptions::RCLInvalidArgument

1、问题描述 启动ROS节点时,直接崩溃,打印信息如下: terminate called after throwing an instance of rclcpp::exceptions::RCLInvalidArgumentwhat(): failed to create guard condition: context argument is null, at ./src/rcl/guard_condition.c:65 [ros2run]: Abo…...

Flutter在键盘的上方加一个完成按钮

有些情况下&#xff0c;输入框在输入键盘弹出后&#xff0c; 需要在键盘的上方显示一个toolbar &#xff0c; 然后 toolbar 上面一个完成按钮&#xff0c;点完成按钮把键盘关闭。 如图&#xff1a; 直接上代码&#xff0c;这样写的好处是&#xff0c;把 TextField 给封装了&…...

SQL注入---05--跨站注入

1 权限说明 select * from mysql.user; 这里的Y表示我前面的命令权限为root&#xff0c;n表示不支持root权限 导致结果&#xff1a; 如果为root的话&#xff0c;我就可操作这些命令并且可以进行跨数据库攻击&#xff0c;但是如果不是高权限root就无法执行这些操作 2 root权限…...

Vue 学习随笔系列二十三 -- el-date-picker 组件

el-date-picker 组件 文章目录 el-date-picker 组件el-date-picker 只有某些日期可选 el-date-picker 只有某些日期可选 <template><div><el-form ref"form" size"mini":model"form" :rules"rules"label-width"8…...

【免费分享】虚拟机VM(适用于 Windows)17.6.3

—————【下 载 地 址】——————— 【​本章下载一】&#xff1a;https://drive.uc.cn/s/7c4da5cd2af64 【​本章下载二】&#xff1a;https://pan.xunlei.com/s/VOQDkRRKc5OUVTauZezaiDEHA1?pwdpybg# 【百款黑科技】&#xff1a;https://ucnygalh6wle.feishu.cn/wiki/…...

Oracle中的select1条、几条、指定范围的语句

在Oracle中&#xff0c;可以使用不同的方法来选择一条记录、多条记录或指定范围内的记录。以下是具体的实现方式&#xff1a; 1. 查询单条记录 使用ROWNUM伪列限制结果为1条&#xff1a; SELECT * FROM your_table WHERE ROWNUM 1;特点&#xff1a;Oracle会在结果集生成时分…...

2025 后端自学UNIAPP【项目实战:旅游项目】5、个人中心页面:微信登录,同意授权,获取用户信息

一、框架以及准备工作 1、前端项目文件结构展示 2、后端项目文件结构展示 3、登录微信公众平台&#xff0c;注册一个个人的程序&#xff0c;获取大appid&#xff08;前端后端都需要&#xff09;和密钥&#xff08;后端需要&#xff09; 微信公众平台微信公众平台&…...

校园网规划与设计方案

一、项目概述 校园网是学校实现信息化教学、科研与管理的重要基础设施,其性能与稳定性直接影响学校的整体发展。随着学校规模不断扩大、教学科研活动日益丰富,对校园网的带宽、可靠性、安全性以及智能化管理等方面提出了更高要求。本规划与设计方案旨在构建一个高速、稳定、…...

蓝桥杯算法题 -蛇形矩阵(方向向量)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 P…...

配置VScodePython环境Python was not found;

Python was not found; run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Manage App Execution Aliases. 候试试重启电脑。 在卸载重装python后会出现难以解决的局面&#xff0c;系统变量&#xff0c;命令行&#…...

ollama 重命名模型

ollama 重命名模型 ollama list# 查看列表 ollama list # 生成原模型的Modelfile文件 ollama show --modelfile qwen3:32b > Modelfile # 从Modelfile文件创建新的模型 ollama create qwen3 -f Modelfile # 删除原模型 ollama rm qwen3:32b...

Qt信号槽机制与UI设计完全指南:从基础原理到实战应用

目录 前言一、信号槽1.1 传参1.2 Qt信号与槽的对应关系1.2.1一对多关系1.2.2 多对一关系 二、Designer三、Layout 布局3.1 基础用法3.2 打破布局3.3 贴合窗口3.4 伸展器&#xff08;Spacer&#xff09;3.5 嵌套布局 四、ui指针五、QWidget六、QLabel 标签使用指南总结 前言 本…...

Anaconda环境中conda与pip命令的区别

文章目录 conda与pip的基本区别在Anaconda环境中的实际差异安装包环境管理依赖解决示例最佳实践建议 常见问题解答 conda与pip的基本区别 包来源与生态系统 conda&#xff1a;从Anaconda默认仓库或conda-forge等渠道获取包 不仅管理Python包&#xff0c;还能管理非Python依赖&…...

XBL6501/02/03在POE设备上的应用方案

前言&#xff1a; 在当今数字化时代&#xff0c;POE&#xff08;Power over Ethernet&#xff09;设备因其能够通过以太网线同时传输数据和电力而被广泛应用。为了满足这些设备日益增长的电源需求&#xff0c;芯伯乐推出了XBL6501/02/03系列DC-DC电源芯片&#xff0c;为POE设备…...

编程题 03-树2 List Leaves【PAT】

文章目录 题目输入格式输出格式输入样例输出样例 题解解题思路完整代码 编程练习题目集目录 题目 Given a tree, you are supposed to list all the leaves in the order of top down, and left to right. 输入格式 Each input file contains one test case. For each case, …...

生信小白学Rust-03

语句和表达式 举个栗子&#x1f330; fn add_with_extra(x: i32, y: i32) -> i32 {let x x 1; // 语句let y y 5; // 语句x y // 表达式 } // 语句执行操作 // 表达式会返回一个值 怎么区分呢&#xff0c;目前我的理解是只要返回了值&#xff0c;那它就是表达式 fn…...

缺乏需求优先级划分时,如何合理分配资源?

当需求优先级不明确时&#xff0c;合理分配资源的关键在于建立统一评估标准、实施敏捷资源管理、提升团队协作效率、加强跨部门沟通机制。尤其是建立统一评估标准至关重要&#xff0c;它能帮助组织快速判断各项需求的重要性与紧迫性&#xff0c;从而实现资源的动态匹配与有效利…...

操作系统学习笔记第3章 内存管理(灰灰题库)

1. 单选题 某页式存储管理系统中&#xff0c;主存为 128KB&#xff0c;分成 32 块&#xff0c;块号为 0、1、2、3、…、31。某作业有 5 块&#xff0c;其页号为 0、1、2、3、4&#xff0c;被分别装入主存的 3、8、4、6、9 块中。有一逻辑地址为 [3, 70]&#xff08;其中方括号中…...

详细分析python 中的deque 以及和list 的用法区别

dqque :双端队列&#xff0c;可以快速的从另外一侧追加和推出对象,deque是一个双向链表&#xff0c;针对list连续的数据结构插入和删除进行优化。它提供了两端都可以操作的序列&#xff0c;这表示在序列的前后你都可以执行添加或删除操作。 通过上图可以看出&#xff0c;deque …...

Stack overflow

本文来源 &#xff1a;腾讯元宝 Stack Overflow - Where Developers Learn, Share, & Build Careers 开发者学习&#xff0c;分享 通过学习、工作和经验积累等方式&#xff0c;逐步建立和发展自己的职业生涯。 Find answers to your technical questions and help othe…...