1.Main函数(main.c)

a) gpio_init(); //初始化全部IO口为初始状态(输入、关上拉、关复用)

b) pre_init(); //初始化时钟与RTC0并进行待机,待RTC0中断唤醒。用此方式待电源稳定

c) init1(); //初始化LED、MOTOR、TIMER、ADC、G-SENSOR等硬件。初始化TIMER、系统调度等相关服务

d) 检测是否在充电状态,如果是则关闭系统

e) bootup_check();//启动检测,点亮全部LED。如果未进行过出厂测试,则进行出厂自检

f) reset_check(); //复位检测,在这里代码只是简单输出信息。如打开了INTERNAL_DEV宏,则为内部设备,进行FLASH删除

g) init2(); //初始化看门狗,定时器服务创建,外部中断服务(gpiote_int_init())初始化,系统时间初始化,bond管理器初始化,蓝牙协议栈初始化,蓝牙服务创建,打开G-SENSOR

h) 读取闹铃、用户信息、用户目标、运行/睡眠数据

2.蓝牙通信 (bd_communicate_protocol.c)

a) void L1_receive_data(ble_nus_t * p_nus, uint8_t * data, uint16_t length)

L1_receive_data()为蓝牙通信接收的入口函数,该函数为蓝牙UART服务的接收数据回调函数。

通过数据包长度字段判断是否接收完整一个包(一帧或多帧),接收完成后调用L1_crc_check函数校验CRC是否正确。如果CRC正确则调用L1_receive_response返回ACK,并调用L2_frame_resolve解释数据包。如CRC校验错误,则调用L1_receive_response返回NAC,并调试schedule_crc_error_handle(这个任务只是简单输出信息)。

b) uint32_t L2_frame_resolve(uint8_t * data, uint16_t length,RECEIVE_STATE * resolve_state)

当接收完整的数据包并且CRC校验正确,就会进入该函数中解释数据包。程序首先判断是否有手环已绑定。未绑定状态下,只可以响应绑定、工厂测试、固件升级、输出日志的命令。绑定状态下可响应全部命令。

c) uint32_t L1_send(L2_Send_Content * content)

数据的发送通过调用L1_send实现,该函数的参数为发送数据包的指针函数。如在return_alarm_list()中返回的闹钟列表命令,通过填充全局变量global_reponse_buffer来实现发送数据的链接。在L1_send()函数里再使用全局变量global_L1_header_buffer封装成完成的数据包。最后调用schedule_async_send()调度发送服务。发送部分比较复杂,后面将单独分析。

3.发送数据

a) void send_all_data(bool is_from_cb)

该函数为上传数据的唯一接口,函数里面先启动发送数据定时服务。然后依次检测是否有数据,检测顺序为sleep in flash > sleep  in ram > sleep setting in ram > sport in flash > sport in ram,如有数据则调用相应的send函数,如send_sleep_data_in_ram()。

该函数进入时判断是否为绑定状态,非绑定状态下不传数据。接着判断sending_flag是否为true,如果为true说明正在发送数据,则启动发送服务定时器,达到定时器服务中断后再重试的效果。

b) void send_data_timeout(void * val)

定时发送服务函数,该定时服务的定时时间为5000MS。里面所做的处理就是重复调用send_all_data()去发送数据。

c) void send_all_data_callback(SEND_STATUS status)

该函数为发送数据的回调函数,在低层数据发送完成后会调用该回调函数进行发送错误处理,或更新发送缓存处理。

4.L1层数据传输

5.定时器服务

duBand使用的定时器服务是NRF51 SDK里面提供的APP定时器服务接口,该服务使用RTC中断作为APP定时器中断,通过SWI0(app_timer_start()与app_timer_stop())中断调用LIST TIMER HANDING来设置定时器状态。

a) void timer_init()

在main()-->init1()中调用该函数初始化APP定时器,主要是设置APP定时器的链表。

b) void timers_create(void)

在main()-->init2()中调用该函数建立APP定时器,其实就是填充各APP定时器链表。

c) void RTC1_IRQHandler(void)

RTC1中断,为1ms一次的中断。在timer_init()中初始化该中断。在该中断中主要调用timer_timeouts_check()遍历APP定时器链表。

d) void SWI0_IRQHandler(void)

软件中断0。主要用于更新定时器状态,要启动、停止定时器服务时会产生此中断。

e) void timer_timeouts_check(void)

在RTC1中断中调用,主要作定时定时值自减。

f) void timer_list_handler(void)

更新定时器状态。在SWI0中断中调用。

g) uint32_t app_timer_start(...)

启动APP定时器。

h) uint32_t app_timer_stop(app_timer_id_t timer_id)

停止APP定时器。

i) void RTC0_IRQHandler(void)

RTC0中断,在启动时的初始化用作wakeup中断,程序运行过程中没有再使用。

6.任务/事件调度器

duBand使用的任务调度器是NRF51 SDK里面提供的服务接口,该调试器与定时器服务差不多,只是不需要中断去触发/参与,任务的调试函数直接放在了main()的while(1)循环中。调度器利用的事件触发的方式。

a) void scheduler_init(void)

在main()-->init1()中调用该函数初始化APP调度器,主要是设置APP调度器数组信息。

b) void app_sched_execute(void)

在main()的while(1)循环中调用,用于判断事件缓冲(数组)中是否有要执行的任务,有则调用注册时的handler函数。

c) uint32_t app_sched_event_put(...)

调用该函数注册事件。输入的参数包含事件的处理函数。

7.RTC

RTC通过一个1S的定时器服务(ID为wallClockID)处理。每定时中断一次,SecondCountRTC自加1。RTC时钟最终通过函数将SecondCountRTC转换为时间值。

a) void system_clock_init(void)

在main()-->init2()调用该函数初始化RTC时钟(实际是注册1S的定时器服务)与初始化全局时钟数据。最后启动1S定时器服务,开始计算时间。

b) void update_wall_clock(void * p_context)

1S定时器服务函数。该函数中SecondCountRTC自加1。并处理动作/睡眠数据。闹钟的检测也是在这里调用。

8.Notification(通知服务)

duBand的通知包括有LED和MOTOR。分别由ID为flash_schedule_timer与motor_schedule_timer的定时器服务控制。这两个定时器服务在main()--->init2()--->timers_create()中创建。

a) void notification_start( uint8_t type, uint16_t value )

通过调用该函数启动相应通知。通过已定义的通知类型用LED与MOTOR将当前通知表现出来。

b) void notification_stop( )

通过该函数停止LED和MOTOR通知。

c) void led_schedule_timer_handle(void * context)

LED定时器服务处理函数。里面的操作也主要是通过映射表进行操作。

d) void motor_schedule_timer_handle(void * context)

MOTOR定时器服务处理函数。里面的操作也主要是通过映射表进行操作。

9.运动数据采样

a) void restart_health_algorithm(void)

运动算法的由restart_health_algorithm()函数启动。最初调用此函数是在main()函数中的init2()之后。但要执行到这步,必须restore_backup_info_from_flash()的返回值要为NRF_SUCCESS。

b) uint32_t restore_backup_info_from_flash(void)

该函数先验证FLASH_PAGE_STORE_TIME地址的信息是否正确,从名字来看这里存放的应该是时间的信息。由于我的设备中未进行时间的设备,因此这个函数发现CRC错误,就直接返回ERROR。如果需要restart_health_algorithm()能被调用,也可以通过命令去设置设备的时间从而启动运动数据采集线程,注意的是要登录状态下才可以设置时间。

设置时间后,如果检测到为已绑定的设备,则会调用restart_health_algorithm()启动数据检测。打开DEBUG_LOG,可看到有相关的数据输出。quarter_steps为本周期(15分钟)的计步数,global_step_counts_today为当天的总计步数。

.

c) uint8_t start_health_algorithm(userprofile_union_t *user_profile)

启动运动检测算法,在restart_health_algorithm()中调用,该函数输入参数为用户信息,函数开始先检测用户输入信息是否合法,如不合法则赋默认值 。接着调用算法初始化函数init_health_algorithm(...)初始化Baidu算法,init_health_algorithm(...)的输入参数为采样率和用户信息。最后调用register_health_algorithm_callback(...)注册算法回调函数和启动G-SENSOR。

d) void health_algorithm_cb_implement(algorithm_event_t *event, void* user_data)

该函数为Baidu算法的回调函数(睡眠数据的回调也是它)。当发生计步事件后(非G-SENSOR触发事件),会调用此回调函数。由于关闭了SPORTS_DATA_MOCK_TEST宏,因此其实通过判断event->event_common.type去执行handle_distance_event()或handle_sleep_event(),如果为DISTANCE_EVENT则为动作事件,SLEEP_EVENT则为睡眠事件,event->event_common.type是在算法内赋值的。

e) void handle_distance_event(algorithm_event_t *event)

输入参数event包含着此次采样的步数运动模式(快走、快走、跑)等相关信息,在该函数内只需要读取出来并填充至相关全局变量中即可。另外在该函数内还作了目标完成度的判断。

10.睡眠数据采样

duBand睡眠起始由4个连续敲击开始。敲击事件触发后调用two_Double_tap_handle()进行处理,该函数再调用switch_sleep_state()进行睡眠状态的切换。

a) void two_Double_tap_handle(void)

duBand睡眠起始由4个连续敲击开始。敲击事件触发后调用two_Double_tap_handle()进行处理,再调用switch_sleep_state()进行睡眠状态切换。

b) void switch_sleep_state( int sleep_time_by_minutes )

该函数调用set_curr_sleep_flag()改变curr_sleep_flag标志。同时设置manual_sleep_event变量。

c) static void update_wall_clock(void * p_context)

秒定时器服务。该服务在system_clock_init()中初始化并启动,每秒执行一次。在此函数内还做了一个分钟中断(通过判断全局秒数判断),分钟中断服务函数为minute_timer_handler()。

d) void minute_timer_handler(void * val)

分钟定时中断服务函数。判断一天的开始(数据清零)也是在此函数中。最后调用update_sleep_status()处理睡眠数据。

e) void update_sleep_status(void)

在该函数内使用到刚才第b步设置的curr_sleep_flag标志。如果判断到目前为非睡眠状态并且打开了FEATURE_AUTO_SLEEP宏,则系统自动判断是否需要进入睡眠模式,如需要则调用switch_sleep_state()切换。如果判断到处于睡眠状态并且打开了FEATURE_AUTO_WAKEUP宏,则会根据睡眠时长或动作量判断是否需要调用switch_sleep_state()切换睡眠状态。

f) void health_algorithm_cb_implement(algorithm_event_t *event, void* user_data)

该函数为Baidu算法的回调函数(运动数据采集的回调也是它)。当发生计步事件后(非G-SENSOR触发事件),会调用此回调函数。由于关闭了SPORTS_DATA_MOCK_TEST宏,因此其实通过判断event->event_common.type去执行handle_distance_event()或handle_sleep_event(),如果为DISTANCE_EVENT则为动作事件,SLEEP_EVENT则为睡眠事件,event->event_common.type是在算法内赋值的。

g) void handle_sleep_event(algorithm_event_t *event)

该函数主要是调用void sleep_evt_handler(algorithm_event_t *event)进一步处理。

h) void sleep_evt_handler(algorithm_event_t *event)

先填充睡眠信息等全局变量,再填充g_sleep_send_buffer数据(该数据在定时发送时会使用)。接着调用save_sleep_group_data()保存时间数据。

分析到目前为止发现在睡眠数据采集的相关代码中有两段处理。第一段(a~e)是通过连续4次敲击启动进入睡眠,第二段(f~h)是算法的回调函数计算睡眠数据。这两段处理没看出有什么联系。查看协议文档后发现有一指令为睡眠设定数据返回(CMD=0X05,KEY=0X05),该指令发送的是在连续4次敲击事件中设定sleep_events_buff数据。在send_all_data()中连同运动数据、睡眠数据会一起发送,估计APP是先判断睡眠设定数据中是否为睡眠状态,如果是则记录为睡眠,睡眠的详细数据在g_sleep_send_buffer中。如果为非睡眠状态,就算g_sleep_send_buffer数据中有数据,都不会识别为已睡眠。

再深入分析,觉得可能是因为在sleep_evt_handler()中设置了current_sleep_event.mode与current_sleep_event.starting_time_stamp变量,然后在update_sleep_status()判断这两变量的值,才进入睡眠。

11.自动检测睡眠状态

打开了FEATURE_AUTO_SLEEP宏后,在update_sleep_status()函数中会自动判定用户是否进入睡眠状态。

经串口输出调试,发现在BaiDu算法的睡眠跟踪分为三种模式NONE_SLEEP、LIGHT_SLEEP和DEEP_SLEEP。当有7分钟处于不活动状态时,current_sleep_event.mode=LIGHT_SLEEP,进入轻睡眠。又再10分钟(共不活动17分钟)不活动状态后,current_sleep_event.mode=DEEP_SLEEP,进入深睡眠。

a)  if( tm->hour >= 22 || tm->hour <= 4 )

先判断是否在正常睡眠的时间段内,duBand设定了默认的睡眠时间段为10PM~5AM。

b) get_current_sleep_data( &sleep_event )

接着获取sleep_event,里面包括睡眠模式(深睡、浅睡)、睡眠起始时间。

c) if( ( sleep_event.mode == DEEP_SLEEP ) && ( ((int)get_wall_clock_time_counter() - sleep_event.starting_time_stamp ) > (30*60)) ){   //30 minutes

这里判断是否为深度睡眠(DEEP_SLEEP),并且深度睡眠时间大于30分钟。如果两个条件都满足,证明开始进入睡眠模式,调用switch_sleep_state()切换为睡眠状态。

d) if( sleep_event.starting_time_stamp/60 <= sleepSetting_minutes + 30 ) return;    //in 30 minutes, just skip

如果睡眠时间(sleep_event.starting_time_stamp)未足半小时,则不进行切换。

e) if(manual_sleep_events_head->length > MAX_SLEEP_EVT_LEN) return;

manual_sleep_events_head->length在switch_sleep_state()每次调用加一。如果此值大于20(MAX_SLEEP_EVT_LEN),即10小时,也不进行切换。

f) switch_sleep_state( sleep_event.starting_time_stamp/60 - 10 ); //15 minutes before.

如果上面的条件都满足后,则调用switch_sleep_state()切换睡眠状态。该函数的参数为sleepSetting_minutes的值。这里的调用表示用户在开始进入睡眠的时间的前10分钟开始睡眠,目前已睡眠的时间长度为(当前时间-sleepSetting_minutes)。

12.自动睡眠唤醒

当打开FEATURE_AUTO_WAKEUP宏后,系统将启动自己睡眠唤醒功能,相关代码在update_sleep_status()函数内,该函数一分钟调用一次。

a) sleeping_time = get_wall_clock_time_counter()/60 - sleepSetting_minutes;

计算目前睡眠的时长,使用当前分钟数减去睡眠开始时的分钟数得出。

b) if( sleeping_time >= (12*60) ){   //12 hours

如果睡眠时长大于12个小时,则强制调用two_Double_tap_handle()进行睡眠唤醒。

c) }else if( sleeping_time >= (4*60) ){    //4 hours

如果睡眠时长大于4个小时,则可以根据BaiDu算法去唤醒。如果总步数(从4小时后开始计算)大于200或运动时间大于10分钟,则调用two_Double_tap_handle()唤醒。

如果睡眠时间小于4小时,只能通过敲击去唤醒睡眠。

13.敲击事件

a) start_click_algorithm()

敲击事件的初始化在main函数中调用start_click_algorithm()进行。

调用百度算法库的int init_click_algorithm(int sample_rate)进行敲击算法初始化,参数为采样率。

调用百度算法库的int register_click_algorithm_callback(click_algorithm_cb cb, void *user_data)设置事件回调函数,敲击事件发生后,会调用所设置的回调。

b) void click_algorithm_cb_implement(click_event_t *event, void* user_data)

该函数为敲击事件的回调函数。敲击事件发生后,启动m_double_tap_timer_id定时器处理服务,如果服务已经启动过(通过判断Taped_cnt是否为1),则不需要再开启。

c) void tap_timer_handler(void * val)

该函数为m_double_tap_timer_id所绑定的敲击定时器服务。在该函数中程序判断为长按还是短按,通过app_sched_event_put()将事件类型发给input_event_handle_schedule()函数。

d) void input_event_handle_schedule(void * p_event_data, uint16_t event_size)

该函数是最终响应敲击事件的处理过程。例如在手机申请绑定的过程中,如果为短按则调用bond_press_handle()进行绑定确认处理。

14.手机绑定/登录

a) uint32_t  resolve_private_bond_command(uint8_t key,const uint8_t * value ,uint16_t length)

手机先发送绑定命令进行绑定。该函数对绑定命令的操作先检测电量是否大于20%,小于20%是不能进行绑定的。接着保存手机发过来的用户ID到user_id数组中。接着启动user_action_delay_timer定时器服务。最后调用notification_start(NOTIFICATION_BONDING,0)设置LED闪烁事件。

b) static void user_action_timeout_handle(void * context)

该函数为user_action_delay_timer所绑定的敲击定时器服务。该服务在绑定/登录超时后调用,用于关闭相关LED闪烁,释放资源等。通过参数DO_BOND与DO_WAIT_BOND_COMMAND区分是绑定过程还是登录过程。该服务在调用 resolve_private_bond_command()时会关掉。

c) uint32_t bond_store_user_id(void)

绑定命令执行后,调用该函数保存用于ID。该函数是在ble_evt_dispatch()中调用的。ble_evt_dispatch()为蓝牙协议栈的事件响应回调函数。

要该函数执行,必须should_update_prv_bond_info变量要为true。只有在一种情况下should_update_prv_bond_info变量会置为true。就是在上面敲击事件所分析的 bond_press_handle()调用。

d) uint32_t check_user_id_bonded(const uint8_t* user_id, uint8_t length)

如果为登录命令,则调用该函数判断该用户ID是否已经进行过绑定。如果已进行绑定,则可以登录成功,否则要重新进行绑定。注意重新绑定时会删除存储在手环中的用户profile。

15.外部中断(GPIOTE)

a) void gpiote_init(void)

此函数为GPIOTE初始化函数,在init2()中调用。该函数再调用nrf_gpiote_task_config()进行进一步初始化。

nrf_gpiote_task_config()为一个内联函数,编译时会展开至调用函数里。经分析此函数只是初始化了一个BUZZER任务。

b) LIS3DH_INT1和LIS3DH_INT2

经过全局搜索SENSOR中断引脚的配置宏,发现还有一个GPIOTE初始化函数。

c) void gpiote_int_init(void)

该函数进行G-SENSOR、CHARGE、BUTTON输入中断的初始化。将中断连接到相应的XXX_event_handler()中。

d) APP_BUTTON_INIT(...)

通过调用此宏函数初始化各个外部中断(G-SENSOR、CHARGE、BUTTON中断都被当成BUTTON中断处理)。函数中调用app_button_init()进行进一步初始化处理。

app_button_init()主要初始化中断的电平变化状态和使用app_gpiote_user_register()注册中断。app_gpiote_user_register()中则是将相关的中断处理服务关联到mp_users[]中。在GPIOTE_IRQHandler()中就是以mp_users为接口去调用相应中断的。

e) void GPIOTE_IRQHandler(void)

此函数为GPIOTE的中断服务函数入口处,发生外部中断后则进入此处理,可处理按键或G-SENSOR唤醒。在这里再跳转到相应的XXX_event_handler()中。

16.按键

需要使用按键功能,需要在MDK项目配置中定义HAS_BUTTON宏。该宏打开后,在timers_create()中会增加创建btn_identi定时器,服务函数为btn_timeout_handler(),用于查询按键是否按下。

同时还要在板级配置的H文件中打开FEATURE_BUTTON宏,用于连接按键中断到function_button_event_handler()中。在该handler函数中启动btn_identi定时器。

a) void function_button_event_handler(uint8_t pin_no)

注册好中断好,当产生按键中断,则会进入此函数处理。该函数判断是否输出正确的键值,并开启定时器,用于判断是否长按。如果按键为短按,则不启动定时器,直接调用app_sched_event_put(&type,sizeof(PressType),input_event_handle_schedule)发送给调试器,按键类型为短按。

b) void btn_timeout_handler(void * p_context)

如果定时器服务发生中断,则说明为长按,app_sched_event_put(&type,sizeof(PressType),input_event_handle_schedule)发送给调试器,按键类型为长按。

17.电量检测

电池电量存入在全局变量g_battery_voltage_mv中,从名字上可以看出单位为mV。电池百分比存放在全局变量percentage_batt_lvl中。

a) void battery_adc_dev_init(void)

ADC初始化,在main()-->init1()中调用,主要初始化AD转换器。

b) void battery_measure_init()

电池测量初始化(应用级),在main()-->init1()中调用,主要初始化应用层的电池电量检测相关任务。

c) void ADC_IRQHandler(void)

ADC转换完成中断,当一个ADC转换完成时进入该中断。该函数计算电量值g_battery_voltage_mv与电池百分比percentage_batt_lvl。

d) uint8_t voltage_detect_n_precharging(void)

系统启动时(在main()中)调用该函数是否需要预充电。如果检测到的电压小于MIN_BAT_MV(注意:因为duBand使用的是锂电池,所以此值设定为3600,及3.6V。我们手环使用的是锂锰电池,需要修改此值。转换为百分比的函数uint8_t cal_percentage(uint16_t volatage)也需要修改,可以使用NRF_SDK中HRS例程的)。如果检测电量低于此值,则调用power_system_off()关闭系统(不可唤醒)。

e) void bas_init(void)

该函数用于初始化BLE的电池电量服务(调用ble_bas_init(...)实现)。BLE电量服务发送的不是电压值,而是百分比。具体实现参考代码即可,因为跟蓝牙协议相关的东西,所以不深入分析这里。

【NRF51822】百度手环开源源码分析--框架部分相关推荐

  1. 【NRF51822】百度手环开源源码分析--存储部分

    一.编写说明 对duBand源码的存储部分进行分析,本文档用于记录分析过程. 二.源码分析 1. 存储区域划分 在使用nRF Studio进入程序下载时,可以看出,存储区域分为三个Region,如下图 ...

  2. 【NRF51822】百度手环开源源码分析--底层通讯部分

    一.编写说明 duBand源码中的通讯部分源码较多,但细细阅读发现其分层结构清晰,很值得借鉴.为了深入对duBand通讯源码的学习,并理解通讯分层设计的思想,决定对duBand源码的通讯部分进行分析, ...

  3. 【NRF51822】百度手环开源源码分析--数据自动同步部分

    1.  void send_all_data(boolis_from_cb) 发送数据都是通过send_all_data()去上传的. 1)  static boolneed_send_sync_pr ...

  4. MYQQA 框架开源源码

    简介: MYQQA 框架开源源码 , 仅供研究学习使用! 链接:

  5. 分享网上流行的50+开源源码下载

    本文推荐分享网上流行的50+开源源码下载,在网站收集的包含各类源码下载,php源码,asp源码,本页源码内容没有经过测试,完全开源免费,大家可以自行试用,最受IT公司欢迎的30款开源软件,你也值得拥有 ...

  6. 2022全球20多款知名的Android刷机ROM镜像和Android系统开源源码(覆盖全球机型)

    推荐阅读 ​Android10系统定制|frida逆向分析实战课程 2022全球20多款知名的Android刷机ROM镜像和Android系统开源源码(覆盖全球机型) 因此,您拥有一台Android设 ...

  7. 全网首发2023全新ChatGPT3.5小程序开源源码 全新UI

    源码简介: 2023全新ChatGPT3.5小程序开源源码 全新UI 全网首发 这一版本ui比较好看 回复速度也快了 小程序是java的 带后台 本来准备给你们带上接口的然后么后台是和接口连接的 我改 ...

  8. Android开源源码推荐(一)

    qianqianlianmeng Android开源源码推荐(一) 1.Android-ViewPagerIndicator http://www.akaifa.com/code/86/android ...

  9. 很火的仿soul交友盲盒1.0全开源源码

    简介: 目前很火的仿soul交友盲盒1.0全开源源码,2000块钱购买的一套仿soul盲盒交友开源源码 网盘下载地址: http://kekewl.org/UUqVYnPqS0t0 图片:

最新文章

  1. python项目超级大脑-python项目之超级大脑
  2. 如何设计好词袋模型BoW模型的类类型
  3. 张小龙内部分享:一个产品只能有一个主线功能
  4. The working copy is locked due to a previous error.
  5. 在C#2.0中使用Nullable可空类型
  6. curPos和tgtPos
  7. Python 16进制与字符串的转换、二进制 to 十进制、十六进制 to 十进制、十进制 to 二进制
  8. Win7_64位使用32位Mysql配置Mysql Odbc
  9. JDK+SDK 环境变量记录
  10. 这座中国小城,靠“造假”称霸一个全球市场
  11. freemarker框架 在easyui页面中处理数字 比如在页面得到1,234
  12. java对象存储管理
  13. 初识 Powershell 5.0 class
  14. 记录一次es商品模糊查询
  15. 文件夹1KB快捷方式(暴风一号)病毒的解决办法
  16. 设置计算机名和ip 一键,批量设置IP地址和计算机名
  17. 网络安全简历如何写?
  18. cesium 漫游飞行_Cesium 之三维漫游飞行效果实现篇
  19. Cannot save setting
  20. python语言支持函数式编程_python 函数式编程学习笔记

热门文章

  1. 10月27日Scalers签名书获奖结果
  2. Linux基础学习——用户权限管理
  3. 英语常见短语汇总001
  4. 超边际分析不能用计算机,超边际分析方法
  5. Qt信号槽机制详解及案例
  6. 优秀代码的必知必会(二)?
  7. 光纤仿真相关参数——光纤损耗、数值孔径、归一化参数
  8. Error: Message failed: 554 5.2.0 STOREDRV.Submission.Exception:OutboundSpamException;
  9. mysql+sql+子查询语句_SQL语句:子查询
  10. 深天马A:正在筹划非公开发行股票事项