无线定位之 二 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时间的来龙去脉、并在…...
MINIX 1.0 文件系统的实现(C/C++实现)
MINIX 1.0 文件系统简介: Linux 0.11操作系统启动时需要加载一个根目录,此根目录使用的是MINIX 1.0文件系统,其保存在硬盘的第一个分区中。Linux 0.11操作系统将硬盘上的两个连续的物理扇区(大小为512字节)做为一个物理…...
Spring Data Elasticsearch 中 ElasticsearchOperations 构建查询条件的详解
Spring Data Elasticsearch 中 ElasticsearchOperations 构建查询条件的详解 前言一、引入依赖二、配置 Elasticsearch三、创建模型类(Entity)四、使用 ElasticsearchOperations 进行 CRUD 操作1. 保存数据(Create)2. 获取数据&am…...
keil 解决 Error: CreateProcess failed, Command: ‘XXX\ARM\ARMCC\bin\fromelf.exe
参考文章链接: https://blog.csdn.net/qq_39172792/article/details/145499880 自己的: D:\Program Files\keil529\Keil_v5\ARM\ARMCLANG\bin\fromelf.exe --bin -o …/…/firmware_bin/L.bin ./Object/L.axf...
针对面试-mysql篇
1.如何定位慢查询? 1.1.介绍一下当时产生问题的场景(我们当时的接口测试的时候非常的慢,压测的结果大概5秒钟)),可以监测出哪个接口,最终因为是sql的问题 1.2.我们系统中当时采用了运维工具(Skywalkin就是2秒,一旦sql执行超过2秒…...
HNUST软件测试B考前最终复习
最近根据各个专业整理的考试重点,这两天总结出了以下内容,并附上了我自己复习的一些记忆小技巧,供大家参考,大家就图一乐。希望对你们的复习有所帮助,预祝大家考试顺利,加油! 本次考试和去年的题…...
网络编程epoll和udp
# epoll模型核心要点## 1. epoll核心概念### 1.1 高效IO多路复用- 监视列表与激活列表分离- 内核使用红黑树存储描述符- 边缘触发模式(EPOLLET)支持### 1.2 事件触发机制- **水平触发(LT)**:- 默认模式,类似select/poll- 数据未读完持续触发事件- **边缘…...
【速写】use_cache参数与decode再探讨
序言 纳什最近指出一个小细节,比如在Qwen系列模型中,两个special token: eos_token(<|im_end|>): 151645(im_end 中的 im 指的是 instruct message)pad_token(<|endoftext|>): 151643。 这是很有趣的事…...
智能网联汽车“内外协同、虚实共生”的通信生态
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界…...
《Python星球日记》 第64天:NLP 概述与文本预处理
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、NLP 简介1. 什么是自然语言处理?NLP 的应用场景: 2.…...
Java中堆栈
文章目录 Java中堆栈1. 栈(Stack)特点示例 2. 堆(Heap)特点示例 3. 核心区别4. 常见问题5. 内存可视化示例内存布局示意图: 总结 Java中堆栈 在 Java 中,“堆栈” 通常指的是堆(Heap࿰…...
模块化PCB设计中联排半孔的应用
随着电子产品的快速发展,高密度、多功能和小型化已成为未来的趋势。电路板上的元件几何指数在增加,而PCB尺寸却越来越小,因此需要与支撑板做配合。如果用助焊剂将圆孔焊接到母板上,由于圆孔体积较大,会产生冷焊&#x…...
xss-lab靶场4-7关基础详解
前言: 仅作为练习,复盘 推荐html在线运行平台,弹窗标签可以在平台运行,看语句是否能正常弹窗 HTML/CSS/Javascript在线代码运行工具 | 菜鸟教程 内容: 第四关 打开一看,输入<script>alert(1)&l…...
【Linux】进程状态、优先级、切换和调度
目录 一、传统操作系统进程状态 (一)什么是状态 (二)运行状态 (三)阻塞状态 (四)挂起状态 二、Linux进程状态 (一)进程状态 (二ÿ…...
软件测试基础知识详解
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 1、黑盒测试、白盒测试、灰盒测试 1.1 黑盒测试 黑盒测试 又叫 功能测试、数据驱动测试 或 基于需求规格说明书的功能测试。该类测试注重于测试软件的功能性需…...
【WordPress博客AI内容辅助生成/优化工具箱插件下载无标题】
主要功能 AI内容生成/优化 使用AI模型生成或优化段落内容 支持撤销和模型切换 AI自动评论 智能分析文章内容生成相关评论 可配置的评论数量和随机调度 生成评论回复的概率(评论数>10时) 可能以特定概率包含表情符号 AI标签提取 从文章内容自动提取相关标签 新标签可自动生…...
js 画立方体软件开发日记2
我不懂但我大为震惊 重开几次又回去了 这说明之前的操作无效 搞了个调试当前文件 导出模块有问题,跑显示没事 启动太慢,重构吧 ----------------------------------------------- 把那些鬼相机投影代码删了就有4s了 按钮全删了,还是卡&…...
element plus el-table多选框跨页多选保留
一、基础多选配置 通过 type“selection” 开启多选列,并绑定 selection-change 事件获取选中数据 <template><el-table :data"tableData" selection-change"handleSelectionChange"><el-table-column type"selection&qu…...
智能家居“心脏“升级战:GD25Q127CSIG国产芯片如何重构家庭物联生态
在智能家居设备出货量突破10亿台的2023年,家庭网关正经历着前所未有的技术革新。作为连接云端与终端设备的中枢神经,智能网关的存储芯片选择直接决定着整个智能生态系统的运行效率。在这场技术升级浪潮中,兆易创新GD25Q127CSIG串行闪存芯片主…...
智能枪弹柜管理系统|智能枪弹柜管理系统LK-QDG-Q20型
是一种用于存放枪支弹药的智能化设备,主要应用于涉枪单位,以下将从其功能特点、系统组成、优势等维度展开介绍: 功能特点 身份识别功能:采用多种生物识别技术,如指纹识别、指静脉识别、虹膜识别、人脸识别、声纹识别等…...
bootstrap table 添加跳转到指定页的功能(仅自己可见)
Table回调方法中,添加input和button至Table下方(Table页渲染结束后执行) (input用来输入页码,button执行跳转) function ajaxRequestExtends(data){$(".page-list").append("<input idp…...
rufus+Ubuntu 18.04 镜像
参考:https://blog.csdn.net/Li060703/article/details/106075597 Rufus 官网: https://rufus.ie/zh/ 步骤 安装U盘做好后插在主板io的USB口上,启动阶段用F2或DEL打断进bios,bios里面指定从安装U盘来启动,里面的B…...
基于事件驱动和策略模式的差异化处理方案
一、支付成功后事件驱动 1、支付成功事件 /*** 支付成功事件** author ronshi* date 2025/5/12 14:40*/ Getter Setter public class PaymentSuccessEvent extends ApplicationEvent {private static final long serialVersionUID 1L;private ProductOrderDO productOrderDO;…...
【运维】MacOS蓝牙故障排查与修复指南
在日常使用macOS系统过程中,蓝牙连接问题时有发生。无论是无法连接设备、连接不稳定还是蓝牙功能完全失效,这些问题都会严重影响我们的工作效率。本文将分享一些实用的排查方法和修复技巧,帮助你解决macOS系统上的蓝牙故障。 问题症状 常见…...
linux小主机搭建自己的nas(三)docker安装nextcloud
小主机用的TF卡,不可能把nas的数据放在卡上,所以我买了个2T的移动硬盘 1.挂载移动硬盘 查找硬盘 lsblk # 或 fdisk -l 创建挂载点 sudo mkdir -p alon_ssd 查看硬盘文件系统,文件系统类型一会儿设置挂载用 sudo blkid /dev/sda1 开机自动挂载&…...
Go语言:json 作用和语法
在 Go 语言中,JSON 字段(也称为 JSON Tag)是附加在结构体字段上的元数据,用于控制该字段在 JSON 编码(序列化)和解码(反序列化) 时的行为。它的语法是: type StructName…...
mageia系统详解
Mageia 是一个由社区驱动的 Linux 发行版,源自 Mandriva Linux(原 Mandrake Linux),以用户友好性和强大的系统工具著称。它继承了 Mandriva 的易用性传统,同时专注于稳定性和社区协作。以下从历史背景、技术架构、系统…...
六、STM32 HAL库回调机制详解:从设计原理到实战应用
STM32 HAL库回调机制详解:从设计原理到实战应用 一、回调机制的本质与设计目标 在STM32 HAL库中,回调机制是实现异步事件处理的核心设计模式。它通过弱定义函数用户重写的方式,将硬件事件(如数据传输完成、定时器溢出等…...
USB传输模式
USB有四种传输模式: 控制传输, 中断传输, 同步传输, 批量传输 1. 中断传输 中断传输一般用于小批量, 非连续的传输. 对实时性要求较高. 常见的使用此传输模式的设备有: 鼠标, 键盘等. 要注意的是, 这里的 “中断” 和我们常见的中断概念有差异. Linux中的中断是设备主动发起的…...
lenis滑动插件的笔记
官网 lenis - npm 方法一:基础判断(推荐) 通过 Lenis 自带的 scroll 和 limit 属性直接判断: const lenis new Lenis()// 滚动事件监听 lenis.on(scroll, ({ scroll, limit }) > {const distanceToBottom limit - scroll…...
【网络安全】SQL注入
如果文章不足还请各位师傅批评指正! 想象一下,你经营着一家咖啡店,顾客可以通过店内的点单系统下单。这个系统会根据顾客的输入,向后厨发送指令,比如“为顾客A准备一杯拿铁”。 然而,如果有个不怀好意的顾客…...
window server 2012安装sql server2008 r2
执行sql server2008 r2安装目录下的setup 选择运行程序而不获取帮助 然后就是让人绝望的 只能先搞这个了,F*微软,自家软件不让正常安装 打开服务器管理器->添加角色和功能->选择Web 服务(IIS)->添加.NET Framework3.5 然…...
uni-app学习笔记五--vue3插值表达式的使用
vue3快速上手导航:简介 | Vue.js 模板语法 插值表达式 最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 (即双大括号): <span>Message: {{ msg }}</span> 双大括号标签会被替换为相应组件实例中 msg 属性的值。同…...
RuoYi 中使用 PageUtils.startPage() 实现分页查询的完整解析
文章目录 一、PageHelper 简介与基本用法使用方式如下: 二、Mapper 接口返回类型对分页的影响1. 返回 Page<T> 类型(推荐)2. 返回 List<T> 类型(不推荐) 三、解析RuoYi 是如何使用 PageUtils.startPage()1…...
【番外】02:Windows 编译带 DNN_CUDA 功能的 OpenCV 动态链接库
文章目录 1. 环境准备2. 兼容性说明3. 算力查询4. 编译步骤5. 网盘资料 提示: 如果读者因网络环境受限,无法正常下载与本文相关的软件安装包、压缩包,以及编译时的依赖文件,可以从文章最后提供的网盘链接下载资源。 1. 环境准备 …...
Java详解LeetCode 热题 100(14):LeetCode 56. 合并区间(Merge Intervals)详解
文章目录 1. 题目描述2. 理解题目3. 解法一:排序 一次遍历法3.1 思路3.2 Java代码实现3.3 代码详解3.4 复杂度分析3.5 适用场景 4. 解法二:双指针法4.1 思路4.2 Java代码实现4.3 代码详解4.4 复杂度分析4.5 与解法一的比较 5. 解法三:TreeMa…...
回答 | 图形数据库neo4j社区版可以应用小型企业嘛?
刚在知乎上看到了一个提问,挺有意思,于是乎,贴到这里再简聊一二。 转自知乎提问 当然可以,不过成本问题不容小觑。另外还有性能上的考量。 就在最近,米国国家航空航天局——NASA因为人力成本问题,摒弃了使…...
2024年北理工Python123第六章测验题整理
测验题一般不会太难,但是这次的题目,未免太多了,有的还很难 一、选择题 二、编程题 1-10 列表和字符串 1-10都是和列表、字符串有关的题目,都很简单,我直接给出代码了 1.列表排序输出 import random random.seed(int(input()…...
常用的设计模式详解
常用的设计模式详解 在后端开发中,设计模式是提升代码可维护性、扩展性和灵活性的关键工具。通过合理应用设计模式,开发者能够高效解决复杂问题并优化系统架构。本文将结合实际案例,深入探讨后端开发中常用的设计模式及其核心应用场景。 一、…...
OFCMS代码审计-freemaker注入sql注入xxexss文件上传
环境搭建 下载地址:https://gitee.com/oufu/ofcms/repository/archive/V1.1.2?formatzip SSTI模板注入(freemaker) FreeMarker模板注入实现远程命令执行 - Eleven_Liu - 博客园 在admin中找到这个 发现请求的是这个 找到他 <#assign value"f…...
python与nodejs哪个性能高
在一般的Web开发和高并发场景中,Node.js的性能通常优于Python,特别是在处理大量异步任务和实时应用时更具优势;而在数据分析、机器学习及计算密集型任务中,Python则表现出更高的性能优势。 Node.js以事件驱动的非阻塞I/O模型著称&…...
云平台管理部署知识点——问题+答案
1、在k8s 中定义副本数量的关键字是那个?处于那个模块下? 关键字:replicas 模块:spec下 2、在k8s中,有状态应用和无状态应用的区别?创建有状态和无状态应用分别使用哪种资源对象类型? &#…...
数据结构(六)——树和二叉树
一、树和二叉树的定义与存储 1.树的定义 树是一种非线性的数据结构,它是由n个有限结点组成有层次关系的集合 树具有以下特点: (1)每个结点具有0个或多个子结点 (2)每个子结点只有一个父结点 ÿ…...
基于构件的开发方法与传统开发方法的区别
在软件开发领域,基于构件的开发方法和传统开发方法有着截然不同的特点与应用效果,这些差异显著影响着项目的实施过程与最终成果。下面,我们将从多个关键维度展开对比分析。 一、开发模式:线性搭建与模块组装 传统开发方法遵循线性的、自顶向下的流程,就像搭建一座高楼…...
cursor对话关键词技巧
提示词基本结构与原则 一个好的 Cursor 提示词通常包含三个部分:目标说明 上下文信息 具体要求。 例如: 创建一个React登录组件,使用Tailwind CSS样式,需要包含邮箱验证功能和记住密码选项。 效果演示: 提示词的的…...
克隆虚拟机组成集群
一、克隆虚拟机 1. 准备基础虚拟机 确保基础虚拟机已安装好操作系统(如 Ubuntu)、Java 和 Hadoop。关闭防火墙并禁用 SELinux(如适用): bash sudo ufw disable # Ubuntu sudo systemctl disable firewalld # CentO…...
添加购物车-02.代码开发
一.代码开发 购物车属于用户端功能,因此要在user下创建controller代码。 Controller层 package com.sky.controller.user;import com.sky.dto.ShoppingCartDTO; import com.sky.entity.ShoppingCart; import com.sky.result.Result; import com.sky.service.Shopp…...
2094. 找出 3 位偶数
from typing import Listclass Solution:def findEvenNumbers(self, digits: List[int]) -> List[int]:# 统计 digits 中每个数字(0-9)的出现次数。cnt [0] * 10for d in digits:cnt[d] 1ans []# i0 百位,i1 十位,i2 个位&a…...
外出充电不发愁,倍思便携式移动电源成出行新宠
电子设备已深度融入现代快节奏生活,成为出行必备。但随之而来的电量焦虑,却始终困扰着人们。无论是出差远行、户外探索,还是每日通勤,如何随时为设备充电,成了亟待解决的难题。倍思极客充伸缩数据线充电宝应运而生&…...
防火墙安全策略基础配置
拓朴图 设备基础配置 # AR1 路由器配置 [Huawei]interface GigabitEthernet0/0/0 [Huawei-GigabitEthernet0/0/0]ip address 1.1.1.2 255.255.255.0 [Huawei]ip route-static 192.168.1.0 255.255.255.0 1.1.1.1# FW1 防火墙配置 [USG6000V1]sysname FW1 [FW1]interface Gigab…...