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

无线定位之 三 SX1302 网关源码 thread_gps 线程详解

前言

笔者计划通过无线定位系列文章、系统的描述 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);    //> SX1261 SCAN扫描线程

主程序源码基本功能就这么多,笔者就不贴出源码对照了,下面进入我们本章主题 thread_gps 线程的代码走读。

第2节 thread_gps 程序框架描述

2.1 thread_gps 线程通讯参数

此gps线程是针对 ubx-7 模块设计、串口参数配置内容如下:

 "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},

配置文件中 “gps_tty_path”: “/dev/ttyS0” 指定 UBX-7 模块硬件连接到串口1上,通讯参数配置及串口打开如下:

/* Start GPS a.s.a.p., to allow it to lock */if (gps_tty_path[0] != '\0') { /* do not try to open GPS device if no path set */i = lgw_gps_enable(gps_tty_path, "ubx7", 0, &gps_tty_fd); /* HAL only supports u-blox 7 for now */if (i != LGW_GPS_SUCCESS) {printf("WARNING: [main] impossible to open %s for GPS sync (check permissions)\n", gps_tty_path);gps_enabled = false;gps_ref_valid = false;} else {printf("INFO: [main] TTY port %s open for GPS synchronization\n", gps_tty_path);gps_enabled = true;gps_ref_valid = false;}}//> 此段源码路径 @libloragw/src/loragw_gps.cint lgw_gps_enable(char *tty_path, char *gps_family, speed_t target_brate, int *fd_ptr) {int i;struct termios ttyopt; /* serial port options */int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */uint8_t ubx_cmd_timegps[UBX_MSG_NAVTIMEGPS_LEN] = {0xB5, 0x62, /* UBX Sync Chars */0x06, 0x01, /* CFG-MSG Class/ID */0x08, 0x00, /* Payload length */0x01, 0x20, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, /* Enable NAV-TIMEGPS output on serial */0x32, 0x94 }; /* Checksum */ssize_t num_written;/* check input parameters */CHECK_NULL(tty_path);CHECK_NULL(fd_ptr);/* open TTY device */gps_tty_dev = open(tty_path, O_RDWR | O_NOCTTY);if (gps_tty_dev <= 0) {DEBUG_MSG("ERROR: TTY PORT FAIL TO OPEN, CHECK PATH AND ACCESS RIGHTS\n");return LGW_GPS_ERROR;}*fd_ptr = gps_tty_dev;/* manage the different GPS modules families */if (gps_family == NULL) {DEBUG_MSG("WARNING: this version of GPS module may not be supported\n");} else if (strncmp(gps_family, "ubx7", 4) != 0) {/* The current implementation relies on proprietary messages from U-Blox *//* GPS modules (UBX, NAV-TIMEGPS...) and has only be tested with a u-blox 7. *//* Those messages allow to get NATIVE GPS time (no leap seconds) required *//* for class-B handling and GPS synchronization *//* see lgw_parse_ubx() function for details */DEBUG_MSG("WARNING: this version of GPS module may not be supported\n");}/* manage the target bitrate */if (target_brate != 0) {DEBUG_MSG("WARNING: target_brate parameter ignored for now\n"); // TODO}/* get actual serial port configuration */i = tcgetattr(gps_tty_dev, &ttyopt);if (i != 0) {DEBUG_MSG("ERROR: IMPOSSIBLE TO GET TTY PORT CONFIGURATION\n");return LGW_GPS_ERROR;}/* Save current serial port configuration for restoring later */memcpy(&ttyopt_restore, &ttyopt, sizeof ttyopt);/* update baudrates */cfsetispeed(&ttyopt, DEFAULT_BAUDRATE);   //>DEFAULT_BAUDRATE=B9600cfsetospeed(&ttyopt, DEFAULT_BAUDRATE);/* update terminal parameters *//* The following configuration should allow to:- Get ASCII NMEA messages- Get UBX binary messages- Send UBX binary commandsNote: as binary data have to be read/written, we need to disablevarious character processing to avoid loosing data *//* Control Modes */ttyopt.c_cflag |= CLOCAL;  /* local connection, no modem control */ttyopt.c_cflag |= CREAD;   /* enable receiving characters */ttyopt.c_cflag |= CS8;     /* 8 bit frames */ttyopt.c_cflag &= ~PARENB; /* no parity */ttyopt.c_cflag &= ~CSTOPB; /* one stop bit *//* Input Modes */ttyopt.c_iflag |= IGNPAR;  /* ignore bytes with parity errors */ttyopt.c_iflag &= ~ICRNL;  /* do not map CR to NL on input*/ttyopt.c_iflag &= ~IGNCR;  /* do not ignore carriage return on input */ttyopt.c_iflag &= ~IXON;   /* disable Start/Stop output control */ttyopt.c_iflag &= ~IXOFF;  /* do not send Start/Stop characters *//* Output Modes */ttyopt.c_oflag = 0;        /* disable everything on output as we only write binary *//* Local Modes */ttyopt.c_lflag &= ~ICANON; /* disable canonical input - cannot use with binary input */ttyopt.c_lflag &= ~ISIG;   /* disable check for INTR, QUIT, SUSP special characters */ttyopt.c_lflag &= ~IEXTEN; /* disable any special control character */ttyopt.c_lflag &= ~ECHO;   /* do not echo back every character typed */ttyopt.c_lflag &= ~ECHOE;  /* does not erase the last character in current line */ttyopt.c_lflag &= ~ECHOK;  /* do not echo NL after KILL character *//* settings for non-canonical moderead will block for until the lesser of VMIN or requested chars have been received */ttyopt.c_cc[VMIN]  = LGW_GPS_MIN_MSG_SIZE;ttyopt.c_cc[VTIME] = 0;/* set new serial ports parameters */i = tcsetattr(gps_tty_dev, TCSANOW, &ttyopt);if (i != 0){DEBUG_MSG("ERROR: IMPOSSIBLE TO UPDATE TTY PORT CONFIGURATION\n");return LGW_GPS_ERROR;}tcflush(gps_tty_dev, TCIOFLUSH);/* Send UBX CFG NAV-TIMEGPS message to tell GPS module to output native GPS time *//* This is a binary message, serial port has to be properly configured to handle this */num_written = write (gps_tty_dev, ubx_cmd_timegps, UBX_MSG_NAVTIMEGPS_LEN);if (num_written != UBX_MSG_NAVTIMEGPS_LEN) {DEBUG_MSG("ERROR: Failed to write on serial port (written=%d)\n", (int) num_written);}/* get timezone info */tzset();/* initialize global variables */gps_time_ok = false;gps_pos_ok = false;gps_mod = 'N';return LGW_GPS_SUCCESS;
}

主程序启动时打开串口、并配置 UBX-7 模块的工作参数,启动 thread_gps 线程时就直接读取gps串口数据就可以。

2.2 thread_gps 程序框架


void thread_gps(void) {/* serial variables */char serial_buff[128]; /* buffer to receive GPS data */size_t wr_idx = 0;     /* pointer to end of chars in buffer *//* variables for PPM pulse GPS synchronization */enum gps_msg latest_msg; /* keep track of latest NMEA message parsed *//* initialize some variables before loop */memset(serial_buff, 0, sizeof serial_buff);while (!exit_sig && !quit_sig) {size_t rd_idx = 0;size_t frame_end_idx = 0;/* blocking non-canonical read on serial port,阻塞式读取 gps 串口数据内容 */ssize_t nb_char = read(gps_tty_fd, serial_buff + wr_idx, LGW_GPS_MIN_MSG_SIZE);if (nb_char <= 0) {MSG("WARNING: [gps] read() returned value %zd\n", nb_char);continue;}wr_idx += (size_t)nb_char;/******************************************** Scan buffer for UBX/NMEA sync chars and ** attempt to decode frame if one is found ********************************************/while (rd_idx < wr_idx) {size_t frame_size = 0;/* Scan buffer for UBX sync char */if (serial_buff[rd_idx] == (char)LGW_GPS_UBX_SYNC_CHAR) {/************************ Found UBX sync char ************************/latest_msg = lgw_parse_ubx(&serial_buff[rd_idx], (wr_idx - rd_idx), &frame_size);if (frame_size > 0) {if (latest_msg == INCOMPLETE) {/* UBX header found but frame appears to be missing bytes */frame_size = 0;} else if (latest_msg == INVALID) {/* message header received but message appears to be corrupted */MSG("WARNING: [gps] could not get a valid message from GPS (no time)\n");frame_size = 0;} else if (latest_msg == UBX_NAV_TIMEGPS) {gps_process_sync();}}} else if (serial_buff[rd_idx] == (char)LGW_GPS_NMEA_SYNC_CHAR) {/************************* Found NMEA sync char *************************//* scan for NMEA end marker (LF = 0x0a) */char* nmea_end_ptr = memchr(&serial_buff[rd_idx],(int)0x0a, (wr_idx - rd_idx));if(nmea_end_ptr) {/* found end marker */frame_size = nmea_end_ptr - &serial_buff[rd_idx] + 1;latest_msg = lgw_parse_nmea(&serial_buff[rd_idx], frame_size);if(latest_msg == INVALID || latest_msg == UNKNOWN) {/* checksum failed */frame_size = 0;} else if (latest_msg == NMEA_RMC) { /* Get location from RMC frames */gps_process_coords();}}}if (frame_size > 0) {/* At this point message is a checksum verified framewe're processed or ignored. Remove frame from buffer */rd_idx += frame_size;frame_end_idx = rd_idx;} else {rd_idx++;}} if (frame_end_idx) {/* Frames have been processed. Remove bytes to end of last processed frame */memcpy(serial_buff, &serial_buff[frame_end_idx], wr_idx - frame_end_idx);wr_idx -= frame_end_idx;} /* Prevent buffer overflow */if ((sizeof(serial_buff) - wr_idx) < LGW_GPS_MIN_MSG_SIZE) {memcpy(serial_buff, &serial_buff[LGW_GPS_MIN_MSG_SIZE], wr_idx - LGW_GPS_MIN_MSG_SIZE);wr_idx -= LGW_GPS_MIN_MSG_SIZE;}}MSG("\nINFO: End of GPS thread\n");
}

线程 thread_gps 通过阻塞式读取 ubx-7 模块的时间同步信息和 NMEA 信息内容。

2.3 lgw_parse_ubx 函数

源码路径:@libloragw/src/loragw-gps.c

enum gps_msg lgw_parse_ubx(const char *serial_buff, size_t buff_size, size_t *msg_size) {bool valid = 0;    /* iTOW, fTOW and week validity */unsigned int payload_length;uint8_t ck_a, ck_b;uint8_t ck_a_rcv, ck_b_rcv;unsigned int i;*msg_size = 0; /* ensure msg_size alway receives a value *//* check input parameters */if (serial_buff == NULL) {return IGNORED;}if (buff_size < 8) {DEBUG_MSG("ERROR: TOO SHORT TO BE A VALID UBX MESSAGE\n");return IGNORED;}/* display received serial data and checksum */DEBUG_MSG("Note: parsing UBX frame> ");for (i=0; i<buff_size; i++) {DEBUG_MSG("%02x ", serial_buff[i]);}DEBUG_MSG("\n");/* Check for UBX sync chars 0xB5 0x62 */if ((serial_buff[0] == (char)0xB5) && (serial_buff[1] == (char)0x62)) {/* Get payload length to compute message size */payload_length  = (uint8_t)serial_buff[4];payload_length |= (uint8_t)serial_buff[5] << 8;*msg_size = 6 + payload_length + 2; /* header + payload + checksum *//* check for complete message in buffer */if(*msg_size <= buff_size) {/* Validate checksum of message */ck_a_rcv = serial_buff[*msg_size-2]; /* received checksum */ck_b_rcv = serial_buff[*msg_size-1]; /* received checksum *//* Use 8-bit Fletcher Algorithm to compute checksum of actual payload */ck_a = 0; ck_b = 0;for (i=0; i<(4 + payload_length); i++) {ck_a = ck_a + serial_buff[i+2];ck_b = ck_b + ck_a;}/* Compare checksums and parse if OK */if ((ck_a == ck_a_rcv) && (ck_b == ck_b_rcv)) {/* Check for Class 0x01 (NAV) and ID 0x20 (NAV-TIMEGPS) */if ((serial_buff[2] == 0x01) && (serial_buff[3] == 0x20)) {/* Check validity of information */valid = serial_buff[17] & 0x3; /* towValid, weekValid */if (valid) {/* Parse buffer to extract GPS time *//* Warning: payload byte ordering is Little Endian */gps_iTOW =  (uint8_t)serial_buff[6];gps_iTOW |= (uint8_t)serial_buff[7] << 8;gps_iTOW |= (uint8_t)serial_buff[8] << 16;gps_iTOW |= (uint8_t)serial_buff[9] << 24; /* GPS time of week, in ms */gps_fTOW =  (uint8_t)serial_buff[10];gps_fTOW |= (uint8_t)serial_buff[11] << 8;gps_fTOW |= (uint8_t)serial_buff[12] << 16;gps_fTOW |= (uint8_t)serial_buff[13] << 24; /* Fractional part of iTOW, in ns */gps_week =  (uint8_t)serial_buff[14];gps_week |= (uint8_t)serial_buff[15] << 8; /* GPS week number */gps_time_ok = true;
#if 0/* For debug */{short ubx_gps_hou = 0; /* hours (0-23) */short ubx_gps_min = 0; /* minutes (0-59) */short ubx_gps_sec = 0; /* seconds (0-59) *//* Format GPS time in hh:mm:ss based on iTOW */ubx_gps_sec = (gps_iTOW / 1000) % 60;ubx_gps_min = (gps_iTOW / 1000 / 60) % 60;ubx_gps_hou = (gps_iTOW / 1000 / 60 / 60) % 24;printf("  GPS time = %02d:%02d:%02d\n", ubx_gps_hou, ubx_gps_min, ubx_gps_sec);}
#endif} else { /* valid */gps_time_ok = false;}return UBX_NAV_TIMEGPS;} else if ((serial_buff[2] == 0x05) && (serial_buff[3] == 0x00)) {DEBUG_MSG("NOTE: UBX ACK-NAK received\n");return IGNORED;} else if ((serial_buff[2] == 0x05) && (serial_buff[3] == 0x01)) {DEBUG_MSG("NOTE: UBX ACK-ACK received\n");return IGNORED;} else { /* not a supported message */DEBUG_MSG("ERROR: UBX message is not supported (%02x %02x)\n", serial_buff[2], serial_buff[3]);return IGNORED;}} else { /* checksum failed */DEBUG_MSG("ERROR: UBX message is corrupted, checksum failed\n");return INVALID;}} else { /* message contains less bytes than indicated by header */DEBUG_MSG("ERROR: UBX message incomplete\n");return INCOMPLETE;}} else { /* Not a UBX message *//* Ignore messages which are not UBX ones for now */return IGNORED;}
}

2.5 gps_process_sync 函数功能

代码路径在 lora_pkt_fwd.c 文件中

static void gps_process_sync(void) {struct timespec gps_time;struct timespec utc;uint32_t trig_tstamp; /* concentrator timestamp associated with PPM pulse */int i = lgw_gps_get(&utc, &gps_time, NULL, NULL);                      //>第一处 转换 gps 时间/* get GPS time for synchronization */if (i != LGW_GPS_SUCCESS) {MSG("WARNING: [gps] could not get GPS time from GPS\n");return;}/* get timestamp captured on PPM pulse  */pthread_mutex_lock(&mx_concent);i = lgw_get_trigcnt(&trig_tstamp);                                    //> 第二处 获取网关内部计时器时间pthread_mutex_unlock(&mx_concent);if (i != LGW_HAL_SUCCESS) {MSG("WARNING: [gps] failed to read concentrator timestamp\n");return;}/* try to update time reference with the new GPS time & timestamp */pthread_mutex_lock(&mx_timeref);i = lgw_gps_sync(&time_reference_gps, trig_tstamp, utc, gps_time);   //> 第三处 修正网关内部计时器pthread_mutex_unlock(&mx_timeref);if (i != LGW_GPS_SUCCESS) {MSG("WARNING: [gps] GPS out of sync, keeping previous time reference\n");}
}

第3节 gps本地校时

在GPS线程中调用 gps_process_sync() 函数进行校时,函数中分别调用 lgw_gps_get()、lgw_get_trigcnt()和 lgw_gps_sync() 函数,
完成本地时间同步,函数中 trig_tstamp 和 time_reference_gps 是全局变量,通过获取gps时间进行校正这两个变量内容。

3.1 把 gps 时间转换为UTC时间

在函数 lgw_gps_get() 中把gps时间转换成 UTC 时间, 着重关注 utc->tv_sec 与 utc->tv_nsec 数据.

int lgw_gps_get(struct timespec *utc, struct timespec *gps_time, struct coord_s *loc, struct coord_s *err) {struct tm x;time_t y;double intpart, fractpart;if (utc != NULL) {if (!gps_time_ok) {DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n");return LGW_GPS_ERROR;}memset(&x, 0, sizeof(x));if (gps_yea < 100) { /* 2-digits year, 20xx */x.tm_year = gps_yea + 100; /* 100 years offset to 1900 */} else { /* 4-digits year, Gregorian calendar */x.tm_year = gps_yea - 1900;}x.tm_mon = gps_mon - 1; /* tm_mon is [0,11], gps_mon is [1,12] */x.tm_mday = gps_day;x.tm_hour = gps_hou;x.tm_min = gps_min;x.tm_sec = gps_sec;//> 转换出 utc->tv_sec utc->tv_nsecy = mktime(&x) - timezone; /* need to substract timezone bc mktime assumes time vector is local time */if (y == (time_t)(-1)) {DEBUG_MSG("ERROR: FAILED TO CONVERT BROKEN-DOWN TIME\n");return LGW_GPS_ERROR;}utc->tv_sec = y;utc->tv_nsec = (int32_t)(gps_fra * 1e9);}if (gps_time != NULL) {if (!gps_time_ok) {DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n");return LGW_GPS_ERROR;}//> 周内秒转换fractpart = modf(((double)gps_iTOW / 1E3) + ((double)gps_fTOW / 1E9), &intpart);/* Number of seconds since beginning on current GPS week */gps_time->tv_sec = (time_t)intpart;/* Number of seconds since GPS epoch 06.Jan.1980 */gps_time->tv_sec += (time_t)gps_week * 604800; /* day*hours*minutes*secondes: 7*24*60*60; *//* Fractional part in nanoseconds */gps_time->tv_nsec = (long)(fractpart * 1E9);}if (loc != NULL) {if (!gps_pos_ok) {DEBUG_MSG("ERROR: NO VALID POSITION TO RETURN\n");return LGW_GPS_ERROR;}loc->lat = ((double)gps_dla + (gps_mla/60.0)) * ((gps_ola == 'N')?1.0:-1.0);loc->lon = ((double)gps_dlo + (gps_mlo/60.0)) * ((gps_olo == 'E')?1.0:-1.0);loc->alt = gps_alt;}if (err != NULL) {DEBUG_MSG("Warning: localization error processing not implemented yet\n");err->lat = 0.0;err->lon = 0.0;err->alt = 0;}return LGW_GPS_SUCCESS;
}

同时把gps周时间 gps_iTOW 和 gps_fTOW 值转换成 gps_time->tv_sec 和 gps_time->tv_nsec 绝对时间。

3.2 网关参考时钟

int lgw_get_trigcnt(uint32_t* trig_cnt_us) {DEBUG_PRINTF(" --- %s\n", "IN");CHECK_NULL(trig_cnt_us);*trig_cnt_us = sx1302_timestamp_counter(true);DEBUG_PRINTF(" --- %s\n", "OUT");return LGW_HAL_SUCCESS;
}

函数调用关系如下:
lgw_get_trigcnt()
==>sx1302_timestamp_counter(true);
==>timestamp_counter_get(&counter_us, &inst_cnt, &pps_cnt);
函数入口参数 counter_us 是全局变量、从 SX1302 内部寄存器中读取的 LoRa 信号 TOA 时间, inst_cnt 和 pps_cnt 临时变量;
最终调用函数内容如下:

int timestamp_counter_get(timestamp_counter_t * self, uint32_t * inst, uint32_t * pps) {int x;uint8_t buff[8];uint8_t buff_wa[8];uint32_t counter_inst_us_raw_27bits_now;uint32_t counter_pps_us_raw_27bits_now;/* Get the freerun and pps 32MHz timestamp counters - 8 bytes0 -> 3 : PPS counter4 -> 7 : Freerun counter (inst)*/x = lgw_reg_rb(SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS, &buff[0], 8);if (x != LGW_REG_SUCCESS) {printf("ERROR: Failed to get timestamp counter value\n");return -1;}/* Workaround concentrator chip issue:- read MSB again- if MSB changed, read the full counter again*/x = lgw_reg_rb(SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS, &buff_wa[0], 8);if (x != LGW_REG_SUCCESS) {printf("ERROR: Failed to get timestamp counter MSB value\n");return -1;}if ((buff[0] != buff_wa[0]) || (buff[4] != buff_wa[4])) {x = lgw_reg_rb(SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS, &buff_wa[0], 8);if (x != LGW_REG_SUCCESS) {printf("ERROR: Failed to get timestamp counter MSB value\n");return -1;}memcpy(buff, buff_wa, 8); /* use the new read value */}//> 从 sx1302 中读取的 32MHz 时钟数量counter_pps_us_raw_27bits_now  = (buff[0]<<24) | (buff[1]<<16) | (buff[2]<<8) | buff[3];counter_inst_us_raw_27bits_now = (buff[4]<<24) | (buff[5]<<16) | (buff[6]<<8) | buff[7];/* Store PPS counter to history, for fine timestamp calculation,时间戳存储至全局 timestap_pps_history 中 */timestamp_pps_history_save(counter_pps_us_raw_27bits_now);/* Scale to 1MHz, 转换成 1MHz 时钟 */counter_pps_us_raw_27bits_now /= 32;counter_inst_us_raw_27bits_now /= 32;/* Update counter wrapping status */timestamp_counter_update(self, counter_pps_us_raw_27bits_now, counter_inst_us_raw_27bits_now);/* Convert 27-bits counter to 32-bits counter */*inst = timestamp_counter_expand(self, false, counter_inst_us_raw_27bits_now);*pps  = timestamp_counter_expand(self, true, counter_pps_us_raw_27bits_now);return 0;
}

把27bit us 时间转换为 32bit us 时间, 统一时间位数。

void timestamp_counter_update(timestamp_counter_t * self, uint32_t pps, uint32_t inst) {//struct timestamp_info_s* tinfo = (pps == true) ? &self->pps : &self->inst;/* Check if counter has wrapped, and update wrap status if necessary */if (pps < self->pps.counter_us_27bits_ref) {self->pps.counter_us_27bits_wrap += 1;self->pps.counter_us_27bits_wrap %= 32;}if (inst < self->inst.counter_us_27bits_ref) {self->inst.counter_us_27bits_wrap += 1;self->inst.counter_us_27bits_wrap %= 32;}/* Update counter reference */self->pps.counter_us_27bits_ref = pps;self->inst.counter_us_27bits_ref = inst;
}

此部分程序实现了两件事情、保存32MHz时间到 timestap_pps_history 全局变量中、统一时间位数并更新 counter_us 时间。

3.3 本地时间同步

lgw_gps_sync(&time_reference_gps, trig_tstamp, utc, gps_time); 参数 time_reference_gps 和 trig_tstamp 全局变量,


#define TS_CPS              1E6 /* count-per-second of the timestamp counter */
#define PLUS_10PPM          1.00001
#define MINUS_10PPM         0.99999int lgw_gps_sync(struct tref *ref, uint32_t count_us, struct timespec utc, struct timespec gps_time) {double cnt_diff;   /* internal concentrator time difference (in seconds) */double utc_diff;   /* UTC time difference (in seconds) */double slope;      /* time slope between new reference and old reference (for sanity check) */bool aber_n0;     /* is the update value for synchronization aberrant or not ? */static bool aber_min1 = false; /* keep track of whether value at sync N-1 was aberrant or not  */static bool aber_min2 = false; /* keep track of whether value at sync N-2 was aberrant or not  */CHECK_NULL(ref);/* calculate the slope, 参考时钟有 本地时钟、utc 和 gps 时钟数 *///> 计算出本地时钟与参考时钟间差,cnt_diff = (double)(count_us - ref->count_us) / (double)(TS_CPS); /* uncorrected by xtal_err */utc_diff = (double)(utc.tv_sec - (ref->utc).tv_sec) + (1E-9 * (double)(utc.tv_nsec - (ref->utc).tv_nsec));/* detect aberrant points by measuring if slope limits are exceeded */if (utc_diff != 0) { // prevent divide by zeroslope = cnt_diff/utc_diff;if ((slope > PLUS_10PPM) || (slope < MINUS_10PPM)) {DEBUG_MSG("Warning: correction range exceeded\n");aber_n0 = true;} else {aber_n0 = false;}} else {DEBUG_MSG("Warning: aberrant UTC value for synchronization\n");aber_n0 = true;}/* watch if the 3 latest sync point were aberrant or not */if (aber_n0 == false) {/* value no aberrant -> sync with smoothed slope *///> 时钟未同步、同步参考时钟ref->systime = time(NULL);ref->count_us = count_us;ref->utc.tv_sec = utc.tv_sec;ref->utc.tv_nsec = utc.tv_nsec;ref->gps.tv_sec = gps_time.tv_sec;ref->gps.tv_nsec = gps_time.tv_nsec;ref->xtal_err = slope;aber_min2 = aber_min1;aber_min1 = aber_n0;return LGW_GPS_SUCCESS;} else if (aber_n0 && aber_min1 && aber_min2) {/* 3 successive aberrant values -> sync reset (keep xtal_err) * 同步本地时钟*/ref->systime = time(NULL);ref->count_us = count_us;ref->utc.tv_sec = utc.tv_sec;ref->utc.tv_nsec = utc.tv_nsec;ref->gps.tv_sec = gps_time.tv_sec;ref->gps.tv_nsec = gps_time.tv_nsec;/* reset xtal_err only if the present value is out of range */if ((ref->xtal_err > PLUS_10PPM) || (ref->xtal_err < MINUS_10PPM)) {ref->xtal_err = 1.0;}DEBUG_MSG("Warning: 3 successive aberrant sync attempts, sync reset\n");aber_min2 = aber_min1;aber_min1 = aber_n0;return LGW_GPS_SUCCESS;} else {/* only 1 or 2 successive aberrant values -> ignore and return an error */aber_min2 = aber_min1;aber_min1 = aber_n0;return LGW_GPS_ERROR;}return LGW_GPS_SUCCESS;
}

总结

此篇 thread_gps 线程读取gps同步时间 和 NMEA 信息内容,并同步网关内部计时器。

如果本篇文章对您有所启发或帮助、请给笔者点赞助力、鼓励笔者坚持把此系列内容尽快梳理、分享出来。
谢谢。

相关文章:

无线定位之 三 SX1302 网关源码 thread_gps 线程详解

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

Kubernetes控制平面组件:Kubelet详解(一):API接口层介绍

云原生学习路线导航页&#xff08;持续更新中&#xff09; kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计&#xff08;一&#xff09;Kubernetes架构原则和对象设计&#xff08;二&#xff09;Kubernetes架构原则和对象设计&#xff08;三&#xff09;Kubernetes控…...

Java项目层级介绍 java 层级 层次

java 层级 层次 实体层 控制器层 数据连接层 Service : 业务处理类 Repository &#xff1a;数据库访问类 Java项目层级介绍 https://blog.csdn.net/m0_67574906/article/details/145811846 在Java项目中&#xff0c;层级结构&#xff08;Layered Architecture&#xf…...

【操作系统】零拷贝技术

1. DMA技术 DMA技术也就是直接内存访问技术。在进行I/O设备和内存的数据传输的时候&#xff0c;数据传输的工作全部交给DMA控制器&#xff0c;而不是CPU负责。 2. 传统的文件传输 传统的文件传输的代码如下&#xff1a; read(file, tmp_buf, len); write(socket, tmp_buf,…...

从零构建高性能桌面应用:GPUI Component全解析与实战指南

简介 高性能UI组件库正在重塑桌面应用开发的格局,而GPUI Component作为新兴的Rust桌面UI组件库,凭借其卓越的跨平台支持能力、GPU加速渲染和企业级功能特性,正成为构建现代化高性能桌面应用的首选。本文将从零开始,全面解析GPUI Component的核心特性、安装配置流程,并通过…...

java 中 DTO 和 VO 的核心区别

DTO 和 VO 的核心区别 特性DTO&#xff08;数据传输对象&#xff09;VO&#xff08;视图对象&#xff09;设计目的服务层与外部系统&#xff08;如前端、其他服务&#xff09;之间的数据传输为前端展示层定制数据&#xff0c;通常与 UI 强绑定数据内容可能包含业务逻辑需要的字…...

bazel迁移cmake要点及具体迁移工程示例(apollo radar)

文章目录 bazel迁移cmake要点及具体迁移工程示例迁移要点指南依赖库管理proto编译目标库及二进制文件生成项目导出runtime_data软件打包 conti_rardar完整迁移过程common_msgcommoncanbusconti_radar编译 bazel迁移cmake要点及具体迁移工程示例 迁移要点指南 迁移主要的内容包…...

跨时钟域(CDC,clock domain crossing)信号处理

参考视频&#xff1a; 数字IC&#xff0c;FPGA秋招【单bit信号的CDC跨时钟域处理手撕代码合集】_哔哩哔哩_bilibili 一、亚稳态 原因是&#xff1a;建立时间和保持时间没有保持住。然后在下图的红框里面&#xff0c;产生亚稳态。因为电路反馈机制&#xff0c;最后大概率会恢复…...

Java高频面试之并发编程-16

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天又来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;volatile 实现原理是什么&#xff1f; volatile 关键字的实现原理 volatile 是 Java 中用于解决多线程环境下变量可见性…...

MyBatis 一对多关联映射在Spring Boot中的XML配置

在Spring Boot中使用MyBatis实现一对多关系时&#xff0c;可以通过XML映射文件来配置。下面我将详细介绍几种实现方式。 基本概念 一对多关系指的是一个实体对象包含多个子对象集合的情况&#xff0c;例如&#xff1a; 一个部门有多个员工一个订单有多个订单项一个博客有多个…...

Spring Boot异步任务失效的8大原因及解决方案

Spring Boot异步任务失效的8大原因及解决方案 摘要:在使用Spring Boot的@Async实现异步任务时,你是否遇到过异步不生效的问题?本文总结了8种常见的异步失效场景,并提供对应的解决方案,帮助你彻底解决异步任务失效的难题。 一、异步失效的常见场景 1. 未启用异步支持 ❌ …...

嵌入式Linux I2C驱动开发详解

文章目录 一、I2C协议的核心原理 1. I2C协议的“双线制”通信 2. I2C的地址寻址机制 二、Linux I2C驱动框架的分层设计 1. I2C核心 2. I2C适配器 3. I2C设备驱动 三、I2C驱动开发测试 一、I2C协议的核心原理 1. I2C协议的“双线制”通信 SDA&#xff08;数据线&#xff09; 和…...

基于 Spring Boot 瑞吉外卖系统开发(十二)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;十二&#xff09; 菜品删除 单击“批量删除”和“删除”时&#xff0c;会携带需要删除的菜品的id以delete请求方式向“/dish”发送请求。 URLhttp://127.0.0.1:8080/dish调用方法DELETE参数ids DishController添加删除方法 …...

基于 51 单片机的 PWM 电机调速系统实现

51 单片机通过定时器来实现 PWM(脉冲宽度调制)控制,这是一种通过调整方波信号的占空比来控制输出功率的技术。下面详细介绍如何使用 51 单片机实现 PWM 控制并应用于电机调速。 1. PWM 控制原理 PWM 信号是一个周期性的方波,通过调整高电平时间(Ton)与周期(T)的比例(…...

wordpress主题分享

发布于&#xff1a;Eucalyptus-Blog 一、引言 大家对WordPress免费主题都非常感兴趣&#xff0c;但是一般收费的WordPress主题功能才多&#xff0c;其实有很多开源的免费WordPress主题都很不错&#xff0c;对于很多新建站的小伙伴&#xff0c;这些主题完全足够用了。这里就分享…...

【合新通信】无人机天线拉远RFOF(射频光纤传输)解决方案

无人机天线拉远RFOF方案通过光纤替代传统射频电缆&#xff0c;实现无人机与地面控制站之间的高保真、低损耗信号传输&#xff0c;尤其适用于高频段&#xff08;如毫米波&#xff09;、远距离或复杂电磁环境下的无人机作业场景。 核心应用场景 军事侦察与电子战 隐蔽部署&…...

视觉-语言-动作模型:概念、进展、应用与挑战(下)

25年5月来自 Cornell 大学、香港科大和希腊 U Peloponnese 的论文“Vision-Language-Action Models: Concepts, Progress, Applications and Challenges”。 视觉-语言-动作 (VLA) 模型标志着人工智能的变革性进步&#xff0c;旨在将感知、自然语言理解和具体动作统一在一个计…...

git如何将本地 dev 分支与远程 dev 分支同步

要让本地 dev 分支与远程 dev 分支完全同步&#xff08;丢弃本地多余的提交记录&#xff09;&#xff0c;可以按照以下步骤操作&#xff1a; 1. ​​获取远程最新状态​ git fetch origin dev # 拉取远程 dev 分支的最新提交&#xff0c;但不会修改本地代码 IDEA中点击fetc…...

自动化测试与功能测试详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 什么是自动化测试? 自动化测试是指利用软件测试工具自动实现全部或部分测试&#xff0c;它是软件测试的一个重要组成 部分&#xff0c;能完成许多手工测试无…...

python3数据类型

常见python3数据类型 Number&#xff08;数字&#xff09;String&#xff08;字符串&#xff09;bool&#xff08;布尔类型&#xff09;List&#xff08;列表&#xff09;Tuple&#xff08;元组&#xff09;Set&#xff08;集合&#xff09;Dictionary&#xff08;字典&#x…...

《Python星球日记》 第66天:序列建模与语言模型

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、传统语言模型1. n-gram 模型基础2. n-gram 模型的局限性 二、RNN 在语言建模…...

CRM和SCRM有什么区别

CRM&#xff08;客户关系管理&#xff09;与SCRM&#xff08;社会化客户关系管理&#xff09;的主要区别在于沟通方式、客户互动、数据来源和客户参与度。CRM更侧重企业对客户信息的管理与内部流程优化&#xff0c;而SCRM强调客户主动参与&#xff0c;通过社交媒体等平台加强互…...

破解 Qt QProcess 在 Release 模式下的“卡死”之谜

在使用 Qt 的 QProcess 以调用外部 ffmpeg/ffprobe 进行音视频处理时&#xff0c;常见的工作流程是&#xff1a; gatherParams&#xff1a;通过 ffprobe 同步获取媒体文件的参数&#xff08;分辨率、采样率、声道数、码率等&#xff09;。 reencode&#xff1a;逐个文件调用 f…...

重学安卓14/15自由窗口freeform企业实战bug-学员作业

背景&#xff1a; 在aosp14版本及以后版本上&#xff0c;安卓的自由窗口部分的工具栏部分都有了较大的更新 工具栏这部分已经移到了SystemUI进程的WMShell进行统一的管理。 但是近来有学员朋友在对自由窗口进行相关的拖拽操作时候&#xff0c;有遇到这个工具栏相关的一个bug&…...

【layout组件 与 路由镶嵌】vue3 后台管理系统

前言 很多同学在第一次搭建后台管理系统时&#xff0c;会遇到一个问题&#xff0c;layout组件该放哪里&#xff1f;如何使用&#xff1f;路由又该如何设计&#xff1f; 这边会讲一下我的思考过程和最后的结果&#xff0c;大家可以参考一下&#xff0c;希望大家看完能有所收获。…...

【PXIE301-211】基于PXIE总线的16路并行LVDS数据采集、1路光纤数据收发处理平台

板卡概述 PXIE301-211是一款基于PXIE总线架构的16路并行LVDS数据采集、1路光纤收发处理平台&#xff0c;该板卡采用Xilinx的高性能Kintex 7系列FPGA XC7K325T作为实时处理器&#xff0c;实现各个接口之间的互联。板载1组64位的DDR3 SDRAM用作数据缓存。板卡具有1个FMC&#xf…...

linux-进程信号的产生

Linux中的进程信号&#xff08;signal&#xff09;是一种用于进程间通信或向进程传递异步事件通知的机制。信号是一种软中断&#xff0c;用于通知进程某个事件的发生&#xff0c;如错误、终止请求、计时器到期等。 1. 信号的基本概念 - 信号&#xff08;Signal&#xff09;&am…...

基于 Nexus 在 Dockerfile 配置 yum, conda, pip 仓库的方法和参考

在 Nexus 配置代理仓库的方法&#xff0c;可参考 pypi 的配置博客&#xff1a;https://hellogitlab.com/CI/docker/create_your_nexus_2 更多代理格式&#xff0c;参考官方文档&#xff0c;如 pypi&#xff1a;https://help.sonatype.com/en/pypi-repositories.html 配置 yum…...

精品可编辑PPT | 全面风险管理信息系统项目建设风控一体化标准方案

这份文档是一份全面风险管理信息系统项目建设风控一体化标准方案&#xff0c;涵盖了业务架构、功能方案、系统技术架构设计、项目实施及服务等多个方面的详细内容。方案旨在通过信息化手段提升企业全面风险管理工作水平&#xff0c;促进风险管理落地和内部控制规范化&#xff0…...

Redis集群安装

Redis集群安装 1.集群介绍 首先要了解&#xff0c;Redis的高可用机制。 2个master节点&#xff0c;挂掉1个&#xff0c;1不过半&#xff0c;则集群宕机&#xff0c;不可用&#xff0c;容错率为0&#xff1b;3个master节点&#xff0c;挂掉1个&#xff0c;2>1&#xff0c;…...

下载知网外文文献全文的方法

知网和一些外文数据库机构是合作关系&#xff0c;因知网没有订购外文文献全文&#xff0c;所以可以搜到外文文献但不能下载全文&#xff0c;基本提供的都是外文文献摘要。本文就实例演示一下获取知网外文文献全文的方法步骤。 例如下面这篇知网外文文献&#xff0c;该文献被收…...

解决IDEA无法运行git的问题

之前git一直没有问题&#xff0c;今天打开就提示我安装git&#xff0c;自然用git去提交新项目也会遇到问题。 我出现问题的原因是&#xff1a;git路径缺失 文件->设置->git 发现git的路径为空&#xff0c;按照实际位置填写即可...

基于Qt6 + MuPDF在 Arm IMX6ULL运行的PDF浏览器——MuPDF Adapter文档

项目地址&#xff1a;总项目Charliechen114514/CCIMXDesktop: This is a Qt Written Desktop with base GUI Utilities 本子项目地址&#xff1a;CCIMXDesktop/extern_app/pdfReader at main Charliechen114514/CCIMXDesktop 前言 这个部分说的是Mupdf_adaper下的文档的工…...

Ubuntu20.04 搭建Kubernetes 1.28版本集群

环境依赖 以下操作,无特殊说明,所有节点都需要执行 安装 ssh 服务安装 openssh-server复制代码 sudo apt-get install openssh-server修改配置文件复制代码 vim /etc/ssh/sshd_config找到配置项 复制代码 LoginGraceTime 120 PermitRootLogin prohibit-password StrictModes…...

操作系统和数据库账号密码的安全管理、使用,安当SMS凭据管理系统

引言&#xff1a;密码管理困局下的破局之道 在数字化转型的深水区&#xff0c;企业正面临前所未有的密码管理挑战。某跨国制造企业因数据库密码泄露导致核心工艺参数外泄&#xff0c;某三甲医院因运维账号滥用引发百万级医疗数据泄露事件&#xff0c;这些真实案例揭示着传统密…...

Java设计模式之代理模式:从入门到精通(保姆级教程)

1. 代理模式概述 代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,可以在不改变目标对象代码的情况下增加额外的功能。 1.1 专业概念解释 代理模式:为其他对象提供一种代理…...

单片机-STM32部分:13-1、蜂鸣器

飞书文档https://x509p6c8to.feishu.cn/wiki/V8rpwIlYIiEuXLkUljTcXWiKnSc 一、应用场景 大部分的电子产品、家电&#xff08;风扇、空调、电水壶&#xff09;都会有蜂鸣器&#xff0c;用于提示设备的工作状态 二、原理 蜂鸣器是一种将电信号转换为声音信号的器件&#xff0…...

JVM——方法内联

引入 在现代软件开发中&#xff0c;性能优化始终是一个关键课题。随着硬件架构的不断演进&#xff0c;CPU的主频提升逐渐放缓&#xff0c;而软件复杂度却持续增加&#xff0c;这使得编译器优化技术的重要性日益凸显。方法内联&#xff08;Method Inlining&#xff09;作为编译…...

C++类成员

一、内联函数&#xff08;Inline Functions&#xff09; 作用 解决频繁调用小函数时的栈内存消耗问题&#xff0c;通过将函数代码直接插入调用点&#xff0c;避免压栈/出栈开销。 定义形式 inline 返回类型 函数名(参数列表) { ... }• 隐式声明&#xff1a;类内直接定义的成员…...

SpringBoot校园失物招领信息平台

SpringBoot校园失物招领信息平台 文章目录 SpringBoot校园失物招领信息平台1、技术栈2、项目说明2.1、登录注册2.2、管理员端截图2.3、用户端截图 3、核心代码实现3.1、前端首页3.2、前端招领广场3.3、后端业务处理 1、技术栈 本项目采用前后端分离的架构&#xff0c;前端和后…...

代码随想录算法训练营第三十八天

LeetCode题目: 1143. 最长公共子序列1035. 不相交的线53. 最大子数组和392. 判断子序列2094. 找出 3 位偶数(每日一题) 其他: 今日总结 往期打卡 1143. 最长公共子序列 跳转: 1143. 最长公共子序列 学习: 代码随想录公开讲解 问题: 给定两个字符串 text1 和 text2&#xff0…...

Nginx stream模块是连接级别的负载均衡

在Nginx的stream模块中&#xff0c;upstream的权重配置实现的是连接级别的负载均衡&#xff0c;这和http模块不同。 当客户端发起一个新的TCP连接时&#xff0c;Nginx根据各upstream的权重值选择其中一个upstream建立连接&#xff0c;之后该连接上的所有数据传输都由这个upstre…...

贝叶斯算法

贝叶斯算法是一类基于贝叶斯定理的机器学习算法&#xff0c;它们在分类任务中表现出色&#xff0c;尤其在处理具有不确定性和 probabilistic 关系的数据时具有独特优势。本文将深入探讨贝叶斯算法的核心原理、主要类型以及实际应用案例&#xff0c;带你领略贝叶斯算法在概率推理…...

计算机网络:CPU与时钟的关系

在计算机中,CPU(中央处理器)与时钟的关系是核心且密不可分的。时钟信号是驱动CPU运行的“心跳”,决定了计算机执行指令的节奏和协调性。以下是两者的关键关系及作用: 1. 时钟信号:CPU的“节拍器” 时钟频率(Clock Speed) CPU的时钟频率(如3.5 GHz)表示每秒的时钟周期…...

java中强引用、软应用、弱应用、虚引用

在Java中&#xff0c;引用类型决定了对象的生命周期和垃圾回收的时机。Java提供了四种不同的引用类型&#xff1a;强引用、软引用、弱引用和虚引用。每种引用类型的行为和用途不同&#xff0c;了解这些差异对优化内存管理和垃圾回收非常重要。 1. 强引用&#xff08;Strong Re…...

分析红黑树工程实用的特点

&#x1f9ed; 本节目标 理解红黑树在工程中的优劣势对比红黑树与其他数据结构&#xff08;AVL 树、跳表、哈希表等&#xff09;分析红黑树为何成为内核级应用&#xff08;如 Linux CFS、内存管理&#xff09;首选总结红黑树工程上的典型使用建议 一、红黑树工程级使用的主要特…...

C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )

一、引言&#xff1a;理解内存管理的核心价值 在系统级编程领域&#xff0c;内存管理是决定程序性能、稳定性和安全性的关键因素。C/C 作为底层开发的主流语言&#xff0c;赋予开发者直接操作内存的能力&#xff0c;却也要求开发者深入理解内存布局与生命周期管理。本文将从内…...

如何使用主机名在 CMD 中查找 IP 地址?

在网络中,每个系统都有一个由几位数字组成的唯一标识,称为 IP 地址。然而,记住它们可能是一项艰巨的任务,尤其是当系统数量众多时。例如,互联网上运行的每个网站都有一个 IP 地址,以便其他系统在需要时可以调用它们,但你认为记住我们访问的每个网站的长串数字是可行的吗…...

解读RTOS:第二篇 · 线程/任务管理与调度策略

1. 引言 在 RTOS 中,线程(Task)是最基本的执行单元,它封装了应用功能、资源使用和优先级属性。任务管理与调度策略决定了系统在多任务场景下的响应速度、资源分配效率与实时性保证。理解并掌握任务创建、状态转换、优先级设计和调度算法,是 RTOS 应用开发的核心内容。 2…...

linux下minio的进程管理脚本

准备工作&#xff1a; 参考链接&#xff1a; Deploy MinIO: Single-Node Single-Drive — MinIO Object Storage for Linux 下载&#xff1a; wget https://dl.min.io/server/minio/release/linux-amd64/minio kill-app.sh #!/bin/bash # 文件名&#xff1a; kill-app.sh…...